Wie reflektiere ich über die Mitglieder eines dynamischen Objekts?

131

Ich muss ein Wörterbuch mit Eigenschaften und deren Werten von einem Objekt abrufen, das mit dem dynamischen Schlüsselwort in .NET 4 deklariert wurde. Es scheint, dass die Verwendung von Reflexion dafür nicht funktioniert.

Beispiel:

dynamic s = new ExpandoObject();
s.Path = "/Home";
s.Name = "Home";

// How do I enumerate the Path and Name properties and get their values?
IDictionary<string, object> propertyValues = ???
Flatliner DOA
quelle

Antworten:

32

Wenn der IDynamicMetaObjectProvider die dynamischen Mitgliedsnamen bereitstellen kann, können Sie sie abrufen. Siehe GetMemberNames- Implementierung in der Apache-lizenzierten PCL-Bibliothek Dynamitey (die in Nuget zu finden ist). Sie funktioniert für ExpandoObjects und DynamicObjects, die implementiert werden, GetDynamicMemberNamesund für alle anderen IDynamicMetaObjectProvider, die ein Metaobjekt mit einer Implementierung GetDynamicMemberNamesohne benutzerdefinierte Tests bereitstellen is IDynamicMetaObjectProvider.

Nachdem Sie die Mitgliedsnamen erhalten haben, ist es etwas aufwendiger, den Wert richtig zu ermitteln, aber Impromptu tut dies, aber es ist schwieriger, nur auf die interessanten Punkte hinzuweisen und sie sinnvoll zu machen. Hier ist die Dokumentation , die gleich oder schneller als die Reflexion ist. Es ist jedoch unwahrscheinlich, dass sie schneller als eine Wörterbuchsuche für expando ist. Sie funktioniert jedoch für jedes Objekt, expando, dynamisch oder original - wie Sie es nennen.

jbtule
quelle
17
Um auf den Punkt zu kommen, lautet der Code: var tTarget = target as IDynamicMetaObjectProvider; if (tTarget! = null) {tList.AddRange (tTarget.GetMetaObject (Expression.Constant (tTarget)). GetDynamicMemberNames ()); }
Flatliner DOA
Vielen Dank für Ihre tolle Bibliothek. Ich hatte Probleme beim Auslösen eines dynamischen Objekts in diese Bibliothek dynamicjson.codeplex.com und konnte es mit Ihrer Bibliothek erweitern.
JJS
1
Beispiel bitte.
Demodave
105

Im Fall von ExpandoObject implementiert die ExpandoObject-Klasse tatsächlich IDictionary<string, object>ihre Eigenschaften, sodass die Lösung so trivial ist wie das Casting:

IDictionary<string, object> propertyValues = (IDictionary<string, object>)s;

Beachten Sie, dass dies für allgemeine dynamische Objekte nicht funktioniert. In diesen Fällen müssen Sie über IDynamicMetaObjectProvider zum DLR wechseln.

itowlson
quelle
Vielen Dank dafür, leider war die Stichprobe etwas vereinfacht. Ich muss in der Lage sein, ein dynamisches Objekt zu untersuchen, ohne zu wissen, um welchen realen Typ es sich handelt.
Flatliner DOA
2
Dies funktioniert nur für Objekte der ExpandoObject-Klasse. Die DynamicObject-Klasse ist eine weitere erweiterbare Klasse, die kein IDictionary implementiert, sondern IDynamicMetaObjectProvider implementiert.
Sharique Abdullah
1
Es beantwortet jedoch die Frage des OP.
Sharique Abdullah
58

Es sind mehrere Szenarien zu berücksichtigen. Zunächst müssen Sie den Typ Ihres Objekts überprüfen. Sie können dazu einfach GetType () aufrufen. Wenn der Typ IDynamicMetaObjectProvider nicht implementiert, können Sie die Reflektion wie für jedes andere Objekt verwenden. Etwas wie:

var propertyInfo = test.GetType().GetProperties();

Bei IDynamicMetaObjectProvider-Implementierungen funktioniert die einfache Reflexion jedoch nicht. Grundsätzlich müssen Sie mehr über dieses Objekt wissen. Wenn es sich um ExpandoObject handelt (eine der IDynamicMetaObjectProvider-Implementierungen), können Sie die von itowlson bereitgestellte Antwort verwenden. ExpandoObject speichert seine Eigenschaften in einem Wörterbuch und Sie können Ihr dynamisches Objekt einfach in ein Wörterbuch umwandeln.

Wenn es sich um DynamicObject handelt (eine weitere IDynamicMetaObjectProvider-Implementierung), müssen Sie die von DynamicObject bereitgestellten Methoden verwenden. DynamicObject ist nicht erforderlich, um die Liste der Eigenschaften irgendwo zu "speichern". Zum Beispiel könnte es so etwas tun (ich verwende ein Beispiel aus meinem Blog-Beitrag wieder ):

public class SampleObject : DynamicObject
{
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = binder.Name;
        return true;
    }
}

In diesem Fall gibt das Objekt bei jedem Versuch, auf eine Eigenschaft (mit einem bestimmten Namen) zuzugreifen, einfach den Namen der Eigenschaft als Zeichenfolge zurück.

dynamic obj = new SampleObject();
Console.WriteLine(obj.SampleProperty);
//Prints "SampleProperty".

Sie müssen also nicht über etwas nachdenken - dieses Objekt hat keine Eigenschaften, und gleichzeitig funktionieren alle gültigen Eigenschaftsnamen.

Ich würde sagen, dass Sie für IDynamicMetaObjectProvider-Implementierungen nach bekannten Implementierungen filtern müssen, in denen Sie eine Liste von Eigenschaften wie ExpandoObject abrufen und den Rest ignorieren (oder eine Ausnahme auslösen) können.

Alexandra Rusina
quelle
7
Es scheint mir nur, dass ich, wenn der Typ, den ich konsumiere, dynamisch ist, keine Annahmen über den zugrunde liegenden Typ treffen sollte. Ich sollte GetDynamicMemberNames aufrufen können, um die Liste der Mitglieder zu erhalten. Es scheint dumm, dass statische Typen eine bessere Unterstützung für die Laufzeitinspektion haben als dynamische!
Flatliner DOA
2
Sie können die GetDynamicMemberNames () -Methode für ein dynamisches Objekt überschreiben, um die Listennamen dynamischer Mitglieder zurückzugeben. Das Problem ist, dass nicht garantiert werden kann, dass jedes dynamische Objekt über diese Methode verfügt (ExpandoObject nicht). Es ist nicht überraschend, dass die Reflexion bei statischen Typen besser funktioniert. Es wurde in erster Linie für sie geschaffen. Mit der Dynamik müssen Sie sich mehr auf Unit-Tests und TDD verlassen.
Alexandra Rusina
1
Die MSDN-Dokumentation für GetDynamicMemberNames () erwähnt: "Diese Methode existiert nur zu Debugging-Zwecken.", Nicht wirklich beruhigend :(
NicoJuicy
19

Benötigt Newtonsoft Json.Net

Ein bisschen spät, aber ich habe mir das ausgedacht. Es gibt Ihnen nur die Tasten und dann können Sie diese für die Dynamik verwenden:

public List<string> GetPropertyKeysForDynamic(dynamic dynamicToGetPropertiesFor)
{
    JObject attributesAsJObject = dynamicToGetPropertiesFor;
    Dictionary<string, object> values = attributesAsJObject.ToObject<Dictionary<string, object>>();
    List<string> toReturn = new List<string>();
    foreach (string key in values.Keys)
    {
        toReturn.Add(key);                
    }
    return toReturn;
}

Dann suchen Sie einfach so:

foreach(string propertyName in GetPropertyKeysForDynamic(dynamicToGetPropertiesFor))
{
    dynamic/object/string propertyValue = dynamicToGetPropertiesFor[propertyName];
    // And
    dynamicToGetPropertiesFor[propertyName] = "Your Value"; // Or an object value
}

Wählen Sie, ob Sie den Wert als Zeichenfolge oder als anderes Objekt abrufen oder eine andere Dynamik ausführen und die Suche erneut verwenden möchten.

Herr B.
quelle
Sie brauchen die Liste nicht, um zurückzukehren.
Aloisdg wechselt zu codidact.com
4
Perfekt! Ich musste JObject-Attribute ändernAsJObject = dynamicToGetPropertiesFor; zu ObjektattributenAsJObject = (JObject) JToken.FromObject (obj); obwohl!!
25.
Nur um noch einmal zu wiederholen, was @ k25 gesagt hat. Sie müssen durch JObject attributesAsJObject = dynamicToGetPropertiesFor;etwas ersetzen wie : var jObject = (JObject) JToken.FromObject(dynamicToGetPropertiesFor);. Zu diesem Zeitpunkt können Sie ein Wörterbuch mit Eigenschaftsnamen und -werten abrufen, indem Sie Folgendes tun var objProperties = jObject.ToObject<Dictionary<string, object>>();. Damit sind Sie auf dem Weg zu den Rennen. Dies erfordert keine Dynamik. Es funktioniert gut mit allem, was eine Unterklasse vonDynamicObject
Flydog57