Как узнать, когда лямбда-выражение равно нулю - PullRequest
5 голосов
/ 24 декабря 2009

Мне нужно программно проверить, является ли вложенное свойство / функция результата в лямбда-выражении нулевым или нет. Проблема заключается в том, что значение 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, или product, или name могут иметь значение null. Код выдает исключение, если я попытаюсь вызвать функцию, а какое-то вложенное свойство имеет значение null.

Важное замечание: Я не хочу использовать для этого попытку try, потому что это губительно для производительности отладки.

Причиной такого подхода является быстрая проверка значений, в то время как я не хочу забывать никакие нули и поэтому вызываю исключения. Это удобно для создания отчетов и решений, в которых значение NULL в отчете может быть пустым и не имеет бизнес-правил.

связанный пост: Не останавливайте отладчик при ЭТОЙ исключительной ситуации, когда он брошен и пойман

Ответы [ 6 ]

3 голосов
/ 24 декабря 2009

Возможно, но я не уверен, что рекомендую это. Вот кое-что, что вы можете найти полезным: оно не возвращает логическое значение, а вместо этого, конечное значение выражения, если это возможно (без нулевой ссылки).

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 голос
/ 28 декабря 2009

Нам определенно нужен нуль-безопасный оператор разыменования в C #, но до тех пор пока не рассмотрим этот вопрос , который дает несколько иное, но и аккуратное решение той же проблемы.

1 голос
/ 24 декабря 2009

Вам придется разбирать выражение и оценивать каждый бит по очереди, останавливаясь, когда вы получите нулевой результат. Это было бы невозможно ни в коем случае, но это было бы довольно много работы.

Вы уверены, что это менее трудоемко, чем просто вставлять в код явные нулевые элементы защиты?

0 голосов
/ 11 августа 2010

Я писал в блоге, он работает в VB.NET (я перевел на C #, но я не тестировал версию C #, проверьте это).

Оформить заказ: Как бы вы проверили car.Finish.Style.Year.Model.Vendor.Contacts.FirstOrDefault (). FullName для null? : -)

Dim x = person.TryGetValue(
    Function(p) p.addressdetail(1).FirstOrDefault().Product.Name)
0 голосов
/ 24 декабря 2009

Хотя это не ответ на этот конкретный вопрос, самый простой способ, которым я знаю, чтобы добиться того же поведения, - это передавать патчи свойствам как перечислимые имена свойств вместо цепочек вызовов.

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" })
0 голосов
/ 24 декабря 2009

Любая причина, почему вы не могли просто сделать следующее?

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;

Я думаю, что эти простые логические выражения были бы наилучшим способом, они работают из-за условной оценки ... При объединении (&&) терминов вместе первый ложный термин вернет false и остановит оценку остальных, поэтому вы не получите никаких исключений при этом.

...