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;
}
}
}
}
It's working as it is supposed to.
Here's that same debug view with different white-spacing & row numbers:
1 .Block()
2 {
3 .If (.Block()
4 {
5 .If (.Constant<ExpressionTrees.Program+<>c__DisplayClass0_0>(ExpressionTrees.Program+<>c__DisplayClass0_0) == null)
6 {
7 .Throw .New System.NullReferenceException("c3")
8 }
9 .Else
10 {
11 .Default(System.Void)
12 };
13 .Constant<ExpressionTrees.Program+<>c__DisplayClass0_0>(ExpressionTrees.Program+<>c__DisplayClass0_0).c3
14 } == null)
15 {
16 .Throw .New System.NullReferenceException("p")
17 } .Else
18 {
19 .Default(System.Void)
20 };
21 (.Constant<ExpressionTrees.Program+<>c__DisplayClass0_0>(ExpressionTrees.Program+<>c__DisplayClass0_0).c3).p
22 }
Look at line 13: it effectively says
if ((closure class).c3 == null) throw new NullReferenceException("p")
. Whereas your first check (line 5) effectively says if ((closure class) == null) throw new NullReferenceException("c3")
. The problem is in the misleading exception message more than anything.