在C#中使用反射来获取嵌套对象的属性

时间:2009-12-23 19:10:53

标签: c# reflection

给出以下对象:

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的{​​{1}}属性。这就是我所追求的,假设这段代码可行:

Customer

当然,由于“BillTo.Address”不是Invoice inv = GetDesiredInvoice(); // magic method to get an invoice PropertyInfo info = inv.GetType().GetProperty("BillTo.Address"); Object val = info.GetValue(inv, null); 类的有效属性,因此失败。

所以,我尝试编写一个方法将字符串拆分成句点,然后遍历对象,寻找我感兴趣的最终值。它可以正常工作,但我对它并不是很满意:

Invoice

有关如何改进此方法的任何想法,或解决此问题的更好方法?

编辑发布后,我看到了一些相关的帖子......但似乎并没有专门针对这个问题的答案。另外,我仍然喜欢我的实施反馈。

15 个答案:

答案 0 :(得分:11)

我实际上认为你的逻辑很好。就个人而言,我可能会更改它,因此您将对象作为第一个参数传递(与PropertyInfo.GetValue更内联,因此不那么令人惊讶)。

我也可能会把它称为更像GetNestedPropertyValue的东西,以明确它搜索属性堆栈。

答案 1 :(得分:10)

您必须访问需要使用反射的ACTUAL对象。这就是我的意思:

而不是:

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

查看这篇文章了解更多信息: Using reflection to set a property of a property of an object

答案 2 :(得分:9)

我知道我在聚会上有点迟了,正如其他人所说,你的实施很好
... 用于简单用例
但是,我已经开发了一个库,可以完全解决该用例Pather.CSharp 它也可以Nuget Package获得。

它的主要类是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]");

答案 3 :(得分:8)

我使用此方法从

等属性中获取值

<强>&#34;属性&#34;

<强>&#34; Address.Street&#34;

<强>&#34; Address.Country.Name&#34;

    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

答案 4 :(得分: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();
        }
    }

答案 5 :(得分:6)

你没有解释你的“不适”的来源,但你的代码对我来说基本上是合理的。

我唯一要问的是错误处理。如果代码尝试遍历空引用或者属性名称不存在,则返回null。这隐藏了错误:很难知道它是否因为没有BillTo客户而返回null,或者因为你拼错了它“BilTo.Address”......或者因为有一个BillTo客户,并且它的地址为空!在这些情况下,我会让方法崩溃并烧掉 - 只是让异常逃脱(或者将它包装在更友好的中)。

答案 6 :(得分: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]);
            }

答案 7 :(得分:1)

   if (info == null) { /* throw exception instead*/ } 

如果他们请求不存在的属性,我实际上会抛出异常。你编码的方式,如果我调用GetPropValue并返回null,我不知道这是否意味着该属性不存在,或者属性确实存在,但它的值为null。

答案 8 :(得分: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() + "'");
    }

答案 9 :(得分:1)

如果它是一个枚举器并且继续深入,那么跳过嵌套属性的另一个实现。类型字符串的属性不受枚举检查的影响。

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

基于这个问题和

  

How to know if a PropertyInfo is a collection   作者:Berryl

我在MVC项目中使用它来通过简单地传递Property来对其进行排序来动态地对我的数据进行排序 示例:

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

其中BookingItems是一个对象列表。

答案 10 :(得分: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);
    }
}

非常确定这解决了您用于属性名称的任何字符串的问题,无论嵌套的程度如何,只要一切都是属性。

答案 11 :(得分:0)

我想分享自己的解决方案,尽管可能为时已晚。该解决方案主要是检查嵌套属性是否存在。但是如果需要,可以很容易地对其进行调整以返回属性值。

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

我不得不参考一些帖子来提出这个解决方案。我认为这将适用于多种嵌套属性类型。

答案 12 :(得分:0)

基于@jheddings 的原始代码,我创建了一个带有泛型类型和验证的扩展方法版本:

public static T GetPropertyValue<T>(this object sourceObject, string propertyName)
{
    if (sourceObject == null) throw new ArgumentNullException(nameof(sourceObject));
    if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentException(nameof(propertyName));

    foreach (string currentPropertyName in propertyName.Split('.'))
    {
        if (string.IsNullOrWhiteSpace(currentPropertyName)) throw new InvalidOperationException($"Invalid property '{propertyName}'");

        PropertyInfo propertyInfo = sourceObject.GetType().GetProperty(currentPropertyName);
        if (propertyInfo == null) throw new InvalidOperationException($"Property '{currentPropertyName}' not found");

        sourceObject = propertyInfo.GetValue(sourceObject);
    }

    return sourceObject is T result ? result : default;
}

答案 13 :(得分:0)

我写了一个方法,它从输入接收一个对象类型作为参数并返回字典

public static Dictionary<string, string> GetProperties(Type placeHolderType)
    {
        var result = new Dictionary<string, string>();
        var properties = placeHolderType.GetProperties();
        foreach (var propertyInfo in properties)
        {
            string name = propertyInfo.Name;
            string description = GetDescriptionTitle(propertyInfo);
            if (IsNonString(propertyInfo.PropertyType))
            {
                var list = GetProperties(propertyInfo.PropertyType);
                foreach (var item in list)
                {
                    result.Add($"{propertyInfo.PropertyType.Name}_{item.Key}", item.Value);
                }
            }
            else
            {
                result.Add(name, description);
            }
        }
        return result;
    }

public static bool IsNonString(Type type)
    {
        if (type == null || type == typeof(string))
            return false;
        return typeof(IPlaceHolder).IsAssignableFrom(type);
    }

private static string GetDescriptionTitle(MemberInfo memberInfo)
    {
        if (Attribute.GetCustomAttribute(memberInfo, typeof(DescriptionAttribute)) is DescriptionAttribute descriptionAttribute)
        {
            return descriptionAttribute.Description;
        }
        return memberInfo.Name;
    }

答案 14 :(得分:-7)

尝试inv.GetType().GetProperty("BillTo+Address");