Лучший способ получить подчиненные свойства, используя GetProperty - PullRequest
22 голосов
/ 14 декабря 2008
public class Address
{
    public string ZipCode {get; set;}
}

public class Customer
{
    public Address Address {get; set;}
}

как я могу получить доступ к "ZipCode" или "Address.ZipCode" с отражением? Например:

Typeof(Customer).GetProperty("ZipCode")?

Ответы [ 6 ]

42 голосов
/ 14 декабря 2008

Вам нужно что-то вроде:

PropertyInfo addressProperty = typeof(Customer).GetProperty("Address");
ProportyInfo zipCodeProperty = addressProperty.PropertyType.GetProperty("ZipCode");

object address = addressProperty.GetValue(customer, null);
object zipCode = zipCodeProperty.GetValue(address, null);

Обычно, если вы хотите взять строку «Address.ZipCode» и перейти по ней вниз, вам нужно разделить ее на «.» и затем вызывайте GetProperty для соответствующего типа на каждом шаге, чтобы получить само свойство, затем PropertyInfo.GetValue, чтобы получить следующее значение в цепочке. Примерно так:

public static object FollowPropertyPath(object value, string path)
{
    Type currentType = value.GetType();

    foreach (string propertyName in path.Split('.'))
    {
        PropertyInfo property = currentType.GetProperty(propertyName);
        value = property.GetValue(value, null);
        currentType = property.PropertyType;
    }
    return value;
}

Назовите это так:

object zipCode = FollowPropertyPath(customer, "Address.ZipCode");

Обратите внимание, что это работает с типами свойств во время компиляции. Если вы хотите, чтобы он работал с типом времени выполнения (например, если customer.Address не имел свойства ZipCode, но фактический тип, возвращенный Address, имел), то измените property.PropertyType на property.GetType().

Также обратите внимание, что здесь нет обработки ошибок и т. Д .:)

7 голосов
/ 14 февраля 2011

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

public static class ReflectorUtil
{
    public static object FollowPropertyPath(object value, string path)
    {
        if (value == null) throw new ArgumentNullException("value");
        if (path == null) throw new ArgumentNullException("path");

        Type currentType = value.GetType();

        object obj = value;
        foreach (string propertyName in path.Split('.'))
        {
            if (currentType != null)
            {
                PropertyInfo property = null;
                int brackStart = propertyName.IndexOf("[");
                int brackEnd = propertyName.IndexOf("]");

                property = currentType.GetProperty(brackStart > 0 ? propertyName.Substring(0, brackStart) : propertyName);
                obj = property.GetValue(obj, null);

                if (brackStart > 0)
                {
                    string index = propertyName.Substring(brackStart + 1, brackEnd - brackStart - 1);
                    foreach (Type iType in obj.GetType().GetInterfaces())
                    {
                        if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
                        {
                            obj = typeof(ReflectorUtil).GetMethod("GetDictionaryElement")
                                                 .MakeGenericMethod(iType.GetGenericArguments())
                                                 .Invoke(null, new object[] { obj, index });
                            break;
                        }
                        if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof(IList<>))
                        {
                            obj = typeof(ReflectorUtil).GetMethod("GetListElement")
                                                 .MakeGenericMethod(iType.GetGenericArguments())
                                                 .Invoke(null, new object[] { obj, index });
                            break;
                        }
                    }
                }

                currentType = obj != null ? obj.GetType() : null; //property.PropertyType;
            }
            else return null;
        }
        return obj;
    }

    public static TValue GetDictionaryElement<TKey, TValue>(IDictionary<TKey, TValue> dict, object index)
    {
        TKey key = (TKey)Convert.ChangeType(index, typeof(TKey), null);
        return dict[key];
    }

    public static T GetListElement<T>(IList<T> list, object index)
    {
        return list[Convert.ToInt32(index)];
    }

}

Использование property.PropertyType предоставит вам тип свойства, определенный в классе obj, а использование obj.GetType () даст вам фактический тип экземпляра свойства.

РЕДАКТИРОВАТЬ: @Oliver - вы абсолютно правы, спасибо, что заметили это. Я настроил метод, чтобы учесть общие списки и словари. Хотя мне не нравится парсинг, я использовал умную идею Марка Гравелла в этом потоке , чтобы получить значения свойства индексатора.

5 голосов
/ 14 декабря 2008

Существующие ответы в порядке; просто альтернативная перспектива: во многих сценариях желательно использовать System.ComponentModel, а не прямое отражение, поскольку это допускает сценарии свойств времени выполнения - то есть, как DataView DataView предоставляет столбцы как properties .

Производительность - по умолчанию это в значительной степени идентично, но если вы делаете это много (например, массовый импорт / экспорт данных), вы можете получить значительное повышение производительности, используя этот подход, благодаря HyperDescriptor .

Чтобы использовать System.ComponentModel, код похож, но немного отличается:

static void Main()
{
    object obj = new Customer { Address = new Address { ZipCode = "abcdef" } };

    object address = GetValue(obj, "Address");
    object zip = GetValue(address, "ZipCode");

    Console.WriteLine(zip);
}
static object GetValue(object component, string propertyName)
{
    return TypeDescriptor.GetProperties(component)[propertyName].GetValue(component);
}

Это дает вам ту же обработку, как если бы вы использовали привязку данных для привязки к «Address.ZipCode» (приглушая некоторые детали, такие как списки и т. Д.).

(обратите внимание, что вы можете приводить zip в виде строки и т. Д., Если знаете, что это ожидаемый тип)

Чтобы получить значение из глубокого пути (включая ту же обработку списка, которую использует привязка данных), вы должны использовать что-то вроде:

static object ResolveValue(object component, string path) {
    foreach(string segment in path.Split('.')) {
        if (component == null) return null;
        if(component is IListSource) {
            component = ((IListSource)component).GetList();
        }
        if (component is IList) {
            component = ((IList)component)[0];
        }
        component = GetValue(component, segment);
    }
    return component;
}

Материал списка примерно отражает поведение обычного связывания данных (хотя в нем отсутствуют некоторые вещи, такие как контексты связывания, менеджеры валют и т. Д.)

2 голосов
/ 14 декабря 2008
typeof (Customer).GetProperty("Address").PropertyType.GetProperty("ZipCode")
1 голос
/ 02 апреля 2014

adabyron,

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

    public static Type FollowPropertyPath<T>(string path)
    {
        if (path == null) throw new ArgumentNullException("path");

        Type currentType = typeof(T);

        foreach (string propertyName in path.Split('.'))
        {
            int brackStart = propertyName.IndexOf("[");

            var property = currentType.GetProperty(brackStart > 0 ? propertyName.Substring(0, brackStart) : propertyName);

            if (property == null)
                return null;

            currentType = property.PropertyType;

            if (brackStart > 0)
            {
                foreach (Type iType in currentType.GetInterfaces())
                {
                    if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof (IDictionary<,>))
                    {
                        currentType = iType.GetGenericArguments()[1];
                        break;
                    }
                    if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof (ICollection<>))
                    {
                        currentType = iType.GetGenericArguments()[0];
                        break;
                    }
                }
            }
        }

        return currentType;
    }
0 голосов
/ 17 сентября 2018

Проблема: слабо типизированные переменные:

метод FollowPropertyPath (...) jonskeet * ... 1005 * почти точно соответствует моим потребностям; Кроме этого моя собственность была слабо напечатана; следовательно, currentType = property.PropertyType просто возвратил System.Object и потерпел неудачу на следующей итерации цикла foreach.

Решение: Чтобы использовать тип времени выполнения, а не тип времени разработки, я настроил метод следующим образом:

public static object FollowPropertyPath(object value, string path)
{
    Type currentType = value.GetType();

    foreach (string propertyName in path.Split('.'))
    {
        PropertyInfo property = currentType.GetProperty(propertyName);
        value = property.GetValue(value, null);
        currentType = value.GetType();    // <-- Change
    }
    return value;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...