Как я знаю, когда лямбда-выражение является пустым

Я должен программно проверить, является ли вложенный результат свойства/функции в лямбда-выражении пустым или нет. Проблема состоит в том, что пустой указатель мог быть в любом из вложенных подсвойств.

Пример. Функция:

 public static bool HasNull(this T someType, Expression> 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 или улицы, или invoicelist или продукта или имени мог быть пустым. Код выдаст исключение, если я попытаюсь вызвать функцию, и некоторое вложенное свойство является пустым.

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

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

связанное сообщение: не останавливайте отладчик при ТОМ исключении, когда это будет брошено и поймано

5
задан Community 23 May 2017 в 12:01
поделиться

5 ответов

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

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

.
1
ответ дан 14 December 2019 в 19:15
поделиться

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

1
ответ дан 14 December 2019 в 19:15
поделиться

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

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

Затем разобрать эти перечисления по кругу или рекурсивно, используя рефлексию. Есть некоторые недостатки, такие как потеря смысла intelly и статическая проверка имен, но они очень просты в реализации, что может перевесить их в некоторых случаях.

Вместо записи

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

вам придется написать

person.HasNull(new string[] { "addressdetails", "0", "Street" })
0
ответ дан 14 December 2019 в 19:15
поделиться

Есть ли причина, по которой вы не могли просто сделать следующее?

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, а остальные не будут вычисляться, так что исключений для этого не будет.

0
ответ дан 14 December 2019 в 19:15
поделиться

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

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), но все еще возможно настроить решение.

3
ответ дан 14 December 2019 в 19:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: