如何在 C# 中迭代​​匿名对象的属性?

发布于 2024-08-28 08:59:32 字数 486 浏览 8 评论 0原文

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

所以我需要的是了解

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

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

我该如何实现这个目标?

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

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

I want to take an anonymous object as argument to a method, and then iterate over its properties to add each property/value to a a dynamic ExpandoObject.

So what I need is to go from

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.

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. What datatype should I use in the method's signature? Will all properties be available if I use object?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

半枫 2024-09-04 08:59:33

在 ASP.NET 中,您可以使用 RouteValueDictionary 类快速将匿名变量转换为属性字典。

在内部它也使用反射,但它还维护属性的内部缓存。因此后续调用会快得多(证明)

因此,如果 (!) 您正在编写 ASP.NET 应用程序,那么您可以使用

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

P.S.每次编写 ASP.NET 代码时都会使用此类:

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

In ASP.NET you can use the RouteValueDictionary class to quickly convert an anonymous variable into a properties dictionary.

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

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

P.S. This class is used every time you write ASP.NET code like this:

Url.Action("Action, "Controller", new { parameter = xxx })
顾忌 2024-09-04 08:59:32
foreach(var prop in myVar.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
   Console.WriteLine("Name: {0}, Value: {1}",prop.Name, prop.GetValue(myVar,null));
}
foreach(var prop in myVar.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
   Console.WriteLine("Name: {0}, Value: {1}",prop.Name, prop.GetValue(myVar,null));
}
相守太难 2024-09-04 08:59:32

反思匿名对象以获取其属性名称和值,然后利用实际上是字典的 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");
    }

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. 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");
    }
寄人书 2024-09-04 08:59:32

另一种方法是使用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) 允许您动态设置缓存字典中的值。

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

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.

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. 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.

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.

×眷恋的温暖 2024-09-04 08:59:32

这是一个老问题,但现在您应该能够使用以下代码来执行此操作:

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:类型:
系统.Int32

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

Number = 34 : Type:
System.Int32

千年*琉璃梦 2024-09-04 08:59:32

你必须使用反射......(从此网址“借用”的代码)

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

you have to use reflection.... (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);
}
Oo萌小芽oO 2024-09-04 08:59:32

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

Use Reflection.Emit to create a generic method to fill an ExpandoObject.

OR use Expressions perhaps (I think this would only be possible in .NET 4 though).

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

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文