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