为什么不抛出表达式树中最里面的异常?
Why is the innermost exception in an expression tree not thrown?
我一直在尝试创建一个自定义 ExpressionVisitor
,它会生成一个表达式(可选)在第一个 null
值上抛出 NullReferenceException
。表达式的 DebugView
对我来说看起来不错,但它不能像 exptecte (对我而言)起作用。我以为它会先抛出
.Throw .New System.NullReferenceException("c3")
因为测试变量是 null
但这个变量被抛出
.Throw .New System.NullReferenceException("p")
我不明白为什么它向后执行语句。不是应该先执行最里面的If
吗?
调试视图:
.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
}
我的完整测试代码:
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;
}
}
}
}
一切正常。
这是具有不同 white-spacing 和行号的相同调试视图:
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 }
看看第 13 行:它实际上说
if ((closure class).c3 == null) throw new NullReferenceException("p")
。而您的第一次检查(第 5 行)有效地显示 if ((closure class) == null) throw new NullReferenceException("c3")
。问题出在误导性异常消息中。
您的问题在以下代码段中:
Expression.Throw(Expression.New(nullReferenceExceptionConstructorInfo,
Expression.Constant(node.Member.Name)))
假设此成员访问权限为 a.b
。您正在检查 a
,然后抛出异常并显示消息 b
is null ,即使您刚刚检查了 a
.
如果您想说 a
为空,而不是说您无法访问 b
因为其容器为空,则需要在异常消息中使用 node.Expression
.
我一直在尝试创建一个自定义 ExpressionVisitor
,它会生成一个表达式(可选)在第一个 null
值上抛出 NullReferenceException
。表达式的 DebugView
对我来说看起来不错,但它不能像 exptecte (对我而言)起作用。我以为它会先抛出
.Throw .New System.NullReferenceException("c3")
因为测试变量是 null
但这个变量被抛出
.Throw .New System.NullReferenceException("p")
我不明白为什么它向后执行语句。不是应该先执行最里面的If
吗?
调试视图:
.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
}
我的完整测试代码:
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;
}
}
}
}
一切正常。
这是具有不同 white-spacing 和行号的相同调试视图:
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 }
看看第 13 行:它实际上说
if ((closure class).c3 == null) throw new NullReferenceException("p")
。而您的第一次检查(第 5 行)有效地显示 if ((closure class) == null) throw new NullReferenceException("c3")
。问题出在误导性异常消息中。
您的问题在以下代码段中:
Expression.Throw(Expression.New(nullReferenceExceptionConstructorInfo,
Expression.Constant(node.Member.Name)))
假设此成员访问权限为 a.b
。您正在检查 a
,然后抛出异常并显示消息 b
is null ,即使您刚刚检查了 a
.
如果您想说 a
为空,而不是说您无法访问 b
因为其容器为空,则需要在异常消息中使用 node.Expression
.