2010-04-07 11 views
50

Chcę wziąć obiekt anonimowy jako argument do metody, a następnie iterować jego właściwości, aby dodać każdą właściwość/wartość do dynamicznego ExpandoObject.Jak wykonać iterację właściwości anonimowego obiektu w języku C#?

Więc co potrzebne jest, aby przejść od

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

do znając nazwy i wartości każdej nieruchomości, a będąc w stanie dodać je do ExpandoObject.

Jak to zrobić?

Nota boczna: Zostanie to zrobione w wielu testach jednostkowych (używam go do usunięcia dużej ilości śmieci w konfiguracji), więc wydajność jest w pewnym stopniu istotna. Nie wiem wystarczająco dużo o refleksji powiedzieć na pewno, ale z tego co zrozumiałem to dość ciężki wydajność, więc jeśli jest to możliwe, wolałbym uniknąć ...

kolejne pytanie: Jak już powiedziałem, biorę ten anonimowy obiekt jako argument do metody. Jaki typ danych powinienem użyć w podpisie metody? Czy wszystkie właściwości będą dostępne, jeśli użyję object?

+1

Odbicie wydajności naprawdę nie jest zbyt straszne. Jeśli masz bardzo dużą liczbę instancji, do których musisz to zrobić, możesz buforować wpisy PropertyInfo dla danego anonimowego typu, a następnie powtarzać te wpisy PropertyInfo, aby rozwiązać właściwości dla każdej instancji. Można nawet tworzyć delegatów dla GetMethod dla każdej właściwości i buforować te. –

Odpowiedz

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

OK, muszę użyć odbicia. Czy to będzie problem z wydajnością? Ponadto, jak uzyskać nazwę właściwości w formularzu, który mogę użyć, aby dodać do obiektu ExpandoObject? –

+1

Rozwiązałem problem następczego pytania: '(MyExpandoObject jako ICollection >) .Dodaj (...)" zmyślił. Dzięki! –

+0

Zasadniczo Odbicie nie powinno być potrzebne, aby to zrobić. Jeśli zapewniono funkcję pętli kompilacji, ten sam wynik mógłby zostać osiągnięty bardziej efektywnie, a introspekcja mogłaby być ograniczona do czasu kompilacji. – pyon

0

trzeba użyć refleksji .... (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); 
} 
+0

OK - Muszę użyć refleksji. Czy będzie to problem z wydajnością, jeśli wykonam to w większości testów jednostkowych, raz na test? –

+1

@ Tom: Nie ma sposobu, aby odpowiedzieć na takie pytanie z taką ilością informacji. Po prostu spróbuj i zobacz. –

1

Zastosowanie Reflection.Emit stworzyć ogólny sposób, aby wypełnić ExpandoObject.

LUB używać wyrażenia być może (myślę, że to byłoby możliwe tylko w .NET 4).

Żadne z tych podejść nie używa odbicia podczas wywoływania, tylko podczas konfigurowania delegata (który oczywiście musi być buforowany).

Oto kilka Reflection.Emit kodu, aby wypełnić słownik (chyba ExpandoObject nie jest daleko);

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

Alternatywnym podejściem jest użycie DynamicObject zamiast ExpandoObject, iw ten sposób masz napowietrznej robi odbicie tylko jeśli rzeczywiście próby uzyskania dostępu do właściwości z innego obiektu.

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

Teraz robi tylko odbicie, gdy faktycznie próbuje się uzyskać dostęp do nieruchomości za pomocą dynamicznego pobierania. Z drugiej strony, jeśli wielokrotnie korzystasz z tej samej właściwości, musisz za każdym razem robić odbicie. Więc można buforować wynik:

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

Można obsługiwać przechowywania listę obiektów docelowych łączyć ich właściwości i ustawienie wsparcie właściwości (z podobnym ręcznym nazywa TrySetMember), co pozwala na dynamiczne ustawianie wartości w pamięci podręcznej słownik.

Oczywiście, narzut na refleksję prawdopodobnie nie będzie warty martwienia się, ale w przypadku dużych obiektów może to ograniczyć jego wpływ. Co może być bardziej interesujące, to dodatkowa elastyczność, którą daje.

6

Zastanów się nad anonimowym obiektem, aby uzyskać jego nazwy i wartości właściwości, a następnie skorzystaj z obiektu ExpandoObject będącego słownikiem do wypełnienia go.Oto przykład, wyrażony jako test jednostki:

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

To stara sprawa, ale teraz powinieneś być w stanie to zrobić za pomocą następującego kodu:

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

Wyjście będzie wyglądać następujące:

Name = James Kirk: Typ: System.String

Number = 34: Typ: systemu .Int32

Powiązane problemy