Comment réfléchir sur les membres de l'objet dynamique?

131

J'ai besoin d'obtenir un dictionnaire de propriétés et de leurs valeurs à partir d'un objet déclaré avec le mot-clé dynamique dans .NET 4? Il semble que l'utilisation de la réflexion ne fonctionnera pas.

Exemple:

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
la source

Réponses:

32

Si IDynamicMetaObjectProvider peut fournir les noms de membres dynamiques, vous pouvez les obtenir. Voir l' implémentation de GetMemberNames dans la bibliothèque PCL sous licence Apache Dynamitey (qui peut être trouvée dans nuget), cela fonctionne pour les ExpandoObjects et DynamicObjects qui implémentent GetDynamicMemberNameset tout autre IDynamicMetaObjectProviderqui fournit un méta-objet avec une implémentation GetDynamicMemberNamessans test personnalisé au-delà is IDynamicMetaObjectProvider.

Après avoir obtenu les noms des membres, il faut un peu plus de travail pour obtenir la valeur de la bonne manière, mais Impromptu le fait, mais il est plus difficile de pointer uniquement les éléments intéressants et de donner un sens. Voici la documentation et elle est égale ou plus rapide que la réflexion, cependant, il est peu probable qu'elle soit plus rapide qu'une recherche dans le dictionnaire pour expando, mais cela fonctionne pour n'importe quel objet, expando, dynamique ou original - vous l'appelez.

jbtule
la source
17
Merci d'aller au point, le code est: var tTarget = target as IDynamicMetaObjectProvider; if (tTarget! = null) {tList.AddRange (tTarget.GetMetaObject (Expression.Constant (tTarget)). GetDynamicMemberNames ()); }
Flatliner DOA
merci pour votre superbe bibliothèque. J'avais des problèmes pour faire le tour d'un objet dynamique vers cette bibliothèque dynamicjson.codeplex.com , et j'ai pu l'étendre avec votre bibliothèque.
JJS
1
exemple s'il vous plaît.
Demodave le
105

Dans le cas de ExpandoObject, la classe ExpandoObject implémente en fait IDictionary<string, object>pour ses propriétés, donc la solution est aussi simple que la conversion :

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

Notez que cela ne fonctionnera pas pour les objets dynamiques généraux. Dans ces cas, vous devrez accéder au DLR via IDynamicMetaObjectProvider.

Itowlson
la source
Merci pour cela, malheureusement, l'échantillon était un peu simplifié. J'ai besoin de pouvoir inspecter un objet dynamique sans savoir quel est son type réel.
Flatliner DOA
2
Cela ne fonctionne que pour les objets de la classe ExpandoObject, la classe DynamicObject est une autre classe extensible qui n'implémente pas IDictionary mais implémente plutôt IDynamicMetaObjectProvider.
Sharique Abdullah
1
Cela répond cependant à la question du PO.
Sharique Abdullah
58

Il existe plusieurs scénarios à considérer. Tout d'abord, vous devez vérifier le type de votre objet. Vous pouvez simplement appeler GetType () pour cela. Si le type n'implémente pas IDynamicMetaObjectProvider, vous pouvez utiliser la réflexion comme pour tout autre objet. Quelque chose comme:

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

Cependant, pour les implémentations IDynamicMetaObjectProvider, la réflexion simple ne fonctionne pas. En gros, vous devez en savoir plus sur cet objet. S'il s'agit de ExpandoObject (qui est l'une des implémentations IDynamicMetaObjectProvider), vous pouvez utiliser la réponse fournie par itowlson. ExpandoObject stocke ses propriétés dans un dictionnaire et vous pouvez simplement convertir votre objet dynamique dans un dictionnaire.

S'il s'agit de DynamicObject (une autre implémentation de IDynamicMetaObjectProvider), vous devez utiliser les méthodes que ce DynamicObject expose. DynamicObject n'est pas obligé de "stocker" sa liste de propriétés n'importe où. Par exemple, cela pourrait faire quelque chose comme ceci (je réutilise un exemple de mon article de blog ):

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

Dans ce cas, chaque fois que vous essayez d'accéder à une propriété (avec un nom donné), l'objet renvoie simplement le nom de la propriété sous forme de chaîne.

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

Donc, vous n'avez rien à réfléchir - cet objet n'a aucune propriété, et en même temps tous les noms de propriété valides fonctionneront.

Je dirais que pour les implémentations IDynamicMetaObjectProvider, vous devez filtrer sur les implémentations connues où vous pouvez obtenir une liste de propriétés, telles que ExpandoObject, et ignorer (ou lever une exception) pour le reste.

Alexandra Rusina
la source
7
Il me semble juste que si le type que je consomme est Dynamique, je ne devrais pas faire d'hypothèses sur son type sous-jacent. Je devrais pouvoir appeler GetDynamicMemberNames pour obtenir la liste des membres. Il semble stupide que les types statiques aient un meilleur support d'inspection d'exécution que les dynamiques!
Flatliner DOA
2
Vous pouvez remplacer la méthode GetDynamicMemberNames () pour qu'un objet dynamique renvoie les noms de liste des membres dynamiques. Le problème est qu'il n'est pas garanti que chaque objet dynamique possède cette méthode (ExpandoObject ne le fait pas). Il n'est pas surprenant que la réflexion fonctionne mieux pour les types statiques. Il a été créé pour eux en premier lieu. Avec la dynamique, vous devez vous fier davantage aux tests unitaires et au TDD.
Alexandra Rusina
1
La documentation MSDN pour GetDynamicMemberNames () mentionne: "Cette méthode existe uniquement à des fins de débogage.", Pas vraiment réconfortant :(
NicoJuicy
19

Nécessite Newtonsoft Json.Net

Un peu tard, mais j'ai trouvé ça. Il ne vous donne que les clés et vous pouvez ensuite les utiliser sur la dynamique:

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

Ensuite, vous foreach simplement comme ceci:

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

Choisir d'obtenir la valeur sous forme de chaîne ou d'un autre objet, ou de faire une autre dynamique et d'utiliser à nouveau la recherche.

Monsieur B
la source
Vous n'avez pas besoin de la liste pour retourner.
aloisdg passe à codidact.com
4
Parfait! J'ai dû changer les attributs JObjectAsJObject = dynamicToGetPropertiesFor; aux attributs de l'objetAsJObject = (JObject) JToken.FromObject (obj); bien que!!
k25
Juste pour réitérer ce que @ k25 a dit. Vous devez remplacer JObject attributesAsJObject = dynamicToGetPropertiesFor;par quelque chose comme: var jObject = (JObject) JToken.FromObject(dynamicToGetPropertiesFor);. À ce stade, vous pouvez obtenir un dictionnaire des noms de propriétés et des valeurs en faisant quelque chose comme var objProperties = jObject.ToObject<Dictionary<string, object>>();. Avec cela en main, vous partez pour les courses. Cela n'a pas besoin d'une dynamique. Cela fonctionne bien avec tout ce qui est une sous-classe deDynamicObject
Flydog57