使用表达式树通过单个 属性 网络比较对象 InvalidOperationException
using expression trees to compare objects by a single property nets InvalidOperationException
我正在尝试使用表达式树,因为根据描述,这似乎是最正确的(性能好、可配置的)方法。
我希望能够编写一个语句,从 existingItems 集合中获取与 incomingItem 的 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.)
}
}
在上面标记的行,我得到一个 "Incorrect number of parameters supplied for lambda declaration" InvalidOperationException。在这一点上,我只是在网上胡扯,我真的不知道这想要什么。 VS 可以用一堆重载来填满我的屏幕,并且 none 的示例从 MSDN/SO 上的文章中可以理解。
PS - 如果有帮助,我真的不想要 IComparer 或类似的实现。我可以通过反思做到这一点。我确实需要尽可能快地完成它,但允许它被多种类型调用,因此表达式树的选择。
使用表达式树时,重要的是首先了解在实际代码中您想要做什么。
我总是首先(在静态代码中)用真正的 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.Lambda(Expression, ParameterExpression[])
重载。每个 ParameterExpression
是正文中使用的参数。而已。如果您想实际调用它,请不要忘记 .Compile()
您的表达式到委托中。
当然这并不意味着你的技术一定很快。如果您使用花哨的表达式树来比较两个列表和简单的 O(n^2) 方法,那没关系。
这是一个制作属性访问表达式的方法;
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),如果效率是这里的驱动因素,那可能应该是下一个考虑因素。
我正在尝试使用表达式树,因为根据描述,这似乎是最正确的(性能好、可配置的)方法。
我希望能够编写一个语句,从 existingItems 集合中获取与 incomingItem 的 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.)
}
}
在上面标记的行,我得到一个 "Incorrect number of parameters supplied for lambda declaration" InvalidOperationException。在这一点上,我只是在网上胡扯,我真的不知道这想要什么。 VS 可以用一堆重载来填满我的屏幕,并且 none 的示例从 MSDN/SO 上的文章中可以理解。
PS - 如果有帮助,我真的不想要 IComparer 或类似的实现。我可以通过反思做到这一点。我确实需要尽可能快地完成它,但允许它被多种类型调用,因此表达式树的选择。
使用表达式树时,重要的是首先了解在实际代码中您想要做什么。
我总是首先(在静态代码中)用真正的 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.Lambda(Expression, ParameterExpression[])
重载。每个 ParameterExpression
是正文中使用的参数。而已。如果您想实际调用它,请不要忘记 .Compile()
您的表达式到委托中。
当然这并不意味着你的技术一定很快。如果您使用花哨的表达式树来比较两个列表和简单的 O(n^2) 方法,那没关系。
这是一个制作属性访问表达式的方法;
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),如果效率是这里的驱动因素,那可能应该是下一个考虑因素。