Using reflection in C# to get properties of a nested object

Viewed 106917

Given the following objects:

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

I'd like to use reflection to go through the Invoice to get the Name property of a Customer. Here's what I'm after, assuming this code would work:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Of course, this fails since "BillTo.Address" is not a valid property of the Invoice class.

So, I tried writing a method to split the string into pieces on the period, and walk the objects looking for the final value I was interested in. It works okay, but I'm not entirely comfortable with it:

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

Any ideas on how to improve this method, or a better way to solve this problem?

EDIT after posting, I saw a few related posts... There doesn't seem to be an answer that specifically addresses this question, however. Also, I'd still like the feedback on my implementation.

16 Answers

I actually think your logic is fine. Personally, I would probably change it around so you pass the object as the first parameter (which is more inline with PropertyInfo.GetValue, so less surprising).

I also would probably call it something more like GetNestedPropertyValue, to make it obvious that it searches down the property stack.

You have to access the ACTUAL object that you need to use reflection on. Here is what I mean:

Instead of this:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Do this (edited based on comment):

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

Look at this post for more information: Using reflection to set a property of a property of an object

You don't explain the source of your "discomfort," but your code basically looks sound to me.

The only thing I'd question is the error handling. You return null if the code tries to traverse through a null reference or if the property name doesn't exist. This hides errors: it's hard to know whether it returned null because there's no BillTo customer, or because you misspelled it "BilTo.Address"... or because there is a BillTo customer, and its Address is null! I'd let the method crash and burn in these cases -- just let the exception escape (or maybe wrap it in a friendlier one).

> 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*/ } 

I would actually throw an exception if they request a property that doesn't exist. The way you have it coded, if I call GetPropValue and it returns null, I don't know if that means the property didn't exist, or the property did exist but it's value was null.

I wanted to share my solution although it may be too late. This solution is primarily to check if the nested property exists. But it can be easily tweaked to return the property value if needed.

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

I had to refer to few posts to come up with this solution. I think this will work for multiple nested property types.

Based on the original code from @jheddings, I have created a extension method version with generic type and verifications:

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

I wrote a method that received one object type as the argument from the input and returns dictionary<string,string>

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;
    }
     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");
        
       var prop = src.GetType().GetProperty(propName);

        if (prop != null)
        {
            return prop.GetValue(src, null);
        }
        else
        {
            var props = src.GetType().GetProperties();

            foreach (var property in props)
            {
                var propInfo = src.GetType().GetProperty(property.Name);

                if (propInfo != null)
                {
                    var propVal = propInfo.GetValue(src, null);

                    if (src.GetType().GetProperty(property.Name).PropertyType.IsClass)
                    {
                        return GetPropertyValue(propVal, propName);
                    }

                    return propVal;
                }
            }

            return null;
        }

usage: calling part

var emp = new Employee() { Person = new Person() { FirstName = "Ashwani" } }; var val = GetPropertyValue(emp, "FirstName");

above can search the property value at any level

Try inv.GetType().GetProperty("BillTo+Address");

Related