Использование отражения в 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напрямую?
ram
На самом деле я использую это немного по-другому, просто упрощенно для моего примера. Я беру объект и передаю его процессору, который объединяет его с шаблоном для печати.
jheddings
Это просто было немного грубой силой, и казалось, что должен быть способ получше. Однако, судя по полученным до сих пор ответам, кажется, что я не был полностью отклонен.
jheddings
3
Я думал, что сошел с ума от этого, но, похоже, у кого-то была такая же проблема, как и у меня.
Между
Также ознакомьтесь с моим ответом в другой теме об их использовании в качестве методов расширения: stackoverflow.com/questions/1196991/…
jheddings

Ответы:

12

Я действительно думаю, что ваша логика в порядке. Лично я бы, вероятно, изменил его так, чтобы вы передавали объект в качестве первого параметра (что более похоже на PropertyInfo.GetValue, поэтому менее удивительно).

Я бы также, вероятно, назвал это чем-то вроде GetNestedPropertyValue, чтобы было очевидно, что он выполняет поиск по стеку свойств.

Рид Копси
источник
Хороший звонок по поводу изменения порядка параметров и предложенного изменения имени.
itowlson
Спасибо за отзыв ... Я учел оба предложения в своей реализации. В итоге я превратил его в метод расширения Objectкласса, что усиливает точку зрения на переупорядочение параметров.
jheddings
Почему принятый ответ? Это не помогает мне получить свойство вложенного объекта, как просил OP.
Levitikon
@Levitikon Второй набор кода OP показывает достойный способ сделать то, о чем просили. В этом вся моя точка зрения. Нет ничего плохого в коде, указанном в самом ответе.
Reed Copsey
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

DevT
источник
Если свойство имеет значение null, это не сработает, необходимо проверить, является ли src нулевым в начале, прежде чем работать с.
Furtiro
@Furtiro да, конечно, не может работать, если src (или propName) имеет значение null. Я добавил исключение throw. Спасибо
DevT
Рад помочь ! Но у меня это не работает с тройным свойством вложенности, он останавливается через 2, печальный, но отличный код!
Furtiro
@Furtiro, это странно, должно работать, как вы можете видеть на скрипке, которую я только что добавил в сообщение. Посмотрите, может, вы найдете свою проблему.
DevT
@DevT Привет, у вас был бы такой же подход к работе GetProperty, когда мы используем вложенный класс? т.е. var property = 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]");
Domysee
источник
2
Как это обрабатывает нулевые объекты для вложенных свойств, например, NullObject.Id, когда NullObject имеет значение NULL в счете-фактуре?
AVFC_Bubble88
10

Вы должны получить доступ к ФАКТИЧЕСКОМУ объекту, для которого нужно использовать отражение. Вот что я имею в виду:

Вместо этого:

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
Необходимо напрямую получить вложенные свойства. Я также нахожусь в ситуации, когда «Счет-фактура» - это T, и у меня есть строка пути «Property.Property.Property». Нельзя возиться с каждым свойством.
Levitikon
Это сделало это для меня. Мне тоже не повезло с BillTo.Address. Мне любопытно, работает ли настройка свойства таким же образом?
Юсиф Нуризаде 07
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, если код пытается пройти через пустую ссылку или если имя свойства не существует. Это скрывает ошибки: трудно понять, вернул ли он значение null, потому что нет клиента BillTo, или из-за того, что вы неправильно написали "BilTo.Address" ... или потому, что есть клиент BillTo, и его адрес равен нулю! В таких случаях я бы позволил методу вылететь и сгореть - просто позволю исключению ускользнуть (или, возможно, обернуть его более дружелюбным).

Itowlson
источник
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 коллекцией Berryl

Я использую это в проекте MVC, чтобы динамически упорядочивать мои данные, просто передав свойство для сортировки по примеру:

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

где BookingItems - список объектов.

Erevosgr
источник
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, я не знаю, означает ли это, что свойство не существует, или свойство действительно существует, но его значение было нулевым.

AaronLS
источник
Кроме того, переместите проверку на нулевое значение obj за пределы цикла.
Кевин Брок,
Извините, не видел повторного использования obj. Изменять параметры - не лучшая практика программирования, это может привести к путанице в будущем. Используйте другую переменную для параметра obj для перемещения внутри цикла.
Кевин Брок,
Кевин: Чтобы использовать другую переменную, ему пришлось бы либо присвоить ее объекту obj в конце, либо сделать метод рекурсивным. Лично я не думаю, что это проблема (хотя хороший комментарий был бы неплохим ...)
Рид Копси
1
@Levitikon В ОП заявили: «Есть идеи, как улучшить этот метод или как лучше решить эту проблему?». Таким образом, это ответ, а не комментарий, потому что OP запросил улучшения, а это улучшение.
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);
    }
}

Совершенно уверен, что это решает проблему для любой строки, которую вы используете для имени свойства, независимо от степени вложенности, пока все является свойством.

МалибуКассер
источник
-7

Пытаться inv.GetType().GetProperty("BillTo+Address");

баран
источник