Click here to Skip to main content
15,881,757 members
Articles / Web Development / ASP.NET
Tip/Trick

Get Nested Property value using reflection and Linq.Expression

Rate me:
Please Sign up or sign in to vote.
4.86/5 (12 votes)
20 Nov 2011CPOL2 min read 58.7K   13   7
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:

C#
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:

C#
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:

C#
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:

C#
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”:

C#
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:

C#
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:

C#
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:

C#
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)


Written By
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 ... Pin
Richard Deeming21-Nov-11 8:33
mveRichard Deeming21-Nov-11 8:33 
GeneralHi thanks for your feedback It doesn't use expression means ... Pin
Massimiliano Peluso "WeDev Limited"9-Apr-11 9:46
Massimiliano Peluso "WeDev Limited"9-Apr-11 9:46 
GeneralUmmm .. you say that your code doesn't use expression trees ... Pin
Chris Trelawny-Ross9-Apr-11 6:32
Chris Trelawny-Ross9-Apr-11 6:32 
GeneralYou might want to do some comparisons in performance, becaus... Pin
Andrew Rissing5-Apr-11 3:51
Andrew Rissing5-Apr-11 3:51 
You might want to do some comparisons in performance, because I believe "info.GetValue(obj, null)" will be orders of magnitude slower than expression-based alternatives.
GeneralSuggestion Pin
Andrew Rissing5-Apr-11 5:08
Andrew Rissing5-Apr-11 5:08 
GeneralRe: Suggestion Pin
Massimiliano Peluso "WeDev Limited"6-Apr-11 3:32
Massimiliano Peluso "WeDev Limited"6-Apr-11 3:32 

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

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