使用表达式树通过单个属性比较对象网络InvalidOperationException

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

我希望能够创建一个语句,该语句从existingItems集合中获取与incomingItem的propertyNameToCompareOn值匹配的第一项。

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

DetectDifferences(List incomingItems, List 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>(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或类似的实现,如果它可以帮助。 我可以用反思做到这一点。 我确实需要尽可能快地进行此操作,但允许为多种类型调用它,因此可以选择表达式树。

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

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

你可以这样使用;

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

让你缺少一条线

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

请注意,==仅适用于像int这样的值类型; 小心比较对象。

这里certificate了lambda方法更快;

  static void Main(string[] args) { var l1 = new List { }; for(var i = 0; i < 10000000; i++) { l1.Add(new Foo { Name = "x" + i.ToString() }); } var propertyName = nameof(Foo.Name); var lambda = MakeLambda(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),如果效率是这里的驱动因素,那应该是下一个考虑因素。

使用表达式树时,首先要在实际代码中了解您想要做什么,这一点很重要。

我总是首先写出(在静态代码中)使用真正的C#lambda语法得到的表达式。

根据您的描述,您声明的目标是您应该能够(动态地)查找某种类型T属性,以便进行某种快速比较。 如果TTProperty在编译时都是已知的,你会怎么写呢? 我怀疑它看起来像这样:

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

我们马上就可以看到你的Expression错了。 你不需要一个输入T,你需要两个!

为什么你也得到InvalidOperationException也应该是显而易见的。 您从未向lambda表达式提供任何参数,仅提供正文。 上面,’first’和’second’是提供给lambda的参数。 您还需要将它们提供给Expression.Lambda()调用。

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

这只是为Expression.Lambda(Expression, ParameterExpression[])使用Expression.Lambda(Expression, ParameterExpression[])重载。 每个ParameterExpression都是正文中使用的参数。 而已。 如果要实际调用它,请不要忘记.Compile()将表达式转换为委托。

当然,这并不意味着你的技术必然会很快。 如果你使用花哨的表达式树来比较两个列表和一个朴素的O(n ^ 2)方法,那就无所谓了。