Używanie odbicia w języku C # do pobierania właściwości zagnieżdżonego obiektu


82

Biorąc pod uwagę następujące obiekty:

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

Chciałbym użyć refleksji, aby przejść przez, Invoiceaby uzyskać Namewłaściwość a Customer. Oto, czego szukam, zakładając, że ten kod zadziała:

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

Oczywiście kończy się to niepowodzeniem, ponieważ „BillTo.Address” nie jest prawidłową właściwością Invoiceklasy.

Spróbowałem więc napisać metodę dzielenia ciągu na części na kropce i przechodzenia po obiektach w poszukiwaniu interesującej mnie wartości końcowej. Działa dobrze, ale nie do końca mi to odpowiada:

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

Jakieś pomysły na ulepszenie tej metody lub lepszy sposób rozwiązania tego problemu?

EDYTUJ po wysłaniu, widziałem kilka powiązanych postów ... Jednak nie wydaje się, aby istniała odpowiedź, która konkretnie odnosi się do tego pytania. Nadal chciałbym poznać opinie na temat mojej implementacji.


po prostu ciekawy, jeśli GetDesiredInvoicezwraca ci obiekt typu, Invoicedlaczego nie użyć inv.BillTo.Namebezpośrednio?
ram

Właściwie używam tego trochę inaczej, tylko uproszczony dla mojego przykładu. Biorę obiekt i przekazuję go do procesora, który łączy go z szablonem do drukowania.
jheddings

Poczułem tylko odrobinę „brutalnej siły” i wydawało się, że powinien być lepszy sposób. Jednak z dotychczasowych odpowiedzi wydaje się, że nie byłem całkowicie poza bazą.
jheddings

3
Myślałem, że zwariowałem, robiąc to, ale wygląda na to, że ktoś miał ten sam problem co ja.
Swoją

Sprawdź także moją odpowiedź na inny temat dotyczący używania tych metod jako rozszerzenia: stackoverflow.com/questions/1196991/ ...
jheddings

Odpowiedzi:


12

Właściwie myślę, że twoja logika jest w porządku. Osobiście prawdopodobnie zmieniłbym to tak, aby przekazać obiekt jako pierwszy parametr (który jest bardziej zgodny z PropertyInfo.GetValue, więc mniej zaskakujący).

Prawdopodobnie nazwałbym to również czymś bardziej podobnym do GetNestedPropertyValue, aby było oczywiste, że przeszukuje stos właściwości.


Dobre wezwanie do zmiany kolejności parametrów i sugerowanej zmiany nazwy.
itowlson

Dzięki za informację zwrotną ... Obie sugestie przyjąłem do swojej implementacji. Skończyło się na tym, że zamieniłem to w metodę rozszerzającą w Objectklasie, co wzmacnia punkt dotyczący zmiany kolejności parametrów.
jheddings

Dlaczego jest akceptowana odpowiedź? Nie pomaga mi to w uzyskaniu właściwości zagnieżdżonego obiektu, o co prosił OP.
Levitikon

@Levitikon Drugi zestaw kodu OP pokazuje przyzwoity sposób wykonania tego, o co proszono. To był cały mój punkt widzenia. Nie ma nic złego w kodzie podanym w samej odpowiedzi.
Reed Copsey

27

Używam następującej metody, aby uzyskać wartości z (zagnieżdżonych klas) właściwości, takich jak

"Własność"

"Adres ulica"

„Adres.Kraj.Nazwa”

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

Oto Fiddle: https://dotnetfiddle.net/PvKRH0


Jeśli właściwość ma wartość null, to nie zadziała, przed rozpoczęciem pracy należy sprawdzić, czy src ma wartość null.
Furtiro

@Furtiro tak, jasne, nie działa, czy src (lub propName) ma wartość null. Dodałem wyjątek rzutowania. Dzięki
DevT

Miło, że mogłem pomóc ! Ale to nie działa dla mnie z potrójną właściwością zagnieżdżania, zatrzymuje się po 2, smutny, ale świetny kod!
Furtiro,

@Furtiro to dziwne, muszę działać, jak widać na skrzypcach, które właśnie dodałem do postu. Spójrz, może uda Ci się znaleźć swój problem.
DevT

@DevT Cześć, czy miałbyś takie samo podejście, aby GetProperty działało, gdy używamy klasy zagnieżdżonej? tj. var property = type.GetProperty (sortProperty); nie powiedzie się z klasą zagnieżdżoną (ponieważ otrzymujemy wynik zerowy), wydaje mi się, że Twoje rozwiązanie może na to odpowiedzieć. (Aby uzyskać szczegółowe informacje, rozwiązanie podane tutaj stackoverflow.com/questions/11336713/… nie działa z klasą zagnieżdżoną)
Kynao

13

Wiem, że trochę się spóźniłem na imprezę i jak powiedzieli inni, twoja implementacja jest w porządku
... w prostych przypadkach użycia .
Jednak opracowałem bibliotekę, która rozwiązuje dokładnie ten przypadek użycia, Pather.CSharp .
Jest również dostępny jako pakiet Nuget .

Jej głównym klasa jest Resolverze swojej Resolvemetody.
Przekazać mu przedmiot i na ścieżkę nieruchomości i zwróci pożądaną wartość .

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

Ale może również rozwiązywać bardziej złożone ścieżki właściwości , w tym dostęp do tablicy i słownika.
Tak więc, na przykład, jeśli Customermiał wiele adresów

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

możesz uzyskać dostęp do drugiego za pomocą Addresses[1].

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

2
Jak to obsługuje obiekty o wartości null dla zagnieżdżonych właściwości, np. NullObject.Id z wartością NullObject na fakturze?
AVFC_Bubble88

10

Musisz uzyskać dostęp do obiektu ACTUAL, nad którym musisz użyć refleksji. Oto o co mi chodzi:

Zamiast tego:

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

Zrób to (edytowane na podstawie komentarza):

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

Zobacz ten post, aby uzyskać więcej informacji: Używanie odbicia do ustawiania właściwości właściwości obiektu


Dzięki za odpowiedź ... Wiem, jak uzyskać wartość właściwości pierwszego poziomu, ale zastanawiałem się, jak uzyskać zagnieżdżoną. W mojej aplikacji nie mam dostępu do rzeczywistego obiektu.
jheddings

1
Trzeba bezpośrednio pobrać zagnieżdżone właściwości. Jestem również w sytuacji, w której „Faktura” to T i mam ciąg ścieżki „Property.Property.Property”. Nie można majstrować przy każdej nieruchomości.
Levitikon

To zrobiło to dla mnie. Nie miałem też szczęścia z BillTo.Address. Jestem ciekaw, czy ustawienie właściwości działa tak samo?
Yusif_Nurizade,

7

W nadziei, że nie zabrzmią za późno na imprezę, chciałbym dodać moje rozwiązanie: Zdecydowanie użyj rekurencji w tej sytuacji

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

6

Nie wyjaśniasz źródła swojego „dyskomfortu”, ale zasadniczo wydaje mi się, że Twój kod jest zdrowy.

Jedyne, co bym kwestionował, to obsługa błędów. Zwracasz wartość null, jeśli kod próbuje przejść przez odwołanie o wartości null lub jeśli nazwa właściwości nie istnieje. To ukrywa błędy: trudno jest wiedzieć, czy zwróciło wartość null, ponieważ nie ma klienta BillTo, czy też dlatego, że błędnie wpisałeś go „BilTo.Address” ... lub ponieważ istnieje klient BillTo, a jego adres jest pusty! Pozwoliłbym metodzie na awarię i spalenie w takich przypadkach - po prostu pozwolę wyjątkowi uciec (lub może owinąć go w bardziej przyjazny).


3

Oto kolejna implementacja, która pominie zagnieżdżoną właściwość, jeśli jest to moduł wyliczający i będzie kontynuować głębszą pracę. Sprawdzanie wyliczenia nie wpływa na właściwości typu string.

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

na podstawie tego pytania i dalej

Jak sprawdzić, czy PropertyInfo jest zbiorem firmy Berryl

Używam tego w projekcie MVC, aby dynamicznie porządkować moje dane, po prostu przekazując Właściwość do sortowania według przykładu:

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

gdzie BookingItems to lista obiektów.


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

1
   if (info == null) { /* throw exception instead*/ } 

W rzeczywistości zgłosiłbym wyjątek, gdyby zażądali właściwości, która nie istnieje. Sposób, w jaki został zakodowany, jeśli wywołam GetPropValue i zwróci wartość null, nie wiem, czy to oznacza, że ​​właściwość nie istniała, czy właściwość istniała, ale ma wartość null.


Ponadto przenieś sprawdzanie, czy obj jest null poza pętlą.
Kevin Brock

Przepraszamy, nie widziałem wielokrotnego użycia obj. Zmiana parametrów nie jest dobrą praktyką programistyczną, może to prowadzić do nieporozumień w przyszłości. Użyj innej zmiennej dla parametru obj, aby przejść w pętli.
Kevin Brock

Kevin: Aby użyć innej zmiennej, musiałby albo przypisać ją do obj na końcu, albo uczynić metodę rekurencyjną. Osobiście nie sądzę, żeby to był problem (chociaż dobry komentarz byłby miły ...)
Reed Copsey

1
@Levitikon OP stwierdził: „Jakieś pomysły na ulepszenie tej metody lub lepszy sposób rozwiązania tego problemu?”. Jest to więc odpowiedź, a nie komentarz, ponieważ PO poprosił o ulepszenia, co jest poprawą.
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() + "'");
    }

1
Opisz na czym opiera się twoje sulution, w jaki sposób pomoże to PO, to dobra praktyka.
DontVoteMeDown,

1

Chciałem podzielić się moim rozwiązaniem, chociaż może być za późno. To rozwiązanie służy przede wszystkim do sprawdzenia, czy zagnieżdżona właściwość istnieje. Ale można go łatwo dostosować, aby w razie potrzeby zwrócić wartość właściwości.

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

Musiałem odnieść się do kilku postów, aby wymyślić to rozwiązanie. Myślę, że to zadziała dla wielu zagnieżdżonych typów właściwości.


0

Moje połączenie internetowe było wyłączone, gdy musiałem rozwiązać ten sam problem, więc musiałem `` wymyślić koło na nowo '':

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

Całkiem pewny, że to rozwiązuje problem dla dowolnego łańcucha używanego jako nazwa właściwości, niezależnie od stopnia zagnieżdżenia, o ile wszystko jest własnością.


-7

Próbować inv.GetType().GetProperty("BillTo+Address");

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.