Click here to Skip to main content
Click here to Skip to main content

Get Nested Property value using reflection and Linq.Expression

, 20 Nov 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Get Nested Property value using reflection and Linq.Expression
Click here to download the assembly from NuGet
 
Sometimes, we need to access an object's nested properties. To avoid the very common "Object Reference Null" exception, we have to test all the nodes of the object tree, which makes the code less readable.
 
Let's see an example to clarify what I'm try to achieve. Let's say, I got Employ and Address classes which are implemented as below:
 
public class Employee
{
    public string Name { get; set; }
    public string LastName { get; set; }
    public Address Address { get; set; }
    public Employee Director { get; set; }
}    
public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
    public string PostCode { get; set; }
}
 
Now suppose we want to get the Director's city property from his employee, it would look like something below:
 
if(employee.Director != null && employee.Director.Address !=null)
{
    employee.Director.Address.City;
}
 
Using the extension methods, we are allowed to access the property without testing all the "nodes" as showed below:
 
string city = employee.NullSafeGetValue(x => x.Director.Address.City, "NoCity");
 
As you can see, the NullSafeGetValue takes two parameters:
 
The first parameter is a lambda expression of the property we want to access.
 
The second is a default value, which is returned when the property is not reachable (due to perhaps the Director member being null).
 
I have found lots of solutions on the internet, but all of them were using expression trees which add lots of overhead.
 
My solution still uses “lambda expression” syntax, but it doesn’t use any expression tree.
 
Below is the NullSafeGetValue extension method code:
 
using System;
using System.Collections;
using System.Linq.Expressions;
using System.Reflection;
public static class ObjectExtension
{
     /// <summary>
    ///
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="source">Root Object - it must be a reference type or a sub class of IEnumerable</param>
    /// <param name="expression">Labmda expression to set the property value returned</param>
    /// <param name="defaultValue">The default value in the case the property is not reachable </param>
    /// <returns></returns>
    public static TResult NullSafeGetValue<TSource, TResult>(this TSource source, Expression<Func<TSource, TResult>> expression, TResult defaultValue)
    {
        var value = GetValue(expression, source);
        return value == null ? defaultValue : (TResult)value;
    }
    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    /// <typeparam name="TCastResultType"></typeparam>
    /// <param name="source">Root Object</param>
    /// <param name="expression">Labmda expression to set the property value returned</param>
    /// <param name="defaultValue">The default value in the case the property is not reachable</param>
    /// <param name="convertToResultToAction">An action to cast the returned value</param>
    /// <returns></returns>
    public static TCastResultType NullSafeGetValue<TSource, TResult, TCastResultType>(this TSource source, Expression<Func<TSource, TResult>> expression, TCastResultType defaultValue, Func<object, TCastResultType> convertToResultToAction)
    {
        var value = GetValue(expression, source);
        return value == null ? defaultValue : convertToResultToAction.Invoke(value);
    }
    private static string GetFullPropertyPathName<TSource, TResult>(Expression<Func<TSource, TResult>> expression)
    {
        return expression.Body.ToString().Replace(expression.Parameters[0] + ".", string.Empty);
    }
    private static object GetValue<TSource, TResult>(Expression<Func<TSource, TResult>> expression, TSource source)
    {
        string fullPropertyPathName = GetFullPropertyPathName(expression);
        return GetNestedPropertyValue(fullPropertyPathName, source);
    }
    private static object GetNestedPropertyValue(string name, object obj)
    {
        PropertyInfo info;
        foreach (var part in name.Split('.'))
        {
            if (obj == null)
            {
                return null;
            }
            var type = obj.GetType();
            if (obj is IEnumerable)
            {
                type = (obj as IEnumerable).GetType();
                var methodInfo = type.GetMethod("get_Item");
                var index = int.Parse(part.Split('(')[1].Replace(")", string.Empty));
                try
                {
                    obj = methodInfo.Invoke(obj, new object[] { index });
                }
                catch (Exception)
                {
                    obj = null;
                }
            }
            else
            {
                info = type.GetProperty(part);
                if (info == null)
                {
                    return null;
                }
                obj = info.GetValue(obj, null);
            }
        }
        return obj;
    }
}
 
There is also an overload that allows you to cast the property before it is returned.
 
Just to give you an example, we need to add a property to Address class named “HouseNumber”:
 
public class Address
{
      public string City { get; set; }
      public string Street { get; set; }
      public string PostCode { get; set; }
      public string HouseNumber { get; set; }
}
 
Now let’s suppose we want to access the employee’s director address to get the house number and cast it to an int. We would do something like the below:
 
Using the NullSafeGetValue overload, we can pass a function for casting the property before it is returned:
 
int houseNumber = employ.NullSafeGetValue(x => x.Director.Address.HouseNumber, 0, x => int.Parse(x.ToString()));
 
The extension method does work even with IEnumerable objects (as List)
 
Let's change a bit the address class making address a list of address:
 
public class Employ
   {
       public string Name { get; set; }
       public string LastName { get; set; }
       public IList<Address> Addresses { get; set; }
       public Employ Director { get; set; }
   }
 
Now, let's say I want to access the address[0].street:
 
I can do the below:
 
var street = e.NullSafeGetValue(x => x.Addresses[0].Street, string.Empty);

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Massimiliano Peluso "WeDev Limited"
Architect PeluSoft Limited
United Kingdom United Kingdom
I have been fascinated by software development since I was 10 years old. I'm proud of learned the first programming language with an Olivetti PC 128S based on Microsoft BASIC. I'm a Software Architect/Senior Developer with a strong background of all the developing Microsoft's technologies.

Comments and Discussions

 
GeneralThere's a bug in GetFullPropertyPathName; try passing in "e ... PinmemberRichard Deeming21-Nov-11 9:33 
GeneralHi thanks for your feedback It doesn't use expression means ... PinmemberMassimiliano Peluso "PeluSoft Limited"9-Apr-11 10:46 
GeneralUmmm .. you say that your code doesn't use expression trees ... PinmemberChris Trelawny-Ross9-Apr-11 7:32 
GeneralYou might want to do some comparisons in performance, becaus... PinmemberAndrew Rissing5-Apr-11 4:51 
GeneralSuggestion PinmemberAndrew Rissing5-Apr-11 6:08 
GeneralRe: Suggestion PinmemberMassimiliano Peluso "PeluSoft Limited"6-Apr-11 4:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141220.1 | Last Updated 20 Nov 2011
Article Copyright 2011 by Massimiliano Peluso "WeDev Limited"
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid