[英]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.