我怎么知道lambda表达式何时为null

时间:2009-12-24 11:10:19

标签: c# exception null lambda

我需要以编程方式检查lambda表达式中的嵌套属性/函数结果是否为null。问题是null可以在任何嵌套的子属性中。

实施例。功能是:

 public static bool HasNull<T, Y>(this T someType, Expression<Func<T, Y>> input)
    {
      //Determine if expression has a null property
    }  

使用:

person.HasNull(d=>d.addressdetails.Street)
person.HasNull(d=>d.addressdetails[1].Street)
person.HasNull(d=>d.addressdetails.FirstOrDefault().Street)
person.HasNull(d=>d.InvoiceList.FirstOrDefault().Product.Name)

在任何示例中,addressdetails或street,或者invoicelist或者产品或名称都可以为null。如果我尝试调用该函数并且某些嵌套属性为null,则代码将抛出异常。

重要提示:我不想使用try catch,因为这对于调试性能来说是灾难性的。

这种方法的原因是快速检查值,而我不想忘记任何空值,因此导致异常。这对报告解决方案和网格非常方便,报告中的空值只能显示为空,并且没有进一步的业务规则。

相关帖子:Don't stop debugger at THAT exception when it's thrown and caught

6 个答案:

答案 0 :(得分:3)

有可能,但我不确定我会推荐它。以下是您可能会发现有用的内容:它不返回布尔值,而是返回表达式的叶值(如果可能)(无空引用)。

public static class Dereferencer
{
    private static readonly MethodInfo safeDereferenceMethodInfo 
        = typeof (Dereferencer).GetMethod("SafeDereferenceHelper", BindingFlags.NonPublic| BindingFlags.Static);


    private static TMember SafeDereferenceHelper<TTarget, TMember>(TTarget target,
                                                            Func<TTarget, TMember> walker)
    {
        return target == null ? default(TMember) : walker(target);
    }

    public static TMember SafeDereference<TTarget, TMember>(this TTarget target, Expression<Func<TTarget, TMember>> expression)
    {
        var lambdaExpression = expression as LambdaExpression;
        if (lambdaExpression == null)
            return default(TMember);

        var methodCalls = new Queue<MethodCallExpression>();
        VisitExpression(expression.Body, methodCalls);
        var callChain = methodCalls.Count == 0 ? expression.Body : CombineMethodCalls(methodCalls);
        var exp = Expression.Lambda(typeof (Func<TTarget, TMember>), callChain, lambdaExpression.Parameters);
        var safeEvaluator = (Func<TTarget, TMember>) exp.Compile();

        return safeEvaluator(target);
    }

    private static Expression CombineMethodCalls(Queue<MethodCallExpression> methodCallExpressions)
    {
        var callChain = methodCallExpressions.Dequeue();
        if (methodCallExpressions.Count == 0)
            return callChain;

        return Expression.Call(callChain.Method, 
                               CombineMethodCalls(methodCallExpressions), 
                               callChain.Arguments[1]);
    }

    private static MethodCallExpression GenerateSafeDereferenceCall(Type targetType,
                                                                    Type memberType,
                                                                    Expression target,
                                                                    Func<ParameterExpression, Expression> bodyBuilder)
    {
        var methodInfo = safeDereferenceMethodInfo.MakeGenericMethod(targetType, memberType);
        var lambdaType = typeof (Func<,>).MakeGenericType(targetType, memberType);
        var lambdaParameterName = targetType.Name.ToLower();
        var lambdaParameter = Expression.Parameter(targetType, lambdaParameterName);
        var lambda = Expression.Lambda(lambdaType, bodyBuilder(lambdaParameter), lambdaParameter);
        return Expression.Call(methodInfo, target, lambda);
    }

    private static void VisitExpression(Expression expression, 
                                        Queue<MethodCallExpression> methodCallsQueue)
    {
        switch (expression.NodeType)
        {
            case ExpressionType.MemberAccess:
                VisitMemberExpression((MemberExpression) expression, methodCallsQueue);
                break;
            case ExpressionType.Call:
                VisitMethodCallExpression((MethodCallExpression) expression, methodCallsQueue);
                break;
        }
    }

    private static void VisitMemberExpression(MemberExpression expression, 
                                              Queue<MethodCallExpression> methodCallsQueue)
    {
        var call = GenerateSafeDereferenceCall(expression.Expression.Type,
                                               expression.Type,
                                               expression.Expression,
                                               p => Expression.PropertyOrField(p, expression.Member.Name));

        methodCallsQueue.Enqueue(call);
        VisitExpression(expression.Expression, methodCallsQueue);
    }

    private static void VisitMethodCallExpression(MethodCallExpression expression, 
                                                  Queue<MethodCallExpression> methodCallsQueue)
    {
        var call = GenerateSafeDereferenceCall(expression.Object.Type,
                                               expression.Type,
                                               expression.Object,
                                               p => Expression.Call(p, expression.Method, expression.Arguments));

        methodCallsQueue.Enqueue(call);
        VisitExpression(expression.Object, methodCallsQueue);
    }
}

你可以这样使用它:

var street = person.SafeDereference(d=>d.addressdetails.Street);
street = person.SafeDereference(d=>d.addressdetails[1].Street);
street = person.SafeDereference(d=>d.addressdetails.FirstOrDefault().Street);
var name = person.SafeDereference(d=>d.InvoiceList.FirstOrDefault().Product.Name);

警告:这未经过全面测试, 应该使用方法和属性,但可能不会使用表达式中的扩展方法。

编辑:好的,它现在无法处理扩展方法(例如FirstOrDefault),但仍然可以调整解决方案。

答案 1 :(得分:1)

你必须将表达式分开并依次评估每个位,当你得到一个空结果时停止。这绝不是不可能的,但这将是相当多的工作。

你确定这不仅仅是在代码中添加显式空值守卫吗?

答案 2 :(得分:1)

我们肯定需要在C#中使用null安全的解除引用运算符,但在此之前请查看this question,它为同一问题提供了略微不同但又整洁的解决方案。

答案 3 :(得分:0)

为什么你不能只做以下事情?

bool result;
result = addressdetails != null && addressdetails.Street != null;
result = addressdetails != null && addressdetails.Count > 1 && addressdetails[1].Street != null;
result = addressdetails != null && addressdetails.FirstOrDefault() != null && addressdetails.FirstOrDefault().Street != null;
result = addressdetails != null && addressdetails.FirstOrDefault() != null && addressdetails.FirstOrDefault().Product != null && addressdetails.FirstOrDefault().Product.Name != null;

我认为这些简单的布尔表达式是最好的方法,因为条件评估它们起作用...当和(&amp;&amp;)条款放在一起时,第一个假句将返回false并阻止其余的评估,所以你不会得到任何例外。

答案 4 :(得分:0)

虽然这不是这个问题的答案,但我知道实现相同行为的最简单方法是将pathes作为属性名称的枚举而不是调用链传递给属性。

public static bool HasNull<T, Y>(this T someType, IEnumerable<string> propertyNames)

然后使用反射解析圆圈中的可枚举或递归。有一些缺点,如失去智能感和静态名称检查,但很容易实现,在某些情况下可能超过它们。

而不是写

person.HasNull(d=>d.addressdetails.FirstOrDefault().Street)

你必须写

person.HasNull(new string[] { "addressdetails", "0", "Street" })

答案 5 :(得分:0)

我在博客上说,它适用于VB.NET(我翻译成C#,但我没有测试C#版本,请查看它。)

结帐:How would you check car.Finish.Style.Year.Model.Vendor.Contacts.FirstOrDefault().FullName for null? :-)

Dim x = person.TryGetValue(
    Function(p) p.addressdetail(1).FirstOrDefault().Product.Name)