因此,第一步是创建一个表达式访问者。这让我们可以找到特定表达式中的所有成员访问。这给我们留下了一个问题:如何处理每个成员的访问权限。
因此,第一件事是递归访问正在访问成员的表达式。从那里,我们可以使用Expression.Condition
创建一个条件块,将已处理的基础表达式与null
,并返回null
如果为 true,则为原始起始表达式;如果不是,则为原始起始表达式。
请注意,我们需要为成员和方法调用提供实现,但每个的过程基本相同。
我们还将添加一个检查,以便基础表达式是null
(也就是说,没有实例,它是一个静态成员)或者如果它是一个不可为空的类型,我们只使用基本行为。
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<>);
}
}
然后我们可以创建一个扩展方法以使其更容易调用:
public static Expression PropogateNull(this Expression expression)
{
return new MemberNullPropogationVisitor().Visit(expression);
}
以及接受 lambda(而不是任何表达式)并且可以返回已编译委托的一个:
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();
}
请注意,为了支持所访问的成员解析为不可为空值的情况,我们将更改这些表达式的类型以将它们提升为可为空,使用MakeNullable
。这是最终表达式的问题,因为它需要是Func<T>
,并且如果T
也没有解除。因此,虽然它非常不理想(理想情况下你永远不会使用不可为 null 的值来调用此方法)T
,但在 C# 中没有很好的方法来支持这一点)如有必要,我们使用该类型的默认值来合并最终值。
(您可以简单地修改它以接受接受参数的 lambda,并传入一个值,但您也可以轻松地关闭该参数,所以我认为没有真正的理由这样做。)
还值得指出的是,在 C# 6.0 中,当它实际发布时,我们将拥有一个实际的空传播运算符 (?.
),使这一切变得非常不必要。你将能够写:
if(a?.b?.c?.d?.e?.f != null)
Console.Write("ok");
并且具有您正在寻找的语义。