【发布时间】:2016-01-13 21:37:51
【问题描述】:
我一直在尝试创建一个自定义 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;
}
}
}
}
【问题讨论】:
-
为什么要显式检查 null 并抛出 NRE?如果你不想传播空值,而只是想抛出,你需要做的就是保持表达式不变,当值为空时执行表达式的效果将简单导致 NRE 被抛出。您只是在复制 C# 已经为您执行的空值检查。
-
附带说明,处理值类型使解决这个问题变得更加复杂。目前,如果您的表达式导致不可为空的值,您仍在尝试在传播空路径中返回
null,这将不起作用。你需要在那里使用Nullable<T>。 -
@Servy 我这样做是因为我想向 NRE 添加一条有意义的消息,而不仅仅是一般的异常。目前它只是成员名称,但后来它应该包含到第一个
null的完整路径。 -
@Servy 附注到附注。我在你的回答here 中读到了它,实际上这是基于你的代码;-) 我刚刚开始学习表达式树,这些是我第一次尝试创建有用的东西......但是这个 邪恶的 异常不会让我 ;-) 我想一旦我设法使异常抛出工作,我就会得到可为空的值。
-
对于您的第一点,这是有道理的。顺便说一句,只是想确保你知道你最终需要解决它。首先解决简单的问题当然是个好主意。
标签: c# expression-trees expressionvisitor