web-dev-qa-db-fra.com

Comment parcourir les propriétés d'un objet anonyme en C #?

Je veux prendre un objet anonyme comme argument d'une méthode, puis parcourir ses propriétés pour ajouter chaque propriété/valeur à un ExpandoObject dynamique.

Donc, ce dont j'ai besoin, c'est de

new { Prop1 = "first value", Prop2 = SomeObjectInstance, Prop3 = 1234 }

pour connaître les noms et les valeurs de chaque propriété et pouvoir les ajouter au ExpandoObject.

Comment est-ce que j'accomplis ceci?

Note latérale: Cela sera fait dans plusieurs de mes tests unitaires (je l'utilise pour refactoriser beaucoup de déchets dans la configuration), donc les performances sont dans une certaine mesure pertinentes. Je ne sais pas assez sur la réflexion pour dire avec certitude, mais d'après ce que j'ai compris, c'est une performance assez lourde, donc si c'est possible, je préfère l'éviter ...

Question de suivi: Comme je l'ai dit, je prends cet objet anonyme comme argument d'une méthode. Quel type de données dois-je utiliser dans la signature de la méthode? Toutes les propriétés seront-elles disponibles si j'utilise object?

53
Tomas Aschan
foreach(var prop in myVar.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
   Console.WriteLine("Name: {0}, Value: {1}",prop.Name, prop.GetValue(myVar,null));
}
68
BFree

Réfléchissez sur l'objet anonyme pour obtenir ses noms et valeurs de propriété, puis profitez d'un ExpandoObject qui est en fait un dictionnaire pour le remplir. Voici un exemple, exprimé sous forme de test unitaire:

    [TestMethod]
    public void ShouldBeAbleToConvertAnAnonymousObjectToAnExpandoObject()
    {
        var additionalViewData = new {id = "myControlId", css = "hide well"};
        dynamic result = new ExpandoObject();
        var dict = (IDictionary<string, object>)result;
        foreach (PropertyInfo propertyInfo in additionalViewData.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            dict[propertyInfo.Name] = propertyInfo.GetValue(additionalViewData, null);
        }
        Assert.AreEqual(result.id, "myControlId");
        Assert.AreEqual(result.css, "hide well");
    }
6
Jonathan Moffatt

Une autre approche consiste à utiliser DynamicObject au lieu de ExpandoObject, et de cette façon, vous n'avez la surcharge de faire la réflexion que si vous essayez réellement d'accéder à une propriété à partir de l'autre objet.

public class DynamicForwarder : DynamicObject 
{
    private object _target;

    public DynamicForwarder(object target)
    {
        _target = target;
    }

    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        var prop = _target.GetType().GetProperty(binder.Name);
        if (prop == null)
        {
            result = null;
            return false;
        }

        result = prop.GetValue(_target, null);
        return true;
    }
}

Maintenant, il ne fait que la réflexion lorsque vous essayez d'accéder à la propriété via un get dynamique. En revanche, si vous accédez à plusieurs reprises à la même propriété, elle doit faire la réflexion à chaque fois. Vous pouvez donc mettre en cache le résultat:

public class DynamicForwarder : DynamicObject 
{
    private object _target;
    private Dictionary<string, object> _cache = new Dictionary<string, object>();

    public DynamicForwarder(object target)
    {
        _target = target;
    }

    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        // check the cache first
        if (_cache.TryGetValue(binder.Name, out result))
            return true;

        var prop = _target.GetType().GetProperty(binder.Name);
        if (prop == null)
        {
            result = null;
            return false;
        }

        result = prop.GetValue(_target, null);
        _cache.Add(binder.Name, result); // <-------- insert into cache
        return true;
    }
}

Vous pouvez prendre en charge le stockage d'une liste d'objets cibles pour fusionner leurs propriétés et prendre en charge la définition de propriétés (avec un remplacement similaire appelé TrySetMember ) pour vous permettre de définir dynamiquement des valeurs dans le dictionnaire de cache.

Bien sûr, le surcoût de la réflexion ne vaudra probablement pas la peine de s'inquiéter, mais pour les gros objets, cela pourrait en limiter l'impact. Ce qui est peut-être plus intéressant, c'est la flexibilité supplémentaire qu'il vous donne.

3
Daniel Earwicker

C'est une vieille question, mais maintenant vous devriez pouvoir le faire avec le code suivant:

dynamic expObj = new ExpandoObject();
    expObj.Name = "James Kirk";
    expObj.Number = 34;

// print the dynamically added properties
// enumerating over it exposes the Properties and Values as a KeyValuePair
foreach (KeyValuePair<string, object> kvp in expObj){ 
    Console.WriteLine("{0} = {1} : Type: {2}", kvp.Key, kvp.Value, kvp.Value.GetType());
}

La sortie ressemblerait à ceci:

Nom = James Kirk: Type: System.String

Numéro = 34: Type: System.Int32

2
raddevus

vous devez utiliser la réflexion .... ( code "emprunté" à cette URL )

using System.Reflection;  // reflection namespace

// get all public static properties of MyClass type
PropertyInfo[] propertyInfos;
propertyInfos = typeof(MyClass).GetProperties(BindingFlags.Public |
                                              BindingFlags.Static);
// sort properties by name
Array.Sort(propertyInfos,
        delegate(PropertyInfo propertyInfo1, PropertyInfo propertyInfo2)
        { return propertyInfo1.Name.CompareTo(propertyInfo2.Name); });

// write property names
foreach (PropertyInfo propertyInfo in propertyInfos)
{
  Console.WriteLine(propertyInfo.Name);
}
0
Muad'Dib

Utilisez Reflection.Emit pour créer une méthode générique pour remplir un ExpandoObject.

OU utilisez peut-être des expressions (je pense que cela ne serait cependant possible que dans .NET 4).

Aucune de ces approches n'utilise la réflexion lors de l'appel, uniquement lors de la configuration d'un délégué (qui doit évidemment être mis en cache).

Voici du code Reflection.Emit pour remplir un dictionnaire (je suppose que ExpandoObject n'est pas loin);

static T CreateDelegate<T>(this DynamicMethod dm) where T : class
{
  return dm.CreateDelegate(typeof(T)) as T;
}

static Dictionary<Type, Func<object, Dictionary<string, object>>> cache = 
   new Dictionary<Type, Func<object, Dictionary<string, object>>>();

static Dictionary<string, object> GetProperties(object o)
{
  var t = o.GetType();

  Func<object, Dictionary<string, object>> getter;

  if (!cache.TryGetValue(t, out getter))
  {
    var rettype = typeof(Dictionary<string, object>);

    var dm = new DynamicMethod(t.Name + ":GetProperties", rettype, 
       new Type[] { typeof(object) }, t);

    var ilgen = dm.GetILGenerator();

    var instance = ilgen.DeclareLocal(t);
    var dict = ilgen.DeclareLocal(rettype);

    ilgen.Emit(OpCodes.Ldarg_0);
    ilgen.Emit(OpCodes.Castclass, t);
    ilgen.Emit(OpCodes.Stloc, instance);

    ilgen.Emit(OpCodes.Newobj, rettype.GetConstructor(Type.EmptyTypes));
    ilgen.Emit(OpCodes.Stloc, dict);

    var add = rettype.GetMethod("Add");

    foreach (var prop in t.GetProperties(
      BindingFlags.Instance |
      BindingFlags.Public))
    {
      ilgen.Emit(OpCodes.Ldloc, dict);

      ilgen.Emit(OpCodes.Ldstr, prop.Name);

      ilgen.Emit(OpCodes.Ldloc, instance);
      ilgen.Emit(OpCodes.Ldfld, prop);
      ilgen.Emit(OpCodes.Castclass, typeof(object));

      ilgen.Emit(OpCodes.Callvirt, add);
    }

    ilgen.Emit(OpCodes.Ldloc, dict);
    ilgen.Emit(OpCodes.Ret);

    cache[t] = getter = 
      dm.CreateDelegate<Func<object, Dictionary<string, object>>>();
  }

  return getter(o);
}
0
leppie