Comment accéder à la propriété de type anonyme en C #?

125

J'ai ceci:

List<object> nodes = new List<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.

Merci

wgpubs
la source

Réponses:

63

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.

nodes.Add(new { Checked = false, /* etc */ });
Greg Beech
la source
263

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!

  1. oest null, donc il n'y a aucun objet du tout
  2. on'est nullpas - mais n'a pas de propriétéFoo
  3. 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.

Daniel Earwicker
la source
4
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.

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:

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!");
    }
}
Glennkentwell
la source
6
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 = new List<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:

public static class AnonymousTypeExtensions
{
    // makes properties of object accessible 
    public static IDictionary UnanonymizeProperties(this object 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 filterCriteria
    public static List<IDictionary> UnanonymizeListItems(this List<object> objectList, 
                    Func<IDictionary<string, object>, bool> filterCriteria=default)
    {
        var accessibleList = new List<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>
    public static object GetProp(this object obj, string propertyName, 
                                 bool treatNotFoundAsNull = false)
    {
        try 
        {
            return ((System.Collections.Generic.IDictionary<string, object>)obj)
                   ?[propertyName];
        }
        catch (KeyNotFoundException)
        {
            if (treatNotFoundAsNull) return default(object); else throw;
        }
    }
}

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.

Mat
la source
1

Récemment, j'ai eu le même problème dans .NET 3.5 (aucune dynamique disponible). Voici comment j'ai résolu:

// pass anonymous object as argument
var args = new { Title = "Find", Type = typeof(FindCondition) };

using (frmFind f = new frmFind(args)) 
{
...
...
}

Adapté de quelque part sur stackoverflow:

// Use a custom cast extension
public static T CastTo<T>(this Object x, T targetType)
{
   return (T)x;
}

Maintenant, récupérez l'objet via cast:

public partial class frmFind: Form
{
    public frmFind(object arguments)
    {

        InitializeComponent();

        var args = arguments.CastTo(new { Title = "", Type = typeof(Nullable) });

        this.Text = args.Title;

        ...
    }
    ...
}
orfruit
la source