Wie greife ich in C # auf eine anonyme Eigenschaft zu?


125

Ich habe das:

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

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

... und ich frage mich, ob ich dann die "Checked" -Eigenschaft des anonymen Objekts abrufen kann. Ich bin mir nicht sicher, ob das überhaupt möglich ist. Versucht dies zu tun:

if (nodes.Any(n => n["Checked"] == false)) ... aber es funktioniert nicht.

Vielen Dank

Antworten:


63

Wenn Sie eine stark typisierte Liste anonymer Typen wünschen, müssen Sie die Liste auch zu einem anonymen Typ machen. Der einfachste Weg, dies zu tun, besteht darin, eine Sequenz wie ein Array in eine Liste zu projizieren, z

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

Dann können Sie wie folgt darauf zugreifen:

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

Aufgrund der Funktionsweise des Compilers sollte das Folgende auch funktionieren, sobald Sie die Liste erstellt haben, da die anonymen Typen dieselbe Struktur haben und daher auch denselben Typ haben. Ich habe jedoch keinen Compiler zur Hand, um dies zu überprüfen.

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

263

Wenn Sie das Objekt als Typ speichern object, müssen Sie Reflektion verwenden. Dies gilt für jeden anonymen oder sonstigen Objekttyp. Auf einem Objekt o können Sie seinen Typ erhalten:

Type t = o.GetType();

Dann suchen Sie eine Eigenschaft:

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

Daraus können Sie dann einen Wert erhalten:

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

Diese Antwort ist für ein Update für C # 4 längst überfällig:

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

Und jetzt noch eine Alternative in C # 6:

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

Beachten Sie, dass durch die Verwendung ?.wir bewirken , dass die resultierenden vzu sein nullin drei verschiedenen Situationen!

  1. oist null, also gibt es überhaupt kein Objekt
  2. oist nicht null, hat aber keine EigenschaftFoo
  3. ohat eine Eigenschaft, Fooaber ihr wirklicher Wert ist zufällig null.

Dies entspricht also nicht den früheren Beispielen, kann jedoch sinnvoll sein, wenn Sie alle drei Fälle gleich behandeln möchten.


4
Bis jetzt noch nie eine Dynamik verwendet, schönes Update für .NET 4.0
Alan

In der c # 4-Lösung erhalten Sie eine Laufzeitausnahme, wenn die Eigenschaft nicht vorhanden ist ( object v = d.Foo), während GetValue(o, null)sie null ist, wenn sie nicht vorhanden ist.
YaakovHatam

1
Nein, GetPropertywird zurückkehren nullund GetValuewerfen, wenn dies bestanden nullwird. Der Gesamteffekt ist also eine Ausnahme. Die C # 4.0-Version bietet eine aussagekräftigere Ausnahme.
Daniel Earwicker

4
Wenn Sie Dynamic in einer anderen Assembly als der Quelle verwenden, müssen Sie [InternalsVisibleTo]
Sarath

2
@ DanielEarwicker danke für die Fertigstellung. Dies gilt auch für anonyme Typen. Da alle für anonyme Typen generierten Eigenschaften intern sind.
Sarath

13

Sie können die Eigenschaften des anonymen Typs mithilfe von Reflection durchlaufen. Überprüfen Sie, ob eine "Checked" -Eigenschaft vorhanden ist, und ermitteln Sie deren Wert.

Siehe diesen Blog-Beitrag: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

Also so etwas wie:

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
Wenn Sie nur eine Eigenschaft benötigen und deren Namen bereits kennen, macht es keinen Sinn, alle durchzugehen. Verwenden Sie einfach GetProperty und GetValue. Außerdem ist System.out.println Java, nicht C # ...
Chris Charabaruk

Ups, so ist es, Chris! Ein bisschen peinlich ... jetzt behoben.
Glennkentwell

6

Die akzeptierte Antwort beschreibt korrekt, wie die Liste deklariert werden soll, und wird für die meisten Szenarien dringend empfohlen.

Ich bin jedoch auf ein anderes Szenario gestoßen, das auch die gestellte Frage abdeckt. Was ist, wenn Sie eine vorhandene Objektliste wie ViewData["htmlAttributes"]in MVC verwenden müssen ? Wie können Sie auf seine Eigenschaften zugreifen (sie werden normalerweise über erstellt new { @style="width: 100px", ... })?

Für dieses etwas andere Szenario möchte ich Ihnen mitteilen, was ich herausgefunden habe. In den folgenden Lösungen gehe ich von folgender Erklärung aus für nodes:

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

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

1. Lösung mit Dynamik

In C # 4.0 und höheren Versionen können Sie einfach in dynamisch umwandeln und schreiben:

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

Hinweis: Hierbei wird eine späte Bindung verwendet. Dies bedeutet, dass nur zur Laufzeit erkannt wird, wenn das Objekt keine CheckedEigenschaft hat, und RuntimeBinderExceptionin diesem Fall eine ausgelöst wird. Wenn Sie also versuchen, eine nicht vorhandene Checked2Eigenschaft zu verwenden, wird die folgende Meldung angezeigt Laufzeit : "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. Lösung mit Reflexion

Die Lösung mit Reflection funktioniert sowohl mit alten als auch mit neuen C # -Compilerversionen . Bei alten C # -Versionen beachten Sie bitte den Hinweis am Ende dieser Antwort.

Hintergrund

Als Ausgangspunkt habe ich hier eine gute Antwort gefunden . Die Idee ist, den anonymen Datentyp mithilfe von Reflection in ein Wörterbuch zu konvertieren. Das Wörterbuch erleichtert den Zugriff auf die Eigenschaften, da deren Namen als Schlüssel gespeichert sind (Sie können wie darauf zugreifen myDict["myProperty"]).

Angeregt durch den Code in dem obigen Link, habe ich eine Erweiterungsklasse bietet GetProp, UnanonymizePropertiesund UnanonymizeListItemsals Erweiterungsmethoden, die simplify Zugriff auf anonyme Eigenschaften. Mit dieser Klasse können Sie die Abfrage einfach wie folgt ausführen:

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

oder Sie können den Ausdruck nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()als ifBedingung verwenden, die implizit filtert und dann prüft, ob Elemente zurückgegeben werden.

Um das erste Objekt mit der Eigenschaft "Überprüft" abzurufen und seine Eigenschaft "Tiefe" zurückzugeben, können Sie Folgendes verwenden:

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

oder kürzer: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

Hinweis: Wenn Sie eine Liste von Objekten haben, die nicht unbedingt alle Eigenschaften enthalten müssen (zum Beispiel enthalten einige nicht die Eigenschaft "Geprüft"), und Sie dennoch eine Abfrage basierend auf "Geprüften" Werten erstellen möchten, können Sie dies tun mach das:

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

Dies verhindert, dass a KeyNotFoundExceptionauftritt, wenn die Eigenschaft "Checked" nicht vorhanden ist.


Die folgende Klasse enthält die folgenden Erweiterungsmethoden:

  • UnanonymizeProperties: Wird verwendet, um die in einem Objekt enthaltenen Eigenschaften zu de-anonymisieren . Diese Methode verwendet Reflexion. Es konvertiert das Objekt in ein Wörterbuch, das die Eigenschaften und seine Werte enthält.
  • UnanonymizeListItems: Wird verwendet, um eine Liste von Objekten in eine Liste von Wörterbüchern zu konvertieren, die die Eigenschaften enthalten. Es kann optional einen Lambda-Ausdruck enthalten, der zuvor gefiltert werden soll.
  • GetProp: Wird verwendet, um einen einzelnen Wert zurückzugeben, der dem angegebenen Eigenschaftsnamen entspricht. Ermöglicht die Behandlung nicht vorhandener Eigenschaften als Nullwerte (true) und nicht als KeyNotFoundException (false).

Für die obigen Beispiele müssen Sie lediglich die folgende Erweiterungsklasse hinzufügen:

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

Hinweis: Der obige Code verwendet die seit C # Version 6.0 verfügbaren nullbedingten Operatoren. Wenn Sie mit älteren C # -Compilern (z. B. C # 3.0) arbeiten, ersetzen Sie diese einfach ?.nach .und ?[nach [, z

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

Wenn Sie nicht gezwungen sind, einen älteren C # -Compiler zu verwenden, behalten Sie diesen unverändert bei, da die Verwendung von Nullbedingungen die Nullbehandlung erheblich vereinfacht.

Hinweis: Wie bei der anderen Lösung mit dynamischer Funktion wird auch bei dieser Lösung eine späte Bindung verwendet. In diesem Fall wird jedoch keine Ausnahme angezeigt. Das Element wird einfach nicht gefunden, wenn Sie auf eine nicht vorhandene Eigenschaft verweisen wie Sie die nullbedingten Operatoren behalten .

Für einige Anwendungen kann es nützlich sein, dass auf die Eigenschaft in Lösung 2 über eine Zeichenfolge verwiesen wird und sie daher parametrisiert werden kann.


1

Vor kurzem hatte ich das gleiche Problem in .NET 3.5 (keine Dynamik verfügbar). So habe ich gelöst:

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

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

Von irgendwo auf Stackoverflow angepasst:

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

Holen Sie sich nun das Objekt per Cast zurück:

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

        InitializeComponent();

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

        this.Text = args.Title;

        ...
    }
    ...
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.