迭代表达式树以检查null

时间:2016-06-30 10:26:33

标签: c# .net expression expression-trees

我真的很难写一个泛型类,需要通过调用实际对象来检查传递表达式的所有成员是否都为null。

我正在调用方法:

new TestClass<Class1, Class2>.IsAnyMemberNull(x => x.Property1.List1, object1);

,方法如下:

public bool IsAnyMemberNull(Expression<Func<T, IList<TM>>> listExpression, T entityDb)
{
    var expressionsToCheck = new List<MemberExpression>();
    var expression = listExpression.Body as MemberExpression;
    while (expression != null)
    {
        expressionsToCheck.Add(expression);
        expression = expression.Expression as MemberExpression;
    }

    for (var i = expressionsToCheck.Count - 1; i >= 0; i--)
    {
        var objectMember = Expression.Convert(expressionsToCheck[i], typeof (object));
        var lambda = Expression.Lambda<Func<T, object>>(objectMember);
        var value = lambda.Compile().Invoke(entityDb);
        if (value == null)
            return true;
    }

    return false;
}

执行时,我得到例外:

  

为lambda声明提供的参数数量不正确

任何想法,我做错了什么?

2 个答案:

答案 0 :(得分:1)

虽然可以使代码正常工作,但是构建和编译正确的lambdas,使用重复编译的lambdas来实现空值检查是一种代价高昂的过度杀伤

通常,如此随意地使用lambdas(为链中的每个属性和单个对象编译lambda)会带来显着的性能影响。我已经运行了一些测试,并且在我的计算机上执行此方法1000次,对于给定的对象产生~300-700毫秒的时间(取决于链中的属性数)。 Dunno您处理了多少实体,但这不是一个好兆头,并且可以提供更好的替代品。请进一步阅读......

问题是,你用它做什么?你的那种方法让我想起了null-conditional operators。一般来说,如果你:

  • 只想检查属性链中的任何属性是否为null
  • 使用C#6
  • 让所有参数链lambda(如x => x.Property1.List1)在运行时已知

然后你可以完全取消所有IsAnyMemberNull方法,以支持以下内容:

object1.Property1.List1 == null

更简洁,无需其他方法。我运行了100万次,它仍然在~23ms的时间内。这意味着它比创建所有这些lambdas快几十万。

如果由于某种原因无法使用null-coalescing运算符(特别是在动态构建表达式时),您可能会决定使用Field / Property反射。

我冒昧地删除所有泛型类包装,转而使用泛型方法。从您的用法示例来看,通用类的唯一目的似乎是使用类'泛型类型参数访问特定方法。这意味着必须为方法的每个变体制作和存储一个新类,没有明显的理由,如果我没有弄错的话,应用程序的剩余时间。在这类情况下,特定类中的泛型方法通常优先于泛型类中的特定方法。

另外,我删除了IList,因为我认为没有理由要求最后一个参数是IList类型服务于函数的目的;它只会限制其适用性而没有明显的收益。

总体而言,结果如下:

public bool IsAnyMemberNull<TEntity, TMember>(Expression<Func<TEntity, TMember>> paramChain, TEntity entityDb)
{
    var expressionsToCheck = new List<MemberExpression>();
    var expression = paramChain.Body as MemberExpression;
    while (expression != null)
    {
        expressionsToCheck.Add(expression);
        expression = expression.Expression as MemberExpression;
    }

    object value = entityDb;
    for (var i = expressionsToCheck.Count - 1; i >= 0; i--)
    {
        var member = expressionsToCheck[i].Member;
        if (member is PropertyInfo) value = (member as PropertyInfo).GetValue(value);
        else if (member is FieldInfo) value = (member as FieldInfo).GetValue(value);
        else throw new Exception(); // member generally should be a property or field, shouldn't it?

        if (value == null)
            return true;
    }

    return false;
}

运行~1000次后,耗时约4-6ms;比lambdas好50-100倍,尽管无效传播仍然占主导地位。

调用如下(假设它仍然驻留在TestClass中,它不需要):

new TestClass().IsAnyMemberNull<Class1,Class2>(x => x.Property1.List1, object1);

(由于类型推断,可能不需要Class1和Class2)

希望这会有所帮助。这不完全是你要求的,但我担心所有这些lambda产生你会遇到严重的性能问题;特别是如果每​​个请求多次使用此代码。

答案 1 :(得分:0)

你在lambda表达式创建时遇到问题 - 它比你想象的要简单。您应该使用原始表达式参数为每个lambda构建expressionToCheck

for (var i = expressionsToCheck.Count - 1; i >= 0; i--)
{     
    var lambda = Expression.Lambda<Func<T, object>>(expressionsToCheck[i], listExpression.Parameters);
    var value = lambda.Compile().Invoke(entityDb);
    if (value == null)
        return true;
}