Say you are trying to read this property
var town = Staff.HomeAddress.Postcode.Town;
Somewhere along the chain a null could exist. What wo
You could also consider using the Maybe
monad and having an extension method like ToMaybe()
that gives you a Just a
if the object is not null, a Nothing
if it is.
I won't go into the implementation details (unless someone asks) but the code would look like this:
var maybeTown = from s in staff.ToMaybe()
from h in s.HomeAddress.ToMaybe()
from p in h.Postcode.ToMaybe()
from t in p.Town.ToMaybe()
select t;
var town = maybeTown.OrElse(null);
which is really clean or really ugly depending on you point of view
I came up with the same solution as Ani's some time ago, see this blog post for details. While elegant, it's very inefficient...
var town = Staff.NullSafeEval(s => s.HomeAddress.Postcode.Town, "(N/A)");
A better solution IMHO is the one suggested in this CodeProject article:
string town = Staff.With(s => s.HomeAddress)
.With(a => a.Postcode)
.With(p => p.Town);
The only thing I don't like with this solution is the name of the extension method, but it can easily be changed...
var town = "DefaultCity";
if (Staff != null &&
Staff.HomeAddress != null &&
Staff.HomeAddress.Postcode != null &&
Staff.HomeAddress.Postcode.Town != null)
{
town = Staff.HomeAddress.Postcode.Town;
}
Another go:
Declare a helper method
bool HasNull(params object[] objects)
{
foreach (object o in objects) { if (o == null) return true; }
return false;
}
Then use it like this:
if (!HasNull(Staff, Staff.HomeAdress, Staff.HomeAddress.Postcode, Staff.HomeAddress.Postcode.Town))
{
town = Staff.HomeAddress.Postcode.Town;
}
I agree with Oded that this violates the Law of Demeter.
I was intrigued by your question though, so I wrote up a a poor man's "Null-Safe Evaluate" extension-method with expression-trees, just for fun. This should give you compact syntax to express the desired semantics.
Please don't use this in production code.
Usage:
var town = Staff.NullSafeEvaluate(s => s.HomeAddress.Postcode.Town);
This will evaluate in succession:
Staff
Staff.HomeAddress
Staff.HomeAddress.Postcode
Staff.HomeAddress.Postcode.Town
(Caching and reusing the values of the intermediate expressions to produce the next one)
If it encounters a null
reference, it returns the default value of the type of Town
. Otherwise, it returns the value of the full expression.
(Not throughly tested, can be improved in terms of performance and doesn't support instance-methods. POC only.)
public static TOutput NullSafeEvaluate<TInput, TOutput>
(this TInput input, Expression<Func<TInput, TOutput>> selector)
{
if (selector == null)
throw new ArgumentNullException("selector");
if (input == null)
return default(TOutput);
return EvaluateIterativelyOrDefault<TOutput>
(input, GetSubExpressions(selector));
}
private static T EvaluateIterativelyOrDefault<T>
(object rootObject, IEnumerable<MemberExpression> expressions)
{
object currentObject = rootObject;
foreach (var sourceMemEx in expressions)
{
// Produce next "nested" member-expression.
// Reuse the value of the last expression rather than
// re-evaluating from scratch.
var currentEx = Expression.MakeMemberAccess
(Expression.Constant(currentObject), sourceMemEx.Member);
// Evaluate expression.
var method = Expression.Lambda(currentEx).Compile();
currentObject = method.DynamicInvoke();
// Expression evaluates to null, return default.
if (currentObject == null)
return default(T);
}
// All ok.
return (T)currentObject;
}
private static IEnumerable<MemberExpression> GetSubExpressions<TInput, TOutput>
(Expression<Func<TInput, TOutput>> selector)
{
var stack = new Stack<MemberExpression>();
var parameter = selector.Parameters.Single();
var currentSubEx = selector.Body;
// Iterate through the nested expressions, "reversing" their order.
// Stop when we reach the "root", which must be the sole parameter.
while (currentSubEx != parameter)
{
var memEx = currentSubEx as MemberExpression;
if (memEx != null)
{
// Valid member-expression, push.
stack.Push(memEx);
currentSubEx = memEx.Expression;
}
// It isn't a member-expression, it must be the parameter.
else if (currentSubEx != parameter)
{
// No, it isn't. Throw, don't support arbitrary expressions.
throw new ArgumentException
("Expression not of the expected form.", "selector");
}
}
return stack;
}