Я должен программно проверить, является ли вложенный результат свойства/функции в лямбда-выражении пустым или нет. Проблема состоит в том, что пустой указатель мог быть в любом из вложенных подсвойств.
Пример. Функция:
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 или продукта или имени мог быть пустым. Код выдаст исключение, если я попытаюсь вызвать функцию, и некоторое вложенное свойство является пустым.
Важный: Я не хочу использовать выгоду попытки для этого, потому что это имеет катастрофические последствия для выполнения отладки.
Причина этого подхода состоит в том, чтобы быстро проверить на значения, в то время как я не хочу забывать, что любой аннулирует и так исключения причины. Это удобно для создания отчетов о решениях и сетках, где пустой указатель на отчете может просто показать пустой и не имеет никаких дальнейших бизнес-правил.
связанное сообщение: не останавливайте отладчик при ТОМ исключении, когда это будет брошено и поймано
Придется разбирать выражение и оценивать каждый бит по очереди, останавливаясь при получении нулевого результата. Это ни в коем случае не было бы невозможно, но было бы достаточно много работы.
Вы уверены, что это меньше работы, чем просто поставить явные нулевые защиты в код?
.Нам определенно нужен null-безопасный оператор разыменования на C#, но до тех пор посмотрите на этот вопрос , который дает несколько другое, но в то же время аккуратное решение той же самой проблемы.
Хотя это и не ответ на этот вопрос, но самый простой способ, который я знаю, чтобы добиться такого же поведения - это передать патчи к свойствам в виде перечислений имен свойств, а не цепочки вызовов.
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" })
Есть ли причина, по которой вы не могли просто сделать следующее?
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, а остальные не будут вычисляться, так что исключений для этого не будет.
Это возможно, но я не уверен, что буду это исправлять. Вот кое-что, что может оказаться полезным: он возвращает не булевое, а листовое значение выражения, если это возможно (нулевая ссылка отсутствует).
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
), но все еще возможно настроить решение.