Domanda

Ho questo:

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

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

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

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

Grazie

È stato utile?

Soluzione

Se si desidera un elenco fortemente tipizzato di tipi anonimi, è necessario rendere anche l'elenco un tipo anonimo. Il modo più semplice per farlo è proiettare una sequenza come una matrice in un elenco, ad esempio

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

Quindi potrai accedervi come:

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

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

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

Altri suggerimenti

Se stai memorizzando l'oggetto come tipo object, devi usare la riflessione. Questo vale per qualsiasi tipo di oggetto, anonimo o altro. 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 questo 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);

Nota che usando ?. causiamo che v risultante sia null in tre diverse situazioni!

  1. o è Foo, quindi non c'è alcun oggetto
  2. <=> non è - <=> ma non ha una proprietà <=>
  3. <=> ha una proprietà <=> ma il suo valore reale è <=>.

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

È possibile scorrere le proprietà del tipo anonimo utilizzando Reflection; vedere se c'è un " Checked " proprietà e, se presente, ottiene il suo valore.

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

Quindi qualcosa del tipo:

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

Di recente, ho riscontrato lo stesso problema in .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;

        ...
    }
    ...
}

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

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

Per questo scenario leggermente diverso voglio condividere con te ciò 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 dinamica

Nelle versioni C # 4.0 e successive , puoi semplicemente trasmettere in dinamico e scrivere:

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

Nota: sta usando late binding, che significa che riconoscerà solo in fase di esecuzione se l'oggetto non ha una proprietà Checked e genera un <= > in questo caso, quindi se si tenta di utilizzare una proprietà RuntimeBinderException inesistente si otterrà il seguente messaggio in fase di esecuzione: Checked2.

2. Soluzione con riflessione

La soluzione con reflection funziona sia con compilatore C # vecchio che nuovo . Per le vecchie versioni di C #, ti preghiamo di considerare il suggerimento alla fine di questa risposta.

Sfondo

Come punto di partenza, ho trovato una buona risposta qui . L'idea è di convertire il tipo di dati anonimo in un dizionario utilizzando la riflessione. Il dizionario semplifica l'accesso alle proprietà, poiché i loro nomi sono memorizzati come chiavi (puoi accedervi come "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'").

Ispirato al codice nel link sopra, ho creato una classe di estensione che fornisce myDict["myProperty"], GetProp e UnanonymizeProperties come metodi di estensione, che semplifica l'accesso a proprietà anonime. Con questa classe puoi semplicemente fare la query come segue:

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

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

Per ottenere il primo oggetto contenente " Checked " e restituisce la sua proprietà " depth " puoi usare:

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

o più breve: if

Nota: se si dispone di un elenco di oggetti che non contengono necessariamente tutte le proprietà (ad esempio, alcuni non contengono la " Checked " proprietà), e vuoi comunque creare una query basata su " Checked " valori, puoi farlo:

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

Questo impedisce che si verifichi un nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"]; se il " Checked " la proprietà non esiste.


La classe seguente contiene i seguenti metodi di estensione:

  • KeyNotFoundException: viene utilizzato per disanonimizzare le proprietà contenute in un oggetto. Questo metodo utilizza la riflessione. Converte l'oggetto in un dizionario contenente le proprietà e i suoi valori.
  • ?.: viene utilizzato per convertire un elenco di oggetti in un elenco di dizionari contenente le proprietà. Può facoltativamente contenere in anticipo espressione lambda da filtrare .
  • .: viene utilizzato per restituire un singolo valore corrispondente al nome della proprietà specificato. Consente di trattare le proprietà inesistenti come valori null (true) anziché come KeyNotFoundException (false)

Per gli esempi sopra, tutto ciò che serve è aggiungere la seguente classe di estensione:

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 sopra sta usando null-conditional , disponibile dalla versione 6.0 di C # 6.0 - se lavori con compilatori C # precedenti (ad es. C # 3.0), sostituisci semplicemente <= > di ?[ e [ di <=> ovunque, ad es.

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

Se sei non costretto a utilizzare un compilatore C # precedente, mantienilo così com'è, perché l'uso di null-conditionals rende la gestione null molto più semplice.

Nota: come l'altra soluzione con dynamic, questa soluzione utilizza anche l'associazione tardiva, ma in questo caso non si ottiene un'eccezione: semplicemente non troverà l'elemento se si fa riferimento a una proprietà inesistente, purché si mantenga null-condizionale .

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top