So the starting place is creating an expression visitor. This lets us find all of the member accesses within a particular expression. This leaves us with the question of what to do for each member access.
So the first thing is to recursively visit on the expression that the member is being accessed on. From there, we can use Expression.Condition
to create a conditional block that compares that processed underlying expression to null
, and returns null
if true an the original starting expression if it's not.
Note that we need to provide implementations for both Members and method calls, but the process for each is basically identical.
We'll also add in a check so that of the underlying expression is null
(which is to say, there is no instance and it's a static member) or if it's a non-nullable type, that we just use the base behavior instead.
public class MemberNullPropogationVisitor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == null || !IsNullable(node.Expression.Type))
return base.VisitMember(node);
var expression = base.Visit(node.Expression);
var nullBaseExpression = Expression.Constant(null, expression.Type);
var test = Expression.Equal(expression, nullBaseExpression);
var memberAccess = Expression.MakeMemberAccess(expression, node.Member);
var nullMemberExpression = Expression.Constant(null, node.Type);
return Expression.Condition(test, nullMemberExpression, node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Object == null || !IsNullable(node.Object.Type))
return base.VisitMethodCall(node);
var expression = base.Visit(node.Object);
var nullBaseExpression = Expression.Constant(null, expression.Type);
var test = Expression.Equal(expression, nullBaseExpression);
var memberAccess = Expression.Call(expression, node.Method);
var nullMemberExpression = Expression.Constant(null, MakeNullable(node.Type));
return Expression.Condition(test, nullMemberExpression, node);
}
private static Type MakeNullable(Type type)
{
if (IsNullable(type))
return type;
return typeof(Nullable<>).MakeGenericType(type);
}
private static bool IsNullable(Type type)
{
if (type.IsClass)
return true;
return type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
}
We can then create an extension method to make calling it easier:
public static Expression PropogateNull(this Expression expression)
{
return new MemberNullPropogationVisitor().Visit(expression);
}
As well as one that accepts a lambda, rather than any expression, and can return a compiled delegate:
public static Func<T> PropogateNull<T>(this Expression<Func<T>> expression)
{
var defaultValue = Expression.Constant(default(T));
var body = expression.Body.PropogateNull();
if (body.Type != typeof(T))
body = Expression.Coalesce(body, defaultValue);
return Expression.Lambda<Func<T>>(body, expression.Parameters)
.Compile();
}
Note that, to support cases where the accessed member resolves to a non-nullable value, we're changing the type of those expressions to lift them to be nullable, using MakeNullable
. This is a problem with this final expression, as it needs to be a Func<T>
, and it won't match if T
isn't also lifted. Thus, while it's very much non-ideal (ideally you'd never call this method with a non-nullable T
, but there's no good way to support this in C#) we coalesce the final value using the default value for that type, if necessary.
(You can trivially modify this to accept a lambda accepting a parameter, and pass in a value, but you can just as easily close over that parameter instead, so I see no real reason to.)
It's also worth pointing out that in C# 6.0, when it's actually released, we'll have an actual null propogation operator (?.
), making all of this very unnecessary. You'll be able to write:
if(a?.b?.c?.d?.e?.f != null)
Console.Write("ok");
and have exactly the semantics you're looking for.