简体   繁体   English

如何在 C# 中迭代匿名 object 的属性?

[英]How do I iterate over the properties of an anonymous object in C#?

I want to take an anonymous object as argument to a method, and then iterate over its properties to add each property/value to aa dynamic ExpandoObject .我想将匿名 object 作为方法的参数,然后迭代其属性以将每个属性/值添加到动态ExpandoObject

So what I need is to go from所以我需要的是从 go

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

to knowing names and values of each property, and being able to add them to the ExpandoObject .了解每个属性的名称和值,并能够将它们添加到ExpandoObject中。

How do I accomplish this?我该怎么做?

Side note: This will be done in many of my unit tests (I'm using it to refactor away a lot of junk in the setup), so performance is to some extent relevant.旁注:这将在我的许多单元测试中完成(我正在使用它来重构设置中的大量垃圾),因此性能在某种程度上是相关的。 I don't know enough about reflection to say for sure, but from what I've understood it's pretty performance heavy, so if it's possible I'd rather avoid it...我对反射的了解还不够肯定,但据我所知,它的性能非常重要,所以如果可能的话,我宁愿避免它......

Follow-up question: As I said, I'm taking this anonymous object as an argument to a method.后续问题:正如我所说,我将这个匿名 object 作为方法的参数。 What datatype should I use in the method's signature?我应该在方法的签名中使用什么数据类型? Will all properties be available if I use object ?如果我使用object是否所有属性都可用?

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

Reflect on the anonymous object to get its property names and values, then take advantage of an ExpandoObject actually being a dictionary to populate it. 反思匿名对象以获取其属性名称和值,然后利用ExpandoObject实际上是一个字典来填充它。 Here's an example, expressed as a unit test: 这是一个表示为单元测试的示例:

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

An alternative approach is to use DynamicObject instead of ExpandoObject , and that way you only have the overhead of doing the reflection if you actually try to access a property from the other object. 另一种方法是使用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;
    }
}

Now it only does the reflection when you actually try to access the property via a dynamic get. 现在,仅当您实际尝试通过动态get访问属性时,它才进行反射。 On the downside, if you repeatedly access the same property, it has to do the reflection each time. 不利的一面是,如果您重复访问同一属性,则每次都必须进行反射。 So you could cache the result: 因此,您可以缓存结果:

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

You could support storing a list of target objects to coalesce their properties, and support setting properties (with a similar override called TrySetMember ) to allow you to dynamically set values in the cache dictionary. 您可以支持存储目标对象列表以合并其属性,并支持设置属性(具有类似的替代方法TrySetMember ),以允许您动态设置缓存字典中的值。

Of course, the overhead of reflection is probably not going to be worth worrying about, but for large objects this could limit the impact of it. 当然,反射的开销可能不必担心,但是对于大型物体,这可能会限制反射的影响。 What is maybe more interesting is the extra flexibility it gives you. 也许更有趣的是它为您提供了额外的灵活性。

This is an old question, but now you should be able to do this with the following code: 这是一个古老的问题,但是现在您应该可以使用以下代码执行此操作:

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

The output would look like the following: 输出如下所示:

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

Number = 34 : Type: System.Int32 Number = 34:类型:System.Int32

you have to use reflection.... ( code "borrowed" from this url ) 您必须使用反射...。( 此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);
}

Use Reflection.Emit to create a generic method to fill an ExpandoObject. 使用Reflection.Emit创建一个通用方法来填充ExpandoObject。

OR use Expressions perhaps (I think this would only be possible in .NET 4 though). 或者也许使用表达式(尽管我认为这只能在.NET 4中实现)。

Neither of these approaches uses reflection when invoking, only during setup of a delegate (which obviously needs to be cached). 这些方法都没有在调用时使用反射,仅在设置委托时(显然需要缓存)。

Here is some Reflection.Emit code to fill a dictionary (I guess ExpandoObject is not far off); 这是一些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);
}

In ASP.NET you can use the RouteValueDictionary class to quickly convert an anonymous variable into a properties dictionary.在 ASP.NET 中,您可以使用RouteValueDictionary class 快速将匿名变量转换为属性字典。

Internally it uses reflection too, but it also maintains an internal cache of properties.在内部它也使用反射,但它也维护一个内部属性缓存。 So subsequent calls will be much faster ( proof )所以后续调用会快得多( 证明

So if (,) you are writing an ASP.NET app, then you can use因此,如果 (,) 您正在编写一个 ASP.NET 应用程序,那么您可以使用

var d = new RouteValueDictionary(new { A = 1, B = 2 });
d["A"] //equals 1
d["B"] //equals 2

PS This class is used every time you write ASP.NET code like this: PS 这个 class 每次你写 ASP.NET 这样的代码时都会用到:

Url.Action("Action, "Controller", new { parameter = xxx })

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM