簡體   English   中英

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

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

我想將匿名 object 作為方法的參數,然后迭代其屬性以將每個屬性/值添加到動態ExpandoObject

所以我需要的是從 go

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

了解每個屬性的名稱和值,並能夠將它們添加到ExpandoObject中。

我該怎么做?

旁注:這將在我的許多單元測試中完成(我正在使用它來重構設置中的大量垃圾),因此性能在某種程度上是相關的。 我對反射的了解還不夠肯定,但據我所知,它的性能非常重要,所以如果可能的話,我寧願避免它......

后續問題:正如我所說,我將這個匿名 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));
}

反思匿名對象以獲取其屬性名稱和值,然后利用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");
    }

另一種方法是使用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;
    }
}

現在,僅當您實際嘗試通過動態get訪問屬性時,它才進行反射。 不利的一面是,如果您重復訪問同一屬性,則每次都必須進行反射。 因此,您可以緩存結果:

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 ),以允許您動態設置緩存字典中的值。

當然,反射的開銷可能不必擔心,但是對於大型物體,這可能會限制反射的影響。 也許更有趣的是它為您提供了額外的靈活性。

這是一個古老的問題,但是現在您應該可以使用以下代碼執行此操作:

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

Number = 34:類型:System.Int32

您必須使用反射...。( 此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);
}

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

在 ASP.NET 中,您可以使用RouteValueDictionary class 快速將匿名變量轉換為屬性字典。

在內部它也使用反射,但它也維護一個內部屬性緩存。 所以后續調用會快得多( 證明

因此,如果 (,) 您正在編寫一個 ASP.NET 應用程序,那么您可以使用

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

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