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


80

给定以下对象:

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
我以为我为此感到疯狂,但是,似乎有人遇到了与我相同的问题。好的解决方案
Marcello Grechi Lins 2013年

:还检查了我的答复转到另一个话题上去使用这些作为扩展方法stackoverflow.com/questions/1196991/...
jheddings

Answers:


12

我实际上认为您的逻辑很好。就个人而言,我可能会对其进行更改,以便您将对象作为第一个参数传递(与PropertyInfo.GetValue更加内联,因此不足为奇)。

我也可能将其更像GetNestedPropertyValue,以使其明显地向下搜索属性堆栈。


很好地要求重新排列参数和建议的名称更改。
itowlson

感谢您的反馈...我已将两个建议都纳入了我的实现中。最后,我将其转换为Object类的扩展方法,从而加强了对参数重新排序的意义。
jheddings

为什么会接受答案?如OP要求的那样,这无助于我获取嵌套对象的属性。
Levitikon 2012年

@Levitikon OP的第二组代码显示了一种执行所要求内容的不错方法。这就是我的意思。答案本身中发布的代码没有错。
Reed Copsey 2012年

26

我使用以下方法从(嵌套类)属性获取值,例如

“属性”

“街道地址”

“ 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是否为null,然后再使用。
Furtiro

@Furtiro是的,如果src(或propName)为null,则无法工作。我添加了throw异常。谢谢
DevT

乐意效劳 !但是对于三重嵌套属性,它对我不起作用,它在2(可悲但很棒的代码)之后停止!
Furtiro

@Furtiro,这很奇怪,必须工作,正如您在我刚刚添加到帖子中的小提琴中看到的那样。看看,也许您可​​以找到您的问题。
DevT

@DevT嗨,当我们使用嵌套类时,您有相同的方法使GetProperty起作用吗?即var属性= type.GetProperty(sortProperty); 嵌套类失败(因为我们得到一个空结果),我觉得您的解决方案可以回答这个问题。(有关完整的详细信息,此处给出的解决方案stackoverflow.com/questions/11336713/…对于嵌套类失败)
Kynao

11

我知道我有点迟到了,和其他人说,你的实现是好的
...简单的用例
但是,我已经开发了一个完全解决该用例的库Pather.CSharp
也可以作为Nuget包使用

它的主要类别是ResolverResolve方法。
你传递一个对象,并属性路径,它会返回所需的值

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在发票上为空?
AVFC_Bubble88年

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

请查看此文章以获取更多信息: 使用反射设置对象属性的属性


感谢您的回答...我知道如何获取一级属性的值,但是我想知道如何获取嵌套属性。在我的实际应用程序中,我无权访问实际的对象。
jheddings

1
需要直接获取嵌套属性。我也处于“发票”为T且路径字符串为“ Property.Property.Property”的情况。不能随便摆弄每个属性。
Levitikon 2012年

这为我做到了。我也没有BillTo.Address的运气。我很好奇设置属性是否以相同的方式进行吗?
Yusif_Nurizade

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。这隐藏了错误:因为没有BillTo客户,或者因为您将它拼写错误为“ BilTo.Address”……或者因为有BillTo客户,并且其Address为null,所以很难知道它是否返回null!在这种情况下,我会让方法崩溃并烧掉-只是让异常转义(或者将其包装在更友好的方法中)。


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,我不知道这是否意味着该属性不存在,或者该属性确实存在,但其值为null。


另外,将obj是否为null的检查移到循环外。
Kevin Brock

抱歉,没有看到重复使用obj。更改参数不是好的编程习惯,这可能会导致将来的混乱。对obj参数使用其他变量在循环中遍历。
Kevin Brock

凯文:为了使用其他变量,他必须要么在最后将其分配给obj,要么使该方法递归。就个人而言,我不认为这是个问题(尽管好的评论会很好...)
Reed Copsey

1
@Levitikon OP指出“关于如何改进此方法或解决此问题的更好方法的任何想法?”。因此,这是答案,而不是评论,因为OP要求进行改进,这是一种改进。
AaronLS 2012年

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
描述您的解决方案基于什么,它将如何帮助OP是一个很好的实践。
DontVoteMeDown

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

可以肯定的是,只要所有内容都是属性,无论嵌套程度如何,这都能解决您用于属性名称的任何字符串的问题。


-7

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.