【问题标题】:How do I iterate over the properties of an anonymous object in C#?如何在 C# 中迭代​​匿名对象的属性?
【发布时间】:2011-02-05 09:01:36
【问题描述】:

我想将匿名对象作为方法的参数,然后遍历其属性以将每个属性/值添加到动态ExpandoObject

所以我需要从

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

知道每个属性的名称和值,并能够将它们添加到ExpandoObject

我该如何做到这一点?

旁注:这将在我的许多单元测试中完成(我正在使用它来重构设置中的大量垃圾),因此性能在某种程度上是相关的。我对反射知之甚少,无法肯定地说,但据我所知,它的性能相当沉重,所以如果可能的话,我宁愿避免它......

后续问题: 正如我所说,我将这个匿名对象作为方法的参数。我应该在方法的签名中使用什么数据类型?如果我使用object,所有属性都可用吗?

【问题讨论】:

  • 反射性能真的不算太差。如果您有大量需要执行此操作的实例,则可以缓存给定匿名类型的 PropertyInfo 条目,然后重复这些 PropertyInfo 条目以解析每个实例的属性。您甚至可以为每个属性创建 GetMethod 委托并缓存它们。

标签: c# properties iteration anonymous-types


【解决方案1】:
foreach(var prop in myVar.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
   Console.WriteLine("Name: {0}, Value: {1}",prop.Name, prop.GetValue(myVar,null));
}

【讨论】:

  • 好的,我必须使用反射。这会是性能问题吗?另外,如何以可用于添加到 ExpandoObject 的形式获取属性名称?
  • 我解决了后续问题的问题:(MyExpandoObject as ICollection<KeyValuePair<string, object>>).Add(...) 成功了。谢谢!
  • 原则上,不应该需要反射来做到这一点。如果提供编译时循环功能,则可以更有效地实现相同的结果,并且可以将自省限制在编译时。
  • 纯粹的天才!谢谢:)
【解决方案2】:

反映匿名对象以获取其属性名称和值,然后利用 ExpandoObject 实际上是一个字典来填充它。这是一个示例,表示为单元测试:

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

【讨论】:

    【解决方案3】:

    另一种方法是使用DynamicObject 而不是ExpandoObject,这样,如果您实际上尝试从其他对象访问属性,则只有进行反射的开销。

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

    现在它只在您实际尝试通过动态获取访问属性时进行反射。不利的一面是,如果您重复访问相同的属性,则每次都必须进行反射。所以你可以缓存结果:

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

    您可以支持存储目标对象列表以合并它们的属性,并支持设置属性(使用称为TrySetMember 的类似覆盖)以允许您在缓存字典中动态设置值。

    当然,反射的开销可能不值得担心,但对于大型对象,这可能会限制它的影响。可能更有趣的是它为您提供的额外灵活性。

    【讨论】:

      【解决方案4】:

      这是一个老问题,但现在您应该可以使用以下代码做到这一点:

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

      输出如下所示:

      名称 = James Kirk:类型:System.String

      数字 = 34:类型: System.Int32

      【讨论】:

      • 问题是关于从“匿名”类型开始;这有其自身的好处。首先,使用 ExpandoObject 基本上不是 OP 所寻求的。也许他们无法控制对象的创建方式。
      【解决方案5】:

      你必须使用反射.... (code "borrowed" from this 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);
      }
      

      【讨论】:

      • 好的 - 我必须使用反射。如果我在大多数单元测试中执行此操作,每次测试一次,这会是性能问题吗?
      • @Tomas:没有办法用这么多的信息来回答这类问题。试试看吧。
      【解决方案6】:

      使用 Reflection.Emit 创建一个泛型方法来填充 ExpandoObject。

      或许可以使用表达式(我认为这只能在 .NET 4 中实现)。

      这些方法都没有在调用时使用反射,仅在设置委托期间(显然需要缓存)。

      这里有一些 Reflection.Emit 代码来填充字典(我猜 ExpandoObject 不远了);

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-10-15
        • 1970-01-01
        • 2021-03-19
        • 2020-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多