Использование отражения в 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


Если свойство имеет значение 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]");

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, и его адрес равен нулю! В таких случаях я бы позволил методу вылететь и сгореть - просто позволю исключению ускользнуть (или, возможно, обернуть его более дружелюбным).


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 - список объектов.


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


Кроме того, переместите проверку на нулевое значение 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);
    }
}

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


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.