Использование отражения в C# для получения свойств вложенного объекта



даны следующие объекты:



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.Адреса" не является допустимым свойством!--3--> класса.



Итак, я попытался написать метод, чтобы разделить строку на части на период, и ходить по объектам, ищущим конечное значение, которое меня интересовало в. Он работает нормально, но я не совсем комфортно с ним:



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


любые идеи о том, как улучшить этот метод, или лучший способ решить эту проблему?



EDIT после публикации я увидел несколько связанных сообщений... Однако, похоже, нет ответа, который конкретно затрагивает этот вопрос. Кроме того, я все равно хотел бы получить обратную связь о моей реализации.

689   12  

12 ответов:

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

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

вы должны получить доступ к фактическому объекту, на котором вам нужно использовать отражение. Вот что я имею в виду:

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

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

посмотрите на этот пост для получения дополнительной информации: использование отражения для установки свойства свойства объекта

вы не объясняете источник вашего "дискомфорта", но ваш код в основном выглядит здравым для меня.

единственное, что я бы спросил, это обработка ошибок. Вам возвращают null, если код пытается пройти через нулевую ссылку или если имя свойства не существует. Это скрывает ошибки: трудно узнать, вернул ли он null, потому что нет клиента BillTo, или потому что вы неправильно написали его "BilTo.Адрес.".. или потому что есть клиент BillTo, и его адрес равен нулю! Я бы пусть метод аварийно завершится и сгорит в этих случаях - просто позвольте исключению избежать (или, возможно, оберните его в более дружественный).

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

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

Я знаю, что немного опоздал на вечеринку, и, как говорили другие,ваша реализация в порядке
...для простых случаев использования.
Тем не менее, я разработал библиотеку, которая решает именно этот вариант использования,Песнь.CSharp.
Он также доступен как Пакет Nuget.

его основной класс Resolver С Resolve метод.
Вы передаете его объект и the путь к свойству, и он вернет нужное значение.

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

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

"свойства"

"адрес.Улица"

"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

> 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]);
            }
   if (info == null) { /* throw exception instead*/ } 

Я бы на самом деле бросил исключение, если они запрашивают свойство, которое не существует. Таким, каким вы его закодировали, если я назову новые, и он возвращает значение null, я не знаю, если это означает, что свойство не существует, или свойство существует, но его значение было null.

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

вот еще одна реализация, которая будет пропустить вложенное свойство, если это перечислитель и продолжить глубже. Проверка перечисления не влияет на свойства типа 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;
    }
}

основываясь на этом вопросе и на

Как узнать, является ли PropertyInfo коллекцией по Berryl

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

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

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

мое подключение к интернету было отключено, когда мне нужно было решить ту же проблему, поэтому мне пришлось "заново изобрести колесо":

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

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

попробовать inv.GetType().GetProperty("BillTo+Address");

Comments

    Ничего не найдено.