使用GetProperty获取子属性的最佳方法

29
public class Address
{
    public string ZipCode {get; set;}
}

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

如何使用反射访问"ZipCode"或"Address.ZipCode"? 例如:

Typeof(Customer).GetProperty("ZipCode")?
6个回答

56

你需要类似这样的东西:

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

还要注意,此方法不具备任何错误处理等功能 :)


10

Jon Skeet的回答很好,但是我还需要稍作修改来考虑属性路径中的派生实例:

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 - 你是完全正确的,谢谢注意到这一点。我调整了方法以允许通用列表和字典。虽然我不喜欢解析部分,但我在此线程中使用了Marc Gravell的巧妙想法来获取索引器属性的值。

1
根据您期望在路径中获取的内容,您可能会在索引属性(例如 somePerson.Adresses[3].Street)上失败。 - Oliver

5
现有的答案很好,这里提供另一种观点:在许多情况下,使用System.ComponentModel而不是直接反射更为理想,因为这允许运行时属性场景 - 例如,一个DataTable的DataView将列作为属性公开。
性能方面-默认情况下,这基本相同,但如果您需要频繁执行此操作(例如,批量数据导入/导出),则可以通过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
typeof (Customer).GetProperty("Address").PropertyType.GetProperty("ZipCode")

1
问题:弱类型变量:
@jonskeet的FollowPropertyPath(...)方法几乎完全满足了我的需求;但是,我的属性是弱类型的;因此,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;
}

1

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接