Comment accéder à la propriété de type anonyme en C #?
125
J'ai ceci:
List<object> nodes =newList<object>();
nodes.Add(new{Checked=false,
depth =1,
id ="div_"+ d.Id});
... et je me demande si je peux alors saisir la propriété "Checked" de l'objet anonyme. Je ne suis même pas sûr que cela soit possible. J'ai essayé de faire ceci:
if (nodes.Any(n => n["Checked"] == false)) ... mais ça ne marche pas.
Si vous voulez une liste fortement typée de types anonymes, vous devrez également faire de la liste un type anonyme. Le moyen le plus simple de le faire est de projeter une séquence telle qu'un tableau dans une liste, par exemple
var nodes =(new[]{new{Checked=false,/* etc */}}).ToList();
Ensuite, vous pourrez y accéder comme:
nodes.Any(n => n.Checked);
En raison du fonctionnement du compilateur, les éléments suivants devraient également fonctionner une fois que vous avez créé la liste, car les types anonymes ont la même structure et sont donc également du même type. Je n'ai pas de compilateur sous la main pour vérifier cela.
Si vous stockez l'objet en tant que type object, vous devez utiliser la réflexion. Cela est vrai de tout type d'objet, anonyme ou non. Sur un objet o, vous pouvez obtenir son type:
Type t = o.GetType();
Ensuite, vous recherchez une propriété:
PropertyInfo p = t.GetProperty("Foo");
Ensuite, vous pouvez obtenir une valeur:
object v = p.GetValue(o,null);
Cette réponse est attendue depuis longtemps pour une mise à jour pour C # 4:
dynamic d = o;object v = d.Foo;
Et maintenant une autre alternative en C # 6:
object v = o?.GetType().GetProperty("Foo")?.GetValue(o,null);
Notez qu'en utilisant ?.nous faisons en sorte que le résultat vse trouve nulldans trois situations différentes!
oest null, donc il n'y a aucun objet du tout
on'est nullpas - mais n'a pas de propriétéFoo
oa une propriété Foomais sa valeur réelle se trouve être null.
Ce n'est donc pas équivalent aux exemples précédents, mais cela peut avoir du sens si vous voulez traiter les trois cas de la même manière.
Jamais utilisé de dynamique auparavant, belle mise à jour pour .NET 4.0
Alan
dans la solution c # 4, vous obtiendrez une exception d'exécution si la propriété n'existe pas ( object v = d.Foo), alors GetValue(o, null)qu'elle sera nulle si elle n'existe pas.
YaakovHatam
1
Non, GetPropertyreviendra nullet GetValuelancera si cela est passé null, donc l'effet global est une exception. La version C # 4.0 donne une exception plus descriptive.
Daniel Earwicker
4
Si vous utilisez dynamic dans un assemblage différent de celui de la source, vous devez utiliser [InternalsVisibleTo]
Sarath
2
@DanielEarwicker merci d'avoir terminé. Cela s'applique également aux types anonymes. Comme toutes les propriétés générées pour les types anonymes sont internes.
Sarath
13
Vous pouvez parcourir les propriétés du type anonyme en utilisant Reflection; voir s'il y a une propriété "Checked" et s'il y en a alors obtenir sa valeur.
foreach(object o in nodes){Type t = o.GetType();PropertyInfo[] pi = t.GetProperties();foreach(PropertyInfo p in pi){if(p.Name=="Checked"&&!(bool)p.GetValue(o))Console.WriteLine("awesome!");}}
Si vous n'avez besoin que d'une propriété et que vous connaissez déjà son nom, il est inutile de les parcourir toutes; utilisez simplement GetProperty et GetValue. De plus, System.out.println est Java, pas C # ...
Chris Charabaruk
Oups, c'est vrai, Chris! Un peu gênant ... corrigé maintenant.
glennkentwell
6
La réponse acceptée décrit correctement comment la liste doit être déclarée et est fortement recommandée pour la plupart des scénarios.
Mais je suis tombé sur un scénario différent, qui couvre également la question posée. Que faire si vous devez utiliser une liste d'objets existante, comme ViewData["htmlAttributes"]dans MVC ? Comment pouvez - vous accéder à ses propriétés (ils sont généralement créés par l' intermédiaire new { @style="width: 100px", ... })?
Pour ce scénario légèrement différent, je veux partager avec vous ce que j'ai découvert. Dans les solutions ci-dessous, je suppose la déclaration suivante pour nodes:
List<object> nodes =newList<object>();
nodes.Add(new{Checked=false,
depth =1,
id ="div_1"});
1. Solution avec dynamique
Dans C # 4.0 et les versions supérieures , vous pouvez simplement convertir en dynamique et écrire:
if(nodes.Any(n =>((dynamic)n).Checked==false))Console.WriteLine("found not checked element!");
Remarque: cela utilise une liaison tardive, ce qui signifie qu'il ne reconnaîtra qu'au moment de l'exécution si l'objet n'a pas de Checkedpropriété et lève un RuntimeBinderExceptiondans ce cas - donc si vous essayez d'utiliser une Checked2propriété non existante , vous obtiendrez le message suivant à Durée:"'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .
2. Solution avec réflexion
La solution avec réflexion fonctionne à la fois avec les anciennes et les nouvelles versions du compilateur C # . Pour les anciennes versions C #, veuillez tenir compte de l'indication à la fin de cette réponse.
Contexte
Comme point de départ, j'ai trouvé une bonne réponse ici . L'idée est de convertir le type de données anonyme en dictionnaire en utilisant la réflexion. Le dictionnaire facilite l'accès aux propriétés, car leurs noms sont stockés sous forme de clés (vous pouvez y accéder comme myDict["myProperty"]).
Inspiré par le code dans le lien ci-dessus, j'ai créé une classe d'extension fournissant GetProp, UnanonymizePropertieset UnanonymizeListItemscomme méthodes d'extension, qui simplifient l'accès aux propriétés anonymes. Avec cette classe, vous pouvez simplement faire la requête comme suit:
if(nodes.UnanonymizeListItems().Any(n =>(bool)n["Checked"]==false)){Console.WriteLine("found not checked element!");}
ou vous pouvez utiliser l'expression nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()comme ifcondition, qui filtre implicitement puis vérifie si des éléments sont renvoyés.
Pour obtenir le premier objet contenant la propriété "Checked" et renvoyer sa propriété "depth", vous pouvez utiliser:
var depth = nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");
ou plus court: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];
Remarque: si vous disposez d'une liste d'objets qui ne contiennent pas nécessairement toutes les propriétés (par exemple, certains ne contiennent pas la propriété "Vérifié") et que vous souhaitez quand même créer une requête basée sur les valeurs "Vérifié", vous pouvez faites ceci:
if(nodes.UnanonymizeListItems(x =>{var y =((bool?)x.GetProp("Checked",true));return y.HasValue&& y.Value==false;}).Any()){Console.WriteLine("found not checked element!");}
Cela empêche, qu'un KeyNotFoundExceptionse produise si la propriété "Checked" n'existe pas.
La classe ci-dessous contient les méthodes d'extension suivantes:
UnanonymizeProperties: Est utilisé pour désanonymiser les propriétés contenues dans un objet. Cette méthode utilise la réflexion. Il convertit l'objet en un dictionnaire contenant les propriétés et ses valeurs.
UnanonymizeListItems: Permet de convertir une liste d'objets en une liste de dictionnaires contenant les propriétés. Il peut éventuellement contenir une expression lambda à filtrer au préalable.
GetProp: Est utilisé pour renvoyer une valeur unique correspondant au nom de propriété donné. Permet de traiter les propriétés non existantes comme des valeurs nulles (true) plutôt que comme KeyNotFoundException (false)
Pour les exemples ci-dessus, il suffit d'ajouter la classe d'extension ci-dessous:
publicstaticclassAnonymousTypeExtensions{// makes properties of object accessible publicstaticIDictionaryUnanonymizeProperties(thisobject obj){Type type = obj?.GetType();var properties = type?.GetProperties()?.Select(n => n.Name)?.ToDictionary(k => k, k => type.GetProperty(k).GetValue(obj,null));return properties;}// converts object list into list of properties that meet the filterCriteriapublicstaticList<IDictionary>UnanonymizeListItems(thisList<object> objectList,Func<IDictionary<string,object>,bool> filterCriteria=default){var accessibleList =newList<IDictionary>();foreach(object obj in objectList){var props = obj.UnanonymizeProperties();if(filterCriteria ==default|| filterCriteria((IDictionary<string,object>)props)==true){ accessibleList.Add(props);}}return accessibleList;}// returns specific property, i.e. obj.GetProp(propertyName)// requires prior usage of AccessListItems and selection of one element, because// object needs to be a IDictionary<string, object>publicstaticobjectGetProp(thisobject obj,string propertyName,bool treatNotFoundAsNull =false){try{return((System.Collections.Generic.IDictionary<string,object>)obj)?[propertyName];}catch(KeyNotFoundException){if(treatNotFoundAsNull)returndefault(object);elsethrow;}}}
Astuce: Le code ci-dessus utilise les opérateurs conditionnels null , disponibles depuis la version C # 6.0 - si vous travaillez avec des compilateurs C # plus anciens (par exemple C # 3.0), remplacez simplement ?.par .et ?[par [partout, par exemple
var depth = nodes.UnanonymizeListItems().FirstOrDefault(n => n.Contains("Checked"))["depth"];
Si vous n'êtes pas obligé d'utiliser un ancien compilateur C #, conservez-le tel quel, car l'utilisation de conditions nulles facilite grandement la gestion des valeurs nulles.
Remarque: comme l'autre solution avec dynamique, cette solution utilise également une liaison tardive, mais dans ce cas, vous n'obtenez pas d'exception - elle ne trouvera tout simplement pas l'élément si vous faites référence à une propriété non existante, tant car vous conservez les opérateurs conditionnels nuls .
Ce qui pourrait être utile pour certaines applications, c'est que la propriété est référencée via une chaîne dans la solution 2, elle peut donc être paramétrée.
object v = d.Foo
), alorsGetValue(o, null)
qu'elle sera nulle si elle n'existe pas.GetProperty
reviendranull
etGetValue
lancera si cela est passénull
, donc l'effet global est une exception. La version C # 4.0 donne une exception plus descriptive.Vous pouvez parcourir les propriétés du type anonyme en utilisant Reflection; voir s'il y a une propriété "Checked" et s'il y en a alors obtenir sa valeur.
Voir ce billet de blog: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx
Donc quelque chose comme:
la source
La réponse acceptée décrit correctement comment la liste doit être déclarée et est fortement recommandée pour la plupart des scénarios.
Mais je suis tombé sur un scénario différent, qui couvre également la question posée. Que faire si vous devez utiliser une liste d'objets existante, comme
ViewData["htmlAttributes"]
dans MVC ? Comment pouvez - vous accéder à ses propriétés (ils sont généralement créés par l' intermédiairenew { @style="width: 100px", ... }
)?Pour ce scénario légèrement différent, je veux partager avec vous ce que j'ai découvert. Dans les solutions ci-dessous, je suppose la déclaration suivante pour
nodes
:1. Solution avec dynamique
Dans C # 4.0 et les versions supérieures , vous pouvez simplement convertir en dynamique et écrire:
Remarque: cela utilise une liaison tardive, ce qui signifie qu'il ne reconnaîtra qu'au moment de l'exécution si l'objet n'a pas de
Checked
propriété et lève unRuntimeBinderException
dans ce cas - donc si vous essayez d'utiliser uneChecked2
propriété non existante , vous obtiendrez le message suivant à Durée:"'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'"
.2. Solution avec réflexion
La solution avec réflexion fonctionne à la fois avec les anciennes et les nouvelles versions du compilateur C # . Pour les anciennes versions C #, veuillez tenir compte de l'indication à la fin de cette réponse.
Contexte
Comme point de départ, j'ai trouvé une bonne réponse ici . L'idée est de convertir le type de données anonyme en dictionnaire en utilisant la réflexion. Le dictionnaire facilite l'accès aux propriétés, car leurs noms sont stockés sous forme de clés (vous pouvez y accéder comme
myDict["myProperty"]
).Inspiré par le code dans le lien ci-dessus, j'ai créé une classe d'extension fournissant
GetProp
,UnanonymizeProperties
etUnanonymizeListItems
comme méthodes d'extension, qui simplifient l'accès aux propriétés anonymes. Avec cette classe, vous pouvez simplement faire la requête comme suit:ou vous pouvez utiliser l'expression
nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()
commeif
condition, qui filtre implicitement puis vérifie si des éléments sont renvoyés.Pour obtenir le premier objet contenant la propriété "Checked" et renvoyer sa propriété "depth", vous pouvez utiliser:
ou plus court:
nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];
Remarque: si vous disposez d'une liste d'objets qui ne contiennent pas nécessairement toutes les propriétés (par exemple, certains ne contiennent pas la propriété "Vérifié") et que vous souhaitez quand même créer une requête basée sur les valeurs "Vérifié", vous pouvez faites ceci:
Cela empêche, qu'un
KeyNotFoundException
se produise si la propriété "Checked" n'existe pas.La classe ci-dessous contient les méthodes d'extension suivantes:
UnanonymizeProperties
: Est utilisé pour désanonymiser les propriétés contenues dans un objet. Cette méthode utilise la réflexion. Il convertit l'objet en un dictionnaire contenant les propriétés et ses valeurs.UnanonymizeListItems
: Permet de convertir une liste d'objets en une liste de dictionnaires contenant les propriétés. Il peut éventuellement contenir une expression lambda à filtrer au préalable.GetProp
: Est utilisé pour renvoyer une valeur unique correspondant au nom de propriété donné. Permet de traiter les propriétés non existantes comme des valeurs nulles (true) plutôt que comme KeyNotFoundException (false)Pour les exemples ci-dessus, il suffit d'ajouter la classe d'extension ci-dessous:
Astuce: Le code ci-dessus utilise les opérateurs conditionnels null , disponibles depuis la version C # 6.0 - si vous travaillez avec des compilateurs C # plus anciens (par exemple C # 3.0), remplacez simplement
?.
par.
et?[
par[
partout, par exempleSi vous n'êtes pas obligé d'utiliser un ancien compilateur C #, conservez-le tel quel, car l'utilisation de conditions nulles facilite grandement la gestion des valeurs nulles.
Remarque: comme l'autre solution avec dynamique, cette solution utilise également une liaison tardive, mais dans ce cas, vous n'obtenez pas d'exception - elle ne trouvera tout simplement pas l'élément si vous faites référence à une propriété non existante, tant car vous conservez les opérateurs conditionnels nuls .
Ce qui pourrait être utile pour certaines applications, c'est que la propriété est référencée via une chaîne dans la solution 2, elle peut donc être paramétrée.
la source
Récemment, j'ai eu le même problème dans .NET 3.5 (aucune dynamique disponible). Voici comment j'ai résolu:
Adapté de quelque part sur stackoverflow:
Maintenant, récupérez l'objet via cast:
la source