I've been trying to create a custom ExpressionVisitor
that would generate an expression that (optionaly) throws a NullReferenceException
on the first null
value. The DebugView
of the expression looks fine to me but it doesn't work as exptecte (by me). I thought it would first throw the
.Throw .New System.NullReferenceException("c3")
because the test variable is null
but instead this one is thrown
.Throw .New System.NullReferenceException("p")
I cannot understand why it executes the statements backwards. Shouldn't it execute the innermost If
first?
DebugView:
.Block() {
.If (.Block() {
.If (.Constant<ExpressionTrees.Program+<>c__DisplayClass0_0>(ExpressionTrees.Program+<>c__DisplayClass0_0) == null) {
.Throw .New System.NullReferenceException("c3")
} .Else {
.Default(System.Void)
};
.Constant<ExpressionTrees.Program+<>c__DisplayClass0_0>(ExpressionTrees.Program+<>c__DisplayClass0_0).c3
} == null) {
.Throw .New System.NullReferenceException("p")
} .Else {
.Default(System.Void)
};
(.Constant<ExpressionTrees.Program+<>c__DisplayClass0_0>(ExpressionTrees.Program+<>c__DisplayClass0_0).c3).p
}
My complete test code:
namespace ExpressionTrees
{
class c1
{
public c2 c2 { get; set; }
}
class c2
{
public c3 c3 { get; set; }
}
class c3
{
public string p { get; set; }
}
class Program
{
static void Main(string[] args)
{
c3 c3 = null;
var test_c3 = NullGuard.Check(() => c3.p, true);
}
}
public static class NullGuard
{
public static T Check<T>(Expression<Func<T>> expression, bool canThrowNullReferenceException = false)
{
var nullGuardVisitor = new NullGuardVisitor(canThrowNullReferenceException);
var nullGuardExpression = nullGuardVisitor.Visit(expression.Body);
var nullGuardLambda = Expression.Lambda<Func<T>>(nullGuardExpression, expression.Parameters);
var value = nullGuardLambda.Compile()();
return value;
}
}
public class NullGuardVisitor : ExpressionVisitor
{
private readonly bool _canThrowNullReferenceException;
internal NullGuardVisitor(bool canThrowNullReferenceException)
{
_canThrowNullReferenceException = canThrowNullReferenceException;
}
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
// expression == null
var expressionEqualsNull = Expression.Equal(expression, Expression.Constant(null, expression.Type));
if (_canThrowNullReferenceException)
{
var nullReferenceExceptionConstructorInfo = typeof(NullReferenceException).GetConstructor(new[] { typeof(string) });
// if (expression == null) { throw new NullReferenceException() } else { node }
var result =
Expression.Block(
Expression.IfThen(
expressionEqualsNull,
Expression.Throw(Expression.New(nullReferenceExceptionConstructorInfo, Expression.Constant(node.Member.Name)))
),
node
);
return result;
}
else
{
var result = Expression.Condition(
expressionEqualsNull,
Expression.Constant(null, expression.Type),
node);
return result;
}
}
}
}