66 votes

Comment itérer sur les propriétés d'un objet anonyme en C# ?

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

Donc ce dont j'ai besoin c'est d'aller de

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

de connaître les noms et les valeurs de chaque propriété, et d'être capable de les ajouter à l' ExpandoObject .

Comment puis-je y parvenir ?

Note complémentaire : Ceci sera fait dans beaucoup de mes tests unitaires (je l'utilise pour refactoriser beaucoup de déchets dans la configuration), donc la performance est dans une certaine mesure pertinente. Je ne connais pas assez la réflexion pour en être sûr, mais d'après ce que j'ai compris, elle est assez gourmande en performances, 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 ?

87voto

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

9voto

Jonathan Moffatt Points 4322

Réfléchissez sur l'objet anonyme pour obtenir les noms et valeurs de ses propriétés, puis profitez du fait qu'un ExpandoObject est en fait un dictionnaire pour le remplir. Voici un exemple, exprimé sous la forme d'un 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");
    }

4voto

Daniel Earwicker Points 63298

Une autre approche consiste à utiliser DynamicObject au lieu de ExpandoObject De cette façon, vous n'aurez à effectuer la réflexion que si vous essayez réellement d'accéder à une propriété 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 n'effectue la réflexion que lorsque vous essayez d'accéder à la propriété via un get dynamique. En revanche, si vous accédez de manière répétée à la même propriété, la réflexion doit être effectuée à chaque fois. Vous pouvez donc mettre le résultat en cache :

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 pourriez prendre en charge le stockage d'une liste d'objets cibles pour regrouper leurs propriétés, et prendre en charge la définition des propriétés (avec une surcharge similaire appelée TrySetMember ) pour vous permettre de définir dynamiquement des valeurs dans le dictionnaire de cache.

Bien sûr, l'overhead de la réflexion ne vaut probablement pas la peine de s'en préoccuper, 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 que cela vous donne.

0voto

leppie Points 67289

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

Ou utiliser des expressions peut-être (je pense que cela ne serait possible qu'en .NET 4 cependant).

Aucune de ces approches n'utilise la réflexion lors de l'invocation, seulement 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);
}

0voto

Muad'Dib Points 14260

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);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X