96

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.

jheddings
  • 26,717
  • 8
  • 52
  • 65
  • just curious, if your `GetDesiredInvoice` returns you an object of type `Invoice` why not use `inv.BillTo.Name` directly ? – ram Dec 23 '09 at 19:46
  • I'm actually using this a bit differently, just simplified for my example. I'm taking an object and passing it into a processor that merges it with a template for printing. – jheddings Dec 23 '09 at 20:17
  • It just felt a little "brute-force" and seemed like there should be a better way. From the answers so far, however, it seems I wasn't totally off-base. – jheddings Dec 23 '09 at 20:25
  • 5
    I thought that i was crazy for doing this, but, seems like someone have had the same problem as i did. Great solution by the way – Marcello Grechi Lins Oct 07 '13 at 15:24
  • Also check out my reply to another topic for using these as extension methods: http://stackoverflow.com/questions/1196991/get-property-value-from-string-using-reflection-in-c-sharp/1954663#1954663 – jheddings Oct 07 '13 at 22:29

16 Answers16

55

I use following method to get the values from (nested classes) properties like

"Property"

"Address.Street"

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

Here is the Fiddle: https://dotnetfiddle.net/PvKRH0

DevT
  • 1,411
  • 3
  • 16
  • 32
  • If the property is null this won't work, need to check is src is null in the beginning before working with. – Furtiro Jun 12 '17 at 11:41
  • @Furtiro yes sure, can't work whether src (or propName) is null. I added the throw exception. Thanks – DevT Jun 12 '17 at 14:05
  • Glad to help ! But it doesn't work for me with a triple nesting property, it stops after 2 , sad but great code ! – Furtiro Jun 12 '17 at 14:27
  • @Furtiro that's strange, have to work as you can see on the fiddle I just added to the post. Have a look, maybe you can find your issue. – DevT Jun 12 '17 at 15:06
  • @DevT Hi, Would you have the same approach to make GetProperty works when we use nested class ? ie var property = type.GetProperty(sortProperty); fails with nested class (as we get a null result), i feel your solution can answers this. (For complete details, the solution given here https://stackoverflow.com/questions/11336713/how-do-i-create-an-expression-tree-for-run-time-sorting fails with nested class) – Kynao Sep 25 '18 at 15:46
  • @Kynao Hi, the method GetPropertyValue that I have posted should work with nested classes, in the same way. – DevT Oct 08 '18 at 10:34
  • @DevT This great code but sadly it works only upto Single level but not working to access 2 level nested element. – kjosh May 13 '20 at 20:30
  • You could also add an `if(src.GetType().IsValueType) return src;` after the first two checks to immediately return if somehow a variable of a value type snuck its way into there... – lhiapgpeonk Feb 20 '23 at 19:54
16

I know I'm a bit late to the party, and as others said, your implementation is fine
...for simple use cases.
However, I've developed a library that solves exactly that use case, Pather.CSharp.
It is also available as Nuget Package.

Its main class is Resolver with its Resolve method.
You pass it an object and the property path, and it will return the desired value.

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Address");

But it can also resolve more complex property paths, including array and dictionary access.
So, for example, if your Customer had multiple addresses

public class Customer {
    public String Name { get; set; }
    public IEnumerable<String> Addresses { get; set; }
}

you could access the second one using Addresses[1].

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Addresses[1]");
Domysee
  • 12,718
  • 10
  • 53
  • 84
  • 3
    How does this handle null objects for nested properties i.e. NullObject.Id with NullObject being null on the Invoice for instance? – AVFC_Bubble88 Jul 27 '17 at 13:23
13

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.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Good call on reordering the parameters and the suggested name change. – itowlson Dec 23 '09 at 19:21
  • Thanks for the feedback... I've taken both suggestions into my implementation. I ended up turning it into an extension method on the `Object` class, which strengthens the point on reordering the parameters. – jheddings Dec 23 '09 at 20:28
  • Why is the accepted answer? It doesn't help me get the property of a nested object as the OP asked. – Levitikon Jun 20 '12 at 13:53
  • @Levitikon The OP's second set of code shows a decent way to do what was asked. That was my whole point. There is nothing wrong with the code as posted in the answer itself. – Reed Copsey Jun 20 '12 at 15:08
12

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

Community
  • 1
  • 1
Gabriel McAdams
  • 56,921
  • 12
  • 61
  • 77
  • Thanks for the answer... I know how to get the value of a first-level property, but I was wondering how to get a nested one. In my actual application, I don't have access to the actual object. – jheddings Dec 23 '09 at 19:18
  • 2
    Need to directly get nested properties. I'm also in a situation where "Invoice" is T and I have a string of the path "Property.Property.Property". Can't be fiddling around with each property. – Levitikon Jun 20 '12 at 13:49
  • This did it for me. I was having no luck with BillTo.Address either. I am curious whether setting the property works the same way? – Yusif_Nurizade Sep 07 '18 at 22:38
8

In hopes of not sounding too late to the party, I would like to add my solution: Definitely use recursion in this situation

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();
        }
    }
roger l
  • 638
  • 5
  • 8
6

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

itowlson
  • 73,686
  • 17
  • 161
  • 157
4

Here is another implementation that will skip a nested property if it is an enumerator and continue deeper. Properties of type string are not affected by the Enumeration Check.

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

based on this question and on

How to know if a PropertyInfo is a collection by Berryl

I use this in a MVC project to dynamically Order my data by simply passing the Property to sort by Example:

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

where BookingItems is a list of objects.

Community
  • 1
  • 1
erevosgr
  • 41
  • 1
  • 3
3
> 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]);
            }
Mohamed.Abdo
  • 2,054
  • 1
  • 19
  • 12
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() + "'");
    }
BarbaBabak
  • 11
  • 2
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.

AaronLS
  • 37,329
  • 20
  • 143
  • 202
  • In addition, move the check for obj being null outside the loop. – Kevin Brock Dec 23 '09 at 19:36
  • Sorry, didn't see the repeated use of obj. It's not good programming practice to change your parameters, this can lead to confusion in the future. Use a different variable for the obj parameter to traverse within the loop. – Kevin Brock Dec 23 '09 at 19:38
  • Kevin: In order to use a different variable, he'd have to either assign it to obj at the end, or make the method recursive. Personally, I don't think this is a problem (although a good comment would be nice...) – Reed Copsey Dec 23 '09 at 19:39
  • 1
    @Levitikon The OP stated "Any ideas on how to improve this method, or a better way to solve this problem?". Thus this is an answer and not a comment, because the OP asked for improvements, which this is an improvement. – AaronLS Jun 20 '12 at 15:16
1

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.

Harshit Gindra
  • 355
  • 3
  • 7
0

My internet connection was down when I need to solve the same problem, so I had to 're-invent the wheel':

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

Pretty sure this solves the problem for any string you use for property name, regardless of extent of nesting, as long as everything's a property.

MalibuCusser
  • 124
  • 1
  • 8
0

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;
}
dgzornoza
  • 639
  • 4
  • 9
0

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;
    }
pejman
  • 740
  • 7
  • 13
0
     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

-7

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

ram
  • 11,468
  • 16
  • 63
  • 89