使用表达式树通过单个属性比较对象会导致 InvalidOperationException

问题描述 投票:0回答:2

我正在尝试使用表达式树,因为根据描述,这似乎是最正确的(高性能、可配置)方法。

我希望能够制作一个语句,从现有项目集合中获取与传入项目的 propertyNameToCompareOn 值匹配的第一个项目。

我有一个具有以下签名和模拟代码体的方法...

DetectDifferences<T>(List<T> incomingItems, List<T> existingItems)
{
  var propertyNameToCompareOn = GetThisValueFromAConfigFile(T.FullName());

  //does this belong outside of the loop?
  var leftParam = Expression.Parameter(typeof(T), "left");
  var leftProperty = Expression.Property(leftParam, identField);
  var rightParam = Expression.Parameter(typeof(T), "right");
  var rightProperty = Expression.Property(rightParam, identField);
  //this throws the error
  var condition = Expression.Lambda<Func<T, bool>>(Expression.Equal(leftProperty, rightProperty));

  foreach (var incomingItem in incomingItems) //could be a parallel or something else. 
  {
     // also, where am I supposed to provide incomingItem to this statement?
     var existingItem = existingItems.FirstOrDefault(expression/condition/idk);

     // the statement for Foo would be something like
     var existingFoos = exsistingItems.FirstOrDefault(f => f.Bar.Equals(incomingItem.Bar);

     //if item does not exist, consider it new for persistence
     //if item does exist, compare a configured list of the remaining properties between the 
     // objects. If they are all the same, report no changes. If any
     // important property is different, capture the differences for 
     // persistence. (This is where precalculating hashes seems like the 
     // wrong approach due to expense.)
  }
}

在上面标记的行中,我收到“为 lambda 声明提供的参数数量不正确”InvalidOperationException。此时我只是从网络上收集材料,我真的不知道这想要什么。 VS 有一堆重载可以填满我的屏幕,但 MSDN/SO 上的文章中的示例都没有意义。

PS - 如果可以提供帮助的话,我真的不想要 IComparer 或类似的实现。我可以通过反思来做到这一点。我确实需要使其尽可能快,但允许为多种类型调用它,因此选择表达式树。

c# expression-trees
2个回答
3
投票

使用表达式树时,首先在实际代码中理解您想要做什么非常重要。

我总是首先使用真正的 C# lambda 语法(以静态代码)写出结果表达式的样子。

根据您的描述,您声明的目标是您应该能够(动态)查找

T
类型的某些属性,以进行某种快速比较。如果
T
TProperty
在编译时都已知,你会怎么写? 我怀疑它看起来像这样:

Func<Foo, Foo, bool> comparer = (Foo first, Foo second) => 
    first.FooProperty == second.FooProperty;

我们立刻就可以看出您的

Expression
是错误的。您不需要一个输入 T,您需要两个!

你也应该明白为什么你会得到

InvalidOperationException
。您从未向 lambda 表达式提供任何参数,仅提供主体。上面,“first”和“second”是提供给 lambda 的参数。您还需要将它们提供给
Expression.Lambda()
通话。

var condition = Expression.Lambda<Func<T,T, bool>>(
    Expression.Equal(leftProperty, rightProperty),
    leftParam,
    rightParam);

这只是使用

Expression.Lambda(Expression, ParameterExpression[])
重载
Expression.Lambda
。每个
ParameterExpression
都是主体中使用的参数。就是这样。如果您想实际调用它,请不要忘记将您的表达式
.Compile()
放入委托中。

当然这并不意味着你的技术一定很快。如果您使用奇特的表达式树通过简单的 O(n^2) 方法来比较两个列表,那么这并不重要。


1
投票

这是制作属性访问表达式的方法;

    public static Expression<Func<T, object>> MakeLambda<T>(string propertyName)
    {
        var param = Expression.Parameter(typeof(T));
        var propertyInfo = typeof(T).GetProperty(propertyName);
        var expr = Expression.MakeMemberAccess(param, propertyInfo);
        var lambda = Expression.Lambda<Func<T, object>>(expr, param);
        return lambda;
    } 

你可以这样使用;

 var accessor = MakeLambda<Foo>("Name").Compile();
 accessor(myFooInstance); // returns name

弥补你缺失的线条

 var existingItem = existingItems.FirstOrDefault(e => accessor(e) == accessor(incomingItem));

请注意 == 仅适用于整数等值类型;小心比较对象。

这里证明 lambda 方法要快得多;

    static void Main(string[] args)
    {
        var l1 = new List<Foo> { };
        for(var i = 0; i < 10000000; i++)
        {
            l1.Add(new Foo { Name = "x" + i.ToString() });
        }

        var propertyName = nameof(Foo.Name);
        var lambda = MakeLambda<Foo>(propertyName);
        var f = lambda.Compile();

        var propertyInfo = typeof(Foo).GetProperty(nameof(Foo.Name));

        var sw1 = Stopwatch.StartNew();
        foreach (var item in l1)
        {
            var value = f(item);
        }
        sw1.Stop();


        var sw2 = Stopwatch.StartNew();
        foreach (var item in l1)
        {
            var value = propertyInfo.GetValue(item);
        }
        sw2.Stop();

        Console.WriteLine($"{sw1.ElapsedMilliseconds} vs {sw2.ElapsedMilliseconds}");



    }

正如有人也指出的那样,OP 中的双循环是 O(N^2),如果效率是这里的驱动因素,那么这可能应该是下一个考虑因素。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.