Come accedere a proprietà di tipo anonimo in C #?


125

Ho questo:

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

nodes.Add(
new {
    Checked     = false,
    depth       = 1,
    id          = "div_" + d.Id
});

... e mi chiedo se riesco a prendere la proprietà "Checked" dell'oggetto anonimo. Non sono sicuro che sia possibile. Ho provato a farlo:

if (nodes.Any(n => n["Checked"] == false)) ... ma non funziona.

Grazie

Risposte:


63

Se desideri un elenco fortemente tipizzato di tipi anonimi, dovrai rendere l'elenco anche un tipo anonimo. Il modo più semplice per farlo è proiettare una sequenza come un array in un elenco, ad es

var nodes = (new[] { new { Checked = false, /* etc */ } }).ToList();

Quindi sarai in grado di accedervi come:

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

A causa del modo in cui funziona il compilatore, anche quanto segue dovrebbe funzionare una volta creato l'elenco, poiché i tipi anonimi hanno la stessa struttura quindi sono anche dello stesso tipo. Tuttavia, non ho un compilatore a portata di mano per verificarlo.

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

263

Se stai memorizzando l'oggetto come tipo object, devi usare la riflessione. Questo è vero per qualsiasi tipo di oggetto, anonimo o meno. Su un oggetto o, puoi ottenere il suo tipo:

Type t = o.GetType();

Quindi da quello cerchi una proprietà:

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

Quindi da ciò puoi ottenere un valore:

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

Questa risposta è attesa da tempo per un aggiornamento per C # 4:

dynamic d = o;
object v = d.Foo;

E ora un'altra alternativa in C # 6:

object v = o?.GetType().GetProperty("Foo")?.GetValue(o, null);

Si noti che utilizzando si fa in modo ?.che il risultato vsia nullin tre diverse situazioni!

  1. oè null, quindi non c'è alcun oggetto
  2. onon è nullma non ha una proprietàFoo
  3. oha una proprietà Fooma il suo valore reale è null.

Quindi questo non è equivalente agli esempi precedenti, ma può avere senso se si desidera trattare tutti e tre i casi allo stesso modo.


4
Mai usato una dinamica prima fino ad ora, bell'aggiornamento per .NET 4.0
Alan

nella soluzione c # 4 si otterrà un'eccezione di runtime se la proprietà non esiste ( object v = d.Foo), mentre GetValue(o, null)sarà nulla se non esiste.
YaakovHatam

1
No, GetPropertytornerà nulle GetValuelancerà se viene superato null, quindi l'effetto complessivo è un'eccezione. La versione C # 4.0 offre un'eccezione più descrittiva.
Daniel Earwicker

4
Se stai usando la dinamica in un assembly diverso da quello sorgente, devi usare [InternalsVisibleTo]
Sarath

2
@DanielEarwicker grazie per il completamento. Si applica anche ai tipi anonimi. Poiché tutte le proprietà generate per i tipi anonimi sono interne.
Sarath

13

È possibile iterare sulle proprietà del tipo anonimo utilizzando Reflection; controlla se c'è una proprietà "Controllata" e se c'è, ottieni il suo valore.

Vedi questo post del blog: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

Quindi qualcosa come:

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

6
Se hai bisogno di una sola proprietà e conosci già il suo nome, non ha senso esaminarle tutte; basta usare GetProperty e GetValue. Inoltre, System.out.println è Java, non C # ...
Chris Charabaruk

Ops, è così, Chris! Un po 'imbarazzante ... risolto ora.
glennkentwell

6

La risposta accettata descrive correttamente come dovrebbe essere dichiarato l'elenco ed è altamente raccomandata per la maggior parte degli scenari.

Ma mi sono imbattuto in uno scenario diverso, che copre anche la domanda posta. Cosa succede se devi usare un elenco di oggetti esistente, come ViewData["htmlAttributes"]in MVC ? Come puoi accedere alle sue proprietà (di solito vengono create tramite new { @style="width: 100px", ... })?

Per questo scenario leggermente diverso voglio condividere con voi quello che ho scoperto. Nelle soluzioni seguenti, presumo la seguente dichiarazione per nodes:

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

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

1. Soluzione con dinamico

In C # 4.0 e versioni successive , puoi semplicemente trasmettere a dinamico e scrivere:

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

Nota: questo utilizza l' associazione tardiva, il che significa che riconoscerà solo in fase di esecuzione se l'oggetto non ha una Checkedproprietà e RuntimeBinderExceptionin questo caso genera un - quindi se provi a utilizzare una Checked2proprietà non esistente riceverai il seguente messaggio a runtime: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. Soluzione con riflessione

La soluzione con la reflection funziona sia con la vecchia che con la nuova versione del compilatore C # . Per le vecchie versioni C # si prega di considerare il suggerimento alla fine di questa risposta.

sfondo

Come punto di partenza, ho trovato una buona risposta qui . L'idea è convertire il tipo di dati anonimo in un dizionario utilizzando la riflessione. Il dizionario lo rende facile accedere alle proprietà, dal momento che i loro nomi vengono memorizzati come chiavi (è possibile accedere come loro myDict["myProperty"]).

Ispirato dal codice nel link qui sopra, ho creato una classe di un'estensione che fornisce GetProp, UnanonymizePropertiese UnanonymizeListItemscome metodi di estensione, che l'accesso alle proprietà Semplificare anonimi. Con questa classe puoi semplicemente eseguire la query come segue:

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

oppure puoi usare l'espressione nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()come ifcondizione, che filtra in modo implicito e quindi controlla se ci sono elementi restituiti.

Per ottenere il primo oggetto contenente la proprietà "Checked" e restituire la sua proprietà "depth", puoi utilizzare:

var depth = nodes.UnanonymizeListItems()
             ?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");

o più breve: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

Nota: se hai un elenco di oggetti che non contengono necessariamente tutte le proprietà (ad esempio, alcuni non contengono la proprietà "Controllato") e desideri comunque creare una query basata sui valori "Selezionato", puoi Fai questo:

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

Ciò impedisce che si KeyNotFoundExceptionverifichi se la proprietà "Checked" non esiste.


La classe seguente contiene i seguenti metodi di estensione:

  • UnanonymizeProperties: Viene utilizzato per de-anonimizzare le proprietà contenute in un oggetto. Questo metodo utilizza la riflessione. Converte l'oggetto in un dizionario contenente le proprietà e i suoi valori.
  • UnanonymizeListItems: Viene utilizzato per convertire un elenco di oggetti in un elenco di dizionari contenenti le proprietà. Può facoltativamente contenere un'espressione lambda da filtrare in anticipo.
  • GetProp: Viene utilizzato per restituire un singolo valore corrispondente al nome della proprietà specificato. Consente di trattare le proprietà non esistenti come valori nulli (true) anziché come KeyNotFoundException (false)

Per gli esempi precedenti, tutto ciò che è richiesto è aggiungere la classe di estensione di seguito:

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

Suggerimento: Il codice di cui sopra è utilizzando il Null-condizionale operatori, disponibili dal C # versione 6.0 - Se si lavora con più vecchio C # compilatori (ad esempio, C # 3.0), è sufficiente sostituire ?.da .e ?[per [tutto il mondo, ad esempio,

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

Se stai non è costretti ad usare un vecchio compilatore C #, tenerlo come è, perché l'utilizzo nulli-condizionali rende nulla la gestione molto più facile.

Nota: come l'altra soluzione con dinamica, anche questa utilizza l'associazione tardiva, ma in questo caso non si ottiene un'eccezione: semplicemente non troverà l'elemento se si fa riferimento a una proprietà non esistente, purché man mano che mantieni gli operatori condizionali nulli .

Ciò che potrebbe essere utile per alcune applicazioni è che la proprietà è indicata tramite una stringa nella soluzione 2, quindi può essere parametrizzata.


1

Recentemente, ho avuto lo stesso problema all'interno di .NET 3.5 (nessuna dinamica disponibile). Ecco come ho risolto:

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

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

Adattato da qualche parte su stackoverflow:

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

Ora recupera l'oggetto tramite cast:

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

        InitializeComponent();

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

        this.Text = args.Title;

        ...
    }
    ...
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.