148 votes

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

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 récupérer la propriété "Checked" de l'objet anonyme. Je ne sais pas si c'est possible. J'ai essayé de le faire :

if (nodes.Any(n => n["Checked"] == false)) ... mais cela ne fonctionne pas.

Remerciements

295voto

Daniel Earwicker Points 63298

Si vous stockez l'objet en tant que type object vous devez utiliser la réflexion. Ceci est vrai pour tout type d'objet, anonyme ou non. Sur un objet o, vous pouvez obtenir son type :

Type t = o.GetType();

A partir de là, vous recherchez un bien immobilier :

PropertyInfo p = t.GetProperty("Foo");

A partir de là, vous pouvez obtenir une valeur :

object v = p.GetValue(o, null);

Cette réponse aurait dû être mise à jour depuis longtemps 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 provoquons la v être null dans trois situations différentes !

  1. o es null Il n'y a donc pas d'objet du tout
  2. o n'est pas null mais n'a pas de propriété Foo
  3. o possède une propriété Foo mais sa valeur réelle est null .

Ce n'est donc pas équivalent aux exemples précédents, mais cela peut avoir un sens si vous voulez traiter les trois cas de la même manière.

Pour utiliser dynamique pour lire les propriétés de type anonyme dans vos tests unitaires, vous devez indiquer aux services de compilation de votre projet de rendre l'assemblage visible en interne à votre projet de test. Vous pouvez ajouter ce qui suit dans votre fichier de projet (.proj). Référer ce lien pour plus d'informations.

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
        <_Parameter1>Name of your test project</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

4 votes

Je n'avais jamais utilisé de dynamique jusqu'à présent, une belle mise à jour pour .NET 4.0.

1 votes

Dans la solution c# 4, vous obtiendrez une exception d'exécution si la propriété n'existe pas ( object v = d.Foo ), tandis que les GetValue(o, null) sera nul s'il n'existe pas.

1 votes

Non, GetProperty renverra null y GetValue sera lancé si l'on lui transmet que null L'effet global est donc une exception. La version C# 4.0 donne une exception plus descriptive.

72voto

Greg Beech Points 55270

Si vous voulez une liste fortement typée de types anonymes, vous devrez également faire de la liste un type anonyme. La façon la 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();

Vous pourrez ensuite y accéder de la manière suivante :

nodes.Any(n => n.Checked);

En raison de la manière dont le compilateur fonctionne, la méthode suivante devrait également fonctionner une fois que vous avez créé la liste, car les types anonymes ont la même structure et sont donc du même type. Je n'ai pas de compilateur sous la main pour le vérifier.

nodes.Add(new { Checked = false, /* etc */ });

13voto

glennkentwell Points 317

Vous pourriez itérer sur les propriétés du type anonyme en utilisant Reflection ; voir s'il y a une propriété "Checked" et si c'est le cas, obtenir sa valeur.

Voir cet article 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!");
    }
}

7 votes

Si vous n'avez besoin que d'une seule propriété et que vous connaissez déjà son nom, il est inutile de les passer toutes en revue ; il suffit d'utiliser GetProperty et GetValue. De plus, System.out.println est Java, pas C#...

0 votes

Oups, c'est ainsi, Chris ! C'est un peu gênant... mais c'est réparé.

10voto

Matt Points 3445

La réponse acceptée décrit correctement la manière dont la liste doit être déclarée et est fortement recommandée pour la plupart des scénarios.

Mais je suis tombé sur un autre scénario, qui couvre également la question posée. Que se passe-t-il si vous devez utiliser une liste d'objets existante, comme ViewData["htmlAttributes"] en MVC ? Comment pouvez-vous accéder à ses propriétés (elles sont généralement créées par l'intermédiaire de new { @style="width: 100px", ... } ) ?

Pour ce scénario légèrement différent, je souhaite partager avec vous ce que j'ai découvert. Dans les solutions ci-dessous, je suppose que la déclaration suivante est utilisée pour nodes :

List<object> nodes = new List<object>();

nodes.Add(
new
{
    Checked = false,
    depth = 1,
    id = "div_1" 
});

Vous avez maintenant une liste d'objets. Comment pouvez-vous accéder aux propriétés des objets, par exemple, retourner une liste de tous les nœuds où la propriété Checked est fausse ?

1. Solution avec dynamique

En C# 4.0 et plus vous pouvez simplement convertir en dynamique et écrire :

if (nodes.Any(n => ((dynamic)n).Checked == false))
    Console.WriteLine("found a  not checked  element!");

Remarque : Il s'agit d'utiliser reliure tardive, ce qui signifie qu'il ne reconnaîtra qu'au moment de l'exécution si l'objet n'a pas d'attribut Checked et lance un RuntimeBinderException dans ce cas - donc si vous essayez d'utiliser un Checked2 vous obtiendrez le message suivant au moment de l'exécution : "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. Solution avec réflexion

La solution avec réflexion fonctionne avec l'ancien et le nouveau compilateur C# versions. Pour les anciennes versions de C#, veuillez consulter l'astuce à la fin de cette réponse.

Fondo

Pour commencer, j'ai trouvé une bonne réponse aquí . L'idée est de convertir le type de données anonyme en un dictionnaire en utilisant la réflexion. Le dictionnaire facilite l'accès aux propriétés, puisque leurs noms sont stockés en tant que clés (vous pouvez y accéder de la manière suivante myDict["myProperty"] ).

Inspiré par le code du lien ci-dessus, j'ai créé une classe d'extension qui fournit GetProp , UnanonymizeProperties y UnanonymizeListItems en tant que méthodes d'extension, ce qui simplifie l'accès aux propriétés anonymes. Avec cette classe, vous pouvez simplement effectuer la requête comme suit :

if (nodes.UnanonymizeListItems().Any(n => (bool)n["Checked"] == false))
{
    Console.WriteLine("found a  not checked  element!");
}

ou vous pouvez utiliser l'expression nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any() comme if qui filtre implicitement et vérifie ensuite 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 moins : 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é "Checked") et que vous souhaitez néanmoins élaborer une requête basée sur les valeurs "Checked", vous pouvez procéder de la manière suivante :

if (nodes.UnanonymizeListItems(x => { var y = ((bool?)x.GetProp("Checked", true)); 
                                      return y.HasValue && y.Value == false;}).Any())
{
    Console.WriteLine("found a  not checked   element!");
}

Cela permet d'éviter qu'un KeyNotFoundException se produit 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. Elle convertit l'objet en un dictionnaire contenant les propriétés et leurs valeurs.
  • UnanonymizeListItems : Est utilisé pour convertir une liste d'objets en une liste de dictionnaires contenant les propriétés. Il peut éventuellement contenir un expression lambda à filtrer au préalable.
  • GetProp : Est utilisé pour renvoyer une seule valeur correspondant au nom de la propriété donnée. Permet de traiter les propriétés inexistantes comme des valeurs nulles (true) plutôt que comme des 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;
        }
    }
}

Pista: Le code ci-dessus utilise la fonction null-conditionnel disponibles depuis la version 6.0 de C# - si vous travaillez avec des opérateurs les anciens compilateurs C# (par exemple C# 3.0), remplacer simplement ?. par . y ?[ par [ partout (et faire le traitement des nullités traditionnellement en utilisant if ou attraper des exceptions de type NullReferenceException), par exemple

var depth = nodes.UnanonymizeListItems()
            .FirstOrDefault(n => n.Contains("Checked"))["depth"];

Comme vous pouvez le constater, la gestion des nullités sans les opérateurs conditionnels null serait lourde ici, car partout où vous les supprimez, vous devez ajouter une vérification des nullités - ou utiliser des instructions catch où il n'est pas si facile de trouver la cause première de l'exception, ce qui se traduit par un code beaucoup plus volumineux et difficile à lire.

Si vous êtes no Si vous êtes obligé d'utiliser un ancien compilateur C#, gardez-le tel quel, car l'utilisation de conditionnels de nullité facilite grandement la gestion des nullités.

Remarque : Comme l'autre solution avec la dynamique, cette solution utilise également la liaison tardive, mais dans ce cas, vous n'obtenez pas d'exception - l'élément ne sera tout simplement pas trouvé si vous faites référence à une propriété inexistante, tant que vous conservez l'attribut null-conditionnel des opérateurs.

Ce qui pourrait être utile pour certaines applications, c'est que la propriété est référencée par une chaîne de caractères dans la solution 2, et qu'elle peut donc être paramétrée.

2voto

orfruit Points 547

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

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

L'objet est ensuite récupéré par moulage :

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

        InitializeComponent();

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

        this.Text = args.Title;

        ...
    }
    ...
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X