我需要以编程方式检查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
答案 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#版本,请查看它。)
Dim x = person.TryGetValue(
Function(p) p.addressdetail(1).FirstOrDefault().Product.Name)