Використання відображення в C # для отримання властивостей вкладеного об’єкта


82

Враховуючи такі об’єкти:

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

Я хотів би використовувати відображення, щоб пройти через, Invoiceщоб отримати Nameвластивість Customer. Ось що я шукаю, припускаючи, що цей код буде працювати:

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

Звичайно, це не вдається, оскільки "BillTo.Address" не є допустимою властивістю Invoiceкласу.

Отже, я спробував написати метод, щоб розділити рядок на частини за періодом і пройтись по об’єктах, шукаючи кінцеве значення, яке мене зацікавило. Це працює нормально, але мені це не зовсім зручно:

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

Будь-які ідеї щодо вдосконалення цього методу чи кращого способу вирішення цієї проблеми?

РЕДАГУВАТИ після публікації, я побачив кілька пов’язаних повідомлень ... Однак, схоже, немає відповіді, яка конкретно стосується цього питання. Крім того, я все ще хотів би отримати відгук про моє впровадження.


просто цікаво, якщо ваш GetDesiredInvoiceповертає вам об’єкт типу, Invoiceчому б не використовувати inv.BillTo.Nameбезпосередньо?
таран

Я насправді використовую це дещо по-іншому, просто спрощено для мого прикладу. Я беру об’єкт і передаю його в процесор, який об’єднує його із шаблоном для друку.
jheddings

Це просто відчувало трохи «грубої сили» і здавалося, що повинен бути кращий спосіб. Однак з досі отриманих відповідей здається, що я не був абсолютно нестандартним.
jheddings

3
Я думав, що я божевільний за це, але, схоже, хтось мав таку ж проблему, як і я. Чудове рішення, до речі
Марчелло Гречі Лінс

Крім того, перевірити мою відповідь на іншу тему для використання їх в якості методів розширення: stackoverflow.com/questions/1196991 / ...
jheddings

Відповіді:


12

Я насправді вважаю, що ваша логіка чудова. Особисто я б, напевно, змінив це, щоб ви передали об'єкт як перший параметр (що є більш вбудованим у PropertyInfo.GetValue, тому менш дивно).

Я також, мабуть, назвав би це дещо більше як GetNestedPropertyValue, щоб було очевидно, що він здійснює пошук у стеку властивостей.


Хороший дзвінок щодо впорядкування параметрів та запропонованої зміни імені.
itowlson

Дякуємо за відгук ... Я взяв обидві пропозиції у своїй реалізації. Врешті-решт я перетворив його на метод розширення для Objectкласу, який підсилює сенс впорядкування параметрів.
jheddings

Чому прийнята відповідь? Це не допомагає мені отримати властивість вкладеного об’єкта, як запитував OP.
Левітікон

@Levitikon Другий набір коду OP показує гідний спосіб зробити те, про що просили. Це була вся моя суть. У коді, який розміщений у самій відповіді, немає нічого поганого.
Рід Копсі,

27

Я використовую наступний метод для отримання значень із властивостей (вкладених класів), таких як

"Власність"

"Адреса. Вулиця"

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

Ось скрипка: https://dotnetfiddle.net/PvKRH0


Якщо властивість має значення null, це не спрацює, перед початком роботи потрібно перевірити, чи є src null.
Furtiro

@Furtiro так, звичайно, не може працювати, незалежно від того, чи є src (або propName) нульовим. Я додав виняток throw. Дякую
DevT

Радий допомогти! Але це не працює для мене з властивістю потрійного вкладеності, це зупиняється через 2, сумний, але чудовий код!
Furtiro

@Furtiro це дивно, доводиться працювати, як ви можете бачити на скрипці, яку я щойно додав до поста. Подивіться, можливо, ви знайдете свою проблему.
DevT

@DevT Привіт, чи мав би ти такий самий підхід, щоб GetProperty працював, коли ми використовуємо вкладений клас? тобто властивість var = type.GetProperty (sortProperty); не вдається з вкладеним класом (оскільки ми отримуємо нульовий результат), я вважаю, що ваше рішення може відповісти на це. (Для отримання детальної інформації наведене тут рішення stackoverflow.com/questions/11336713/… не вдається з вкладеним класом)
Kynao

13

Я знаю, що я трохи спізнився на вечірку, і, як казали інші, ваша реалізація чудова
... для простих випадків використання .
Однак я розробив бібліотеку, яка вирішує саме цей варіант використання, Pather.CSharp .
Він також доступний як пакет Nuget .

Його основний клас - це Resolverйого Resolveметод.
Ви передаєте йому об'єкт і на шляху власності , і він буде повертати необхідне значення .

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

Але він також може вирішити складніші шляхи властивостей , включаючи доступ до масиву та словника.
Наприклад, якщо у вас Customerбуло кілька адрес

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

Ви могли отримати доступ до другого за допомогою Addresses[1].

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

2
Як це обробляє нульові об'єкти для вкладених властивостей, тобто NullObject.Id, наприклад, якщо NullObject є нульовим на рахунку-фактурі?
AVFC_Bubble88

10

Ви повинні отримати доступ до об’єкта ACTUAL, про який вам потрібно використовувати відображення. Ось що я маю на увазі:

Замість цього:

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

Зробіть це (відредаговано на основі коментаря):

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

Подивіться на цю публікацію для отримання додаткової інформації: Використання відображення для встановлення властивості властивості об’єкта


Дякую за відповідь ... Я знаю, як отримати значення властивості першого рівня, але мені було цікаво, як отримати вкладене. У моїй фактичній програмі я не маю доступу до фактичного об'єкта.
jheddings

1
Потрібно безпосередньо отримати вкладені властивості. Я також перебуваю в ситуації, коли "Invoice" має значення T, і у мене є рядок шляху "Property.Property.Property". Не можна возитися з кожною власністю.
Левітікон

Це зробило це для мене. Мені також не пощастило з BillTo.Address. Мені цікаво, чи встановлення властивості працює однаково?
Yusif_Nurizade

7

В надії не прозвучати занадто пізно на вечірці, я хотів би додати своє рішення: безумовно, використовувати рекурсію в цій ситуації

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

Ви не пояснюєте джерело свого "дискомфорту", але ваш код мені в основному здається здоровим.

Єдине, що я б поставив під сумнів, це обробка помилок. Ви повертаєте нуль, якщо код намагається пройти через нульове посилання або якщо ім'я властивості не існує. Це приховує помилки: важко зрозуміти, чи повернуто воно null, тому що немає клієнта BillTo, або тому, що ви неправильно написали його "BilTo.Address" ... або тому, що є клієнт BillTo, і його адреса нульова! Я б дозволив методу збій і спалити у цих випадках - просто дозвольте виключенню уникнути (або, можливо, обернути його в більш дружній).


3

Ось ще одна реалізація, яка пропустить вкладену властивість, якщо вона є перелічувачем, і продовжить глибше. Перевірка переліку не впливає на властивості рядка типу.

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

ґрунтуючись на цьому питанні та на

Як дізнатись, чи є PropertyInfo колекцією Берріла

Я використовую це в проекті MVC для динамічного впорядкування даних, просто передаючи Властивість для сортування за Прикладом:

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

де BookingItems - це список об'єктів.


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*/ } 

Я б насправді кинув виняток, якщо вони запитують властивість, яка не існує. Те, як ви його закодували, якщо я викликаю GetPropValue, і він повертає null, я не знаю, чи означає це, що властивість не існувало, або властивість існувала, але значення було null.


Крім того, перемістіть перевірку на предмет obj як нульову за межі циклу.
Кевін Брок,

На жаль, не бачив багаторазового використання obj. Змінювати параметри не є доброю практикою програмування, це може призвести до плутанини в майбутньому. Використовуйте іншу змінну для параметра obj для обходу всередині циклу.
Кевін Брок,

Кевін: Для того, щоб використовувати іншу змінну, йому довелося б або призначити її obj в кінці, або зробити метод рекурсивним. Особисто я не думаю, що це проблема (хоча хороший коментар був би непоганим ...)
Рід Копсі

1
@Levitikon ОП заявив: "Будь-які ідеї щодо вдосконалення цього методу чи кращого способу вирішення цієї проблеми?". Таким чином, це відповідь, а не коментар, оскільки ОП просив вдосконалення, і це є вдосконаленням.
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
Опишіть, на чому базується ваше рішення, як це допоможе ОП - хороша практика.
DontVoteMeDown

1

Я хотів поділитися своїм рішенням, хоча це може бути пізно. Це рішення насамперед для перевірки, чи існує вкладена властивість. Але його можна легко налаштувати, щоб повернути вартість власності, якщо це необхідно.

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

Мені довелося звернутися до кількох постів, щоб запропонувати це рішення. Я думаю, це буде працювати для декількох вкладених типів властивостей.


0

Моє з’єднання з Інтернетом не працювало, коли мені потрібно було вирішити ту саму проблему, тому мені довелося «заново винаходити колесо»:

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

Практично впевнений, що це вирішує проблему для будь-якого рядка, який ви використовуєте для імені властивості, незалежно від ступеня вкладеності, якщо все є властивістю.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.