Verwenden der Reflektion in C #, um Eigenschaften eines verschachtelten Objekts abzurufen

81

Angesichts der folgenden Objekte:

public class Customer {
    public String Name { get; set; }
    public String Address { get; set; }
}

public class Invoice {
    public String ID { get; set; }
    public DateTime Date { get; set; }
    public Customer BillTo { get; set; }
}

Ich würde gerne Reflexion verwenden, um durch das Invoicezu gehen , um das NameEigentum von a zu erhalten Customer. Ich gehe davon aus, dass dieser Code funktionieren würde:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Dies schlägt natürlich fehl, da "BillTo.Address" keine gültige Eigenschaft der InvoiceKlasse ist.

Also habe ich versucht, eine Methode zu schreiben, um die Zeichenfolge in der Periode in Teile zu teilen und die Objekte auf der Suche nach dem endgültigen Wert zu durchsuchen, an dem ich interessiert war. Es funktioniert in Ordnung, aber ich bin damit nicht ganz zufrieden:

public Object GetPropValue(String name, Object obj) {
    foreach (String part in name.Split('.')) {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        PropertyInfo info = type.GetProperty(part);
        if (info == null) { return null; }

        obj = info.GetValue(obj, null);
    }
    return obj;
}

Irgendwelche Ideen, wie man diese Methode verbessern oder wie man dieses Problem besser lösen kann?

Nach dem Posten BEARBEITEN habe ich einige verwandte Beiträge gesehen ... Es scheint jedoch keine Antwort zu geben, die sich speziell mit dieser Frage befasst. Außerdem möchte ich immer noch das Feedback zu meiner Implementierung.

jheddings
quelle
nur neugierig, wenn GetDesiredInvoiceSie ein Objekt vom Typ zurückgeben, Invoicewarum nicht inv.BillTo.Namedirekt verwenden?
Widder
Ich benutze das eigentlich ein bisschen anders, nur vereinfacht für mein Beispiel. Ich nehme ein Objekt und übergebe es an einen Prozessor, der es mit einer Vorlage zum Drucken zusammenführt.
jheddings
Es fühlte sich nur ein wenig "Brute-Force" an und schien, als gäbe es einen besseren Weg. Aus den bisherigen Antworten geht jedoch hervor, dass ich nicht völlig außer Kontrolle geraten bin.
jheddings
3
Ich dachte, ich wäre verrückt danach, aber anscheinend hatte jemand das gleiche Problem wie ich. Tolle Lösung übrigens
Marcello Grechi Lins
Schauen
jheddings

Antworten:

12

Ich denke tatsächlich, dass Ihre Logik in Ordnung ist. Persönlich würde ich es wahrscheinlich ändern, damit Sie das Objekt als ersten Parameter übergeben (was eher mit PropertyInfo.GetValue übereinstimmt, also weniger überraschend).

Ich würde es wahrscheinlich auch eher GetNestedPropertyValue nennen, um deutlich zu machen, dass es den Eigenschaftenstapel durchsucht.

Reed Copsey
quelle
Guter Aufruf zur Neuordnung der Parameter und zur vorgeschlagenen Namensänderung.
Itowlson
Vielen Dank für das Feedback ... Ich habe beide Vorschläge in meine Implementierung aufgenommen. Am Ende habe ich daraus eine Erweiterungsmethode für die ObjectKlasse gemacht, die den Punkt beim Neuordnen der Parameter verstärkt.
jheddings
Warum wird die Antwort akzeptiert? Es hilft mir nicht, die Eigenschaft eines verschachtelten Objekts zu erhalten, wie das OP gefragt hat.
Levitikon
@Levitikon Der zweite Code des OP zeigt eine anständige Möglichkeit, das zu tun, was gefragt wurde. Das war mein ganzer Punkt. Es ist nichts falsch mit dem Code, wie er in der Antwort selbst angegeben ist.
Reed Copsey
27

Ich benutze die folgende Methode, um die Werte von (verschachtelten Klassen) Eigenschaften wie zu erhalten

"Eigentum"

"Address.Street"

"Address.Country.Name"

    public static object GetPropertyValue(object src, string propName)
    {
        if (src == null) throw new ArgumentException("Value cannot be null.", "src");
        if (propName == null) throw new ArgumentException("Value cannot be null.", "propName");

        if(propName.Contains("."))//complex type nested
        {
            var temp = propName.Split(new char[] { '.' }, 2);
            return GetPropertyValue(GetPropertyValue(src, temp[0]), temp[1]);
        }
        else
        {
            var prop = src.GetType().GetProperty(propName);
            return prop != null ? prop.GetValue(src, null) : null;
        }
    }

Hier ist die Geige: https://dotnetfiddle.net/PvKRH0

DevT
quelle
Wenn die Eigenschaft null ist, funktioniert dies nicht. Sie müssen überprüfen, ob src am Anfang null ist, bevor Sie mit arbeiten.
Furtiro
@Furtiro ja sicher, kann nicht funktionieren, ob src (oder propName) null ist. Ich habe die Wurfausnahme hinzugefügt. Vielen Dank
DevT
Froh, dass ich Helfen kann ! Aber es funktioniert nicht mit einer dreifachen Verschachtelungseigenschaft, es stoppt nach 2, trauriger, aber großartiger Code!
Furtiro
@Furtiro das ist seltsam, muss arbeiten wie du an der Geige sehen kannst, die ich gerade zum Beitrag hinzugefügt habe. Schauen Sie mal, vielleicht finden Sie Ihr Problem.
DevT
@ DevT Hallo, hätten Sie den gleichen Ansatz, damit GetProperty funktioniert, wenn wir verschachtelte Klassen verwenden? dh var property = type.GetProperty (sortProperty); schlägt mit verschachtelter Klasse fehl (da wir ein Null-Ergebnis erhalten), ich denke, Ihre Lösung kann dies beantworten. (Für vollständige Details schlägt die hier angegebene Lösung stackoverflow.com/questions/11336713/… mit verschachtelter Klasse fehl)
Kynao
12

Ich weiß, dass ich ein bisschen zu spät zur Party komme, und wie andere sagten, ist Ihre Implementierung in Ordnung
... für einfache Anwendungsfälle .
Ich habe jedoch eine Bibliothek entwickelt, die genau diesen Anwendungsfall löst: Pather.CSharp .
Es ist auch als Nuget-Paket erhältlich .

Seine Hauptklasse ist Resolvermit seiner ResolveMethode.
Sie übergeben ihm ein Objekt und den Eigenschaftspfad und es wird der gewünschte Wert zurückgegeben .

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Address");

Es können jedoch auch komplexere Eigenschaftspfade aufgelöst werden , einschließlich Array- und Wörterbuchzugriff.
So zum Beispiel, wenn Sie Customerhat mehrere Adressen

public class Customer {
    public String Name { get; set; }
    public IEnumerable<String> Addresses { get; set; }
}

Sie können mit auf den zweiten zugreifen Addresses[1].

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Addresses[1]");
Domysee
quelle
2
Wie behandelt dies Nullobjekte für verschachtelte Eigenschaften, z. B. NullObject.Id, wobei NullObject beispielsweise auf der Rechnung null ist?
AVFC_Bubble88
10

Sie müssen auf das ACTUAL-Objekt zugreifen, für das Sie Reflection verwenden möchten. Folgendes meine ich:

An Stelle von:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Tun Sie dies (basierend auf dem Kommentar bearbeitet):

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo");
Customer cust = (Customer)info.GetValue(inv, null);

PropertyInfo info2 = cust.GetType().GetProperty("Address");
Object val = info2.GetValue(cust, null);

Weitere Informationen finden Sie in diesem Beitrag: Verwenden von Reflection zum Festlegen einer Eigenschaft einer Eigenschaft eines Objekts

Gabriel McAdams
quelle
Vielen Dank für die Antwort ... Ich weiß, wie man den Wert einer Eigenschaft der ersten Ebene erhält, aber ich habe mich gefragt, wie man eine verschachtelte Eigenschaft erhält. In meiner eigentlichen Anwendung habe ich keinen Zugriff auf das eigentliche Objekt.
Jheddings
1
Müssen direkt verschachtelte Eigenschaften erhalten. Ich bin auch in einer Situation, in der "Rechnung" T ist und ich eine Zeichenfolge des Pfads "Property.Property.Property" habe. Kann nicht mit jeder Eigenschaft herumspielen.
Levitikon
Das hat es für mich getan. Ich hatte auch kein Glück mit BillTo.Address. Ich bin gespannt, ob das Einstellen der Immobilie genauso funktioniert.
Yusif_Nurizade
7

In der Hoffnung, nicht zu spät für die Party zu klingen, möchte ich meine Lösung hinzufügen: Verwenden Sie in dieser Situation definitiv die Rekursion

public static Object GetPropValue(String name, object obj, Type type)
    {
        var parts = name.Split('.').ToList();
        var currentPart = parts[0];
        PropertyInfo info = type.GetProperty(currentPart);
        if (info == null) { return null; }
        if (name.IndexOf(".") > -1)
        {
            parts.Remove(currentPart);
            return GetPropValue(String.Join(".", parts), info.GetValue(obj, null), info.PropertyType);
        } else
        {
            return info.GetValue(obj, null).ToString();
        }
    }
roger l
quelle
6

Sie erklären nicht die Ursache Ihres "Unbehagens", aber Ihr Code sieht für mich im Grunde genommen gut aus.

Das einzige, was ich in Frage stellen würde, ist die Fehlerbehandlung. Sie geben null zurück, wenn der Code versucht, eine Nullreferenz zu durchlaufen, oder wenn der Eigenschaftsname nicht vorhanden ist. Dies verbirgt Fehler: Es ist schwer zu wissen, ob es null zurückgegeben hat, weil es keinen BillTo-Kunden gibt oder weil Sie es "BilTo.Address" falsch geschrieben haben ... oder weil es einen BillTo-Kunden gibt und seine Adresse null ist! Ich würde die Methode in diesen Fällen abstürzen und brennen lassen - lassen Sie einfach die Ausnahme entkommen (oder wickeln Sie sie in eine freundlichere ein).

itowlson
quelle
3

Hier ist eine weitere Implementierung, die eine verschachtelte Eigenschaft überspringt, wenn es sich um einen Enumerator handelt, und tiefer geht. Die Eigenschaften der Typzeichenfolge werden von der Aufzählungsprüfung nicht beeinflusst.

public static class ReflectionMethods
{
    public static bool IsNonStringEnumerable(this PropertyInfo pi)
    {
        return pi != null && pi.PropertyType.IsNonStringEnumerable();
    }

    public static bool IsNonStringEnumerable(this object instance)
    {
        return instance != null && instance.GetType().IsNonStringEnumerable();
    }

    public static bool IsNonStringEnumerable(this Type type)
    {
        if (type == null || type == typeof(string))
            return false;
        return typeof(IEnumerable).IsAssignableFrom(type);
    }

    public static Object GetPropValue(String name, Object obj)
    {
        foreach (String part in name.Split('.'))
        {
            if (obj == null) { return null; }
            if (obj.IsNonStringEnumerable())
            {
                var toEnumerable = (IEnumerable)obj;
                var iterator = toEnumerable.GetEnumerator();
                if (!iterator.MoveNext())
                {
                    return null;
                }
                obj = iterator.Current;
            }
            Type type = obj.GetType();
            PropertyInfo info = type.GetProperty(part);
            if (info == null) { return null; }

            obj = info.GetValue(obj, null);
        }
        return obj;
    }
}

basierend auf dieser Frage und auf

Wie Sie feststellen können , ob es sich bei einer PropertyInfo um eine Sammlung von Berryl handelt

Ich verwende dies in einem MVC-Projekt, um meine Daten dynamisch zu ordnen, indem ich einfach die Eigenschaft zum Sortieren nach Beispiel übergebe:

result = result.OrderBy((s) =>
                {
                    return ReflectionMethods.GetPropValue("BookingItems.EventId", s);
                }).ToList();

Dabei ist BookingItems eine Liste von Objekten.

erevosgr
quelle
2
> Get Nest properties e.g., Developer.Project.Name
private static System.Reflection.PropertyInfo GetProperty(object t, string PropertName)
            {
                if (t.GetType().GetProperties().Count(p => p.Name == PropertName.Split('.')[0]) == 0)
                    throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertName, t.ToString()));
                if (PropertName.Split('.').Length == 1)
                    return t.GetType().GetProperty(PropertName);
                else
                    return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Split('.')[1]);
            }
Mohamed.Abdo
quelle
1
   if (info == null) { /* throw exception instead*/ } 

Ich würde tatsächlich eine Ausnahme auslösen, wenn sie eine Eigenschaft anfordern, die nicht existiert. So wie Sie es codiert haben, wenn ich GetPropValue aufrufe und es null zurückgibt, weiß ich nicht, ob dies bedeutet, dass die Eigenschaft nicht existiert hat oder die Eigenschaft existiert hat, aber ihr Wert war null.

AaronLS
quelle
Verschieben Sie außerdem die Prüfung, ob obj null ist, außerhalb der Schleife.
Kevin Brock
Entschuldigung, habe die wiederholte Verwendung von obj nicht gesehen. Es ist keine gute Programmierpraxis, Ihre Parameter zu ändern. Dies kann in Zukunft zu Verwirrung führen. Verwenden Sie eine andere Variable für den Parameter obj, um innerhalb der Schleife zu durchlaufen.
Kevin Brock
Kevin: Um eine andere Variable zu verwenden, müsste er sie entweder am Ende obj zuweisen oder die Methode rekursiv machen. Persönlich denke ich nicht, dass dies ein Problem ist (obwohl ein guter Kommentar nett wäre ...)
Reed Copsey
1
@ Levitikon Das OP erklärte: "Irgendwelche Ideen, wie man diese Methode verbessern oder wie man dieses Problem besser lösen kann?". Dies ist also eine Antwort und kein Kommentar, da das OP um Verbesserungen gebeten hat, was eine Verbesserung darstellt.
AaronLS
1
    public static string GetObjectPropertyValue(object obj, string propertyName)
    {
        bool propertyHasDot = propertyName.IndexOf(".") > -1;
        string firstPartBeforeDot;
        string nextParts = "";

        if (!propertyHasDot)
            firstPartBeforeDot = propertyName.ToLower();
        else
        {
            firstPartBeforeDot = propertyName.Substring(0, propertyName.IndexOf(".")).ToLower();
            nextParts = propertyName.Substring(propertyName.IndexOf(".") + 1);
        }

        foreach (var property in obj.GetType().GetProperties())
            if (property.Name.ToLower() == firstPartBeforeDot)
                if (!propertyHasDot)
                    if (property.GetValue(obj, null) != null)
                        return property.GetValue(obj, null).ToString();
                    else
                        return DefaultValue(property.GetValue(obj, null), propertyName).ToString();
                else
                    return GetObjectPropertyValue(property.GetValue(obj, null), nextParts);
        throw new Exception("Property '" + propertyName.ToString() + "' not found in object '" + obj.ToString() + "'");
    }
BarbaBabak
quelle
1
Beschreiben Sie, worauf Ihre Lösung basiert, wie es dem OP helfen wird, eine gute Übung zu sein.
DontVoteMeDown
0

Meine Internetverbindung war unterbrochen, als ich das gleiche Problem lösen musste, also musste ich das Rad neu erfinden:

static object GetPropertyValue(Object fromObject, string propertyName)
{
    Type objectType = fromObject.GetType();
    PropertyInfo propInfo = objectType.GetProperty(propertyName);
    if (propInfo == null && propertyName.Contains('.'))
    {
        string firstProp = propertyName.Substring(0, propertyName.IndexOf('.'));
        propInfo = objectType.GetProperty(firstProp);
        if (propInfo == null)//property name is invalid
        {
            throw new ArgumentException(String.Format("Property {0} is not a valid property of {1}.", firstProp, fromObject.GetType().ToString()));
        }
        return GetPropertyValue(propInfo.GetValue(fromObject, null), propertyName.Substring(propertyName.IndexOf('.') + 1));
    }
    else
    {
        return propInfo.GetValue(fromObject, null);
    }
}

Ziemlich sicher, dass dies das Problem für jede Zeichenfolge löst, die Sie für den Eigenschaftsnamen verwenden, unabhängig vom Ausmaß der Verschachtelung, solange alles eine Eigenschaft ist.

MalibuCusser
quelle
0

Ich wollte meine Lösung teilen, obwohl es möglicherweise zu spät ist. Diese Lösung besteht hauptsächlich darin, zu überprüfen, ob die verschachtelte Eigenschaft vorhanden ist. Es kann jedoch leicht angepasst werden, um bei Bedarf den Eigenschaftswert zurückzugeben.

private static PropertyInfo _GetPropertyInfo(Type type, string propertyName)
        {
            //***
            //*** Check if the property name is a complex nested type
            //***
            if (propertyName.Contains("."))
            {
                //***
                //*** Get the first property name of the complex type
                //***
                var tempPropertyName = propertyName.Split(".", 2);
                //***
                //*** Check if the property exists in the type
                //***
                var prop = _GetPropertyInfo(type, tempPropertyName[0]);
                if (prop != null)
                {
                    //***
                    //*** Drill down to check if the nested property exists in the complex type
                    //***
                    return _GetPropertyInfo(prop.PropertyType, tempPropertyName[1]);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
            }
        }

Ich musste mich auf einige Beiträge beziehen, um diese Lösung zu finden. Ich denke, dass dies für mehrere verschachtelte Eigenschaftstypen funktionieren wird.

HarshitGindra
quelle
-7

Versuchen inv.GetType().GetProperty("BillTo+Address");

RAM
quelle