关注这个问题:
Should I use two “where” clauses or “&&” in my LINQ query?
Can or should I join two Where clauses together in a LINQ Query?
linq styling, chaining where clause vs and operator
Jon Skeet: blog post
大多数答案都表示Linq To Objects
性能受到影响的条款vs&&在单个lambda表达式中可以忽略不计,因此可以根据您的编码风格来决定使用哪种表达式。
我从查看IL程序集开始,你可以肯定地看到chaining where子句将导致Where扩展被调用2次而第二次调用的输入是第一次调用的结果。
var numbers = new List<int>() { 1, 2 ,3,4,5,6,7,8,9,10};
IEnumerable<int> query = numbers.Where(x=> x>2).Where(x => x<5);
// IL
IL_005B: ldloc.0 // numbers
IL_005C: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate3
IL_0061: brtrue.s IL_0076
IL_0063: ldnull
IL_0064: ldftn b__1
IL_006A: newobj System.Func<System.Int32,System.Boolean>..ctor
IL_006F: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate3
IL_0074: br.s IL_0076
IL_0076: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate3
IL_007B: call System.Linq.Enumerable.Where <-----------First Call
IL_0080: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate4
IL_0085: brtrue.s IL_009A
IL_0087: ldnull
IL_0088: ldftn b__2
IL_008E: newobj System.Func<System.Int32,System.Boolean>..ctor
IL_0093: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate4
IL_0098: br.s IL_009A
IL_009A: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate4
IL_009F: call System.Linq.Enumerable.Where <------------Second Call
IL_00A4: stloc.1 // query
b__1:
IL_0000: ldarg.0
IL_0001: ldc.i4.2
IL_0002: cgt
IL_0004: stloc.0 // CS$1$0000
IL_0005: br.s IL_0007
IL_0007: ldloc.0 // CS$1$0000
IL_0008: ret
b__2:
IL_0000: ldarg.0
IL_0001: ldc.i4.5
IL_0002: clt
IL_0004: stloc.0 // CS$1$0000
IL_0005: br.s IL_0007
IL_0007: ldloc.0 // CS$1$0000
IL_0008: ret
然后我在Win7 .Net 3.5和4.0
上运行一个简单的基准测试 static void Main(string[] args)
{
int size = 10000000;
Console.WriteLine("chain clauses");
RunTests(size,true);
Console.WriteLine("use and");
RunTests(size,false);
}
static void RunTests(int size, bool chainClauses)
{
for (int i = 1; i <= 10; i++)
{
if (chainClauses)
RunTestChaining(i, size);
else
RunTestAnd(i, size);
}
}
static void RunTestChaining(int depth, int size)
{
IEnumerable<string> input = Enumerable.Repeat("value", size);
switch (depth)
{
case 1:
input = input.Where(x => !x.Equals("1"));
break;
case 2:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2"));
break;
case 3:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2")).Where(x => !x.Equals("3"));
break;
case 4:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2")).Where(x => !x.Equals("3")).Where(x => !x.Equals("4"));
break;
case 5:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2")).Where(x => !x.Equals("3")).Where(x => !x.Equals("4")).Where(x => !x.Equals("5"));
break;
case 6:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2")).Where(x => !x.Equals("3")).Where(x => !x.Equals("4")).Where(x => !x.Equals("5")).Where(x => !x.Equals("6"));
break;
case 7:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2")).Where(x => !x.Equals("3")).Where(x => !x.Equals("4")).Where(x => !x.Equals("5")).Where(x => !x.Equals("6")).Where(x => !x.Equals("7"));
break;
case 8:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2")).Where(x => !x.Equals("3")).Where(x => !x.Equals("4")).Where(x => !x.Equals("5")).Where(x => !x.Equals("6")).Where(x => !x.Equals("7")).Where(x => !x.Equals("8"));
break;
case 9:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2")).Where(x => !x.Equals("3")).Where(x => !x.Equals("4")).Where(x => !x.Equals("5")).Where(x => !x.Equals("6")).Where(x => !x.Equals("7")).Where(x => !x.Equals("8")).Where(x => !x.Equals("9"));
break;
case 10:
input = input.Where(x => !x.Equals("1")).Where(x => !x.Equals("2")).Where(x => !x.Equals("3")).Where(x => !x.Equals("4")).Where(x => !x.Equals("5")).Where(x => !x.Equals("6")).Where(x => !x.Equals("7")).Where(x => !x.Equals("8")).Where(x => !x.Equals("9")).Where(x => !x.Equals("10"));
break;
}
Stopwatch sw = Stopwatch.StartNew();
var count = input.Count();
sw.Stop();
Console.WriteLine("Depth: {0} Count: {1} Time: {2}ms",
depth, count, sw.ElapsedMilliseconds);
}
static void RunTestAnd(int depth, int size )
{
IEnumerable<string> input = Enumerable.Repeat("value", size);
Func<string, bool> predicate = x => true;
switch (depth)
{
case 1:
predicate = x => !x.Equals("1");
break;
case 2:
predicate = x => !x.Equals("1") && !x.Equals("2");
break;
case 3:
predicate = x => !x.Equals("1") && !x.Equals("2") && !x.Equals("3");
break;
case 4:
predicate = x => !x.Equals("1") && !x.Equals("2") && !x.Equals("3")&&!x.Equals("3");
break;
case 5:
predicate = x => !x.Equals("1") && !x.Equals("2") && !x.Equals("3")&&!x.Equals("3")&& !x.Equals("5");
break;
case 6:
predicate = x => !x.Equals("1") && !x.Equals("2") && !x.Equals("3")&&!x.Equals("3")&& !x.Equals("5") && !x.Equals("6");
break;
case 7:
predicate = x => !x.Equals("1") && !x.Equals("2") && !x.Equals("3")&&!x.Equals("3")&& !x.Equals("5") && !x.Equals("6") && !x.Equals("7");
break;
case 8:
predicate = x => !x.Equals("1") && !x.Equals("2") && !x.Equals("3")&&!x.Equals("3")&& !x.Equals("5") && !x.Equals("6") && !x.Equals("7") && !x.Equals("8");
break;
case 9:
predicate = x => !x.Equals("1") && !x.Equals("2") && !x.Equals("3")&&!x.Equals("3")&& !x.Equals("5") && !x.Equals("6") && !x.Equals("7") && !x.Equals("8") && !x.Equals("9");
break;
case 10:
predicate = x => !x.Equals("1") && !x.Equals("2") && !x.Equals("3")&&!x.Equals("3")&& !x.Equals("5") && !x.Equals("6") && !x.Equals("7") && !x.Equals("8") && !x.Equals("9") && !x.Equals("10");
break;
}
input = input.Where(predicate);
Stopwatch sw = Stopwatch.StartNew();
var count = input.Count();
sw.Stop();
Console.WriteLine("Depth: {0} Count: {1} Time: {2}ms",
depth, count, sw.ElapsedMilliseconds);
}
结果:
// .Net 3.5 //.Net 4.0
chain clauses chain clauses
Depth: 1 Count: 10000000 Time: 181ms Depth: 1 Count: 10000000 Time: 216ms
Depth: 2 Count: 10000000 Time: 248ms Depth: 2 Count: 10000000 Time: 278ms
Depth: 3 Count: 10000000 Time: 315ms Depth: 3 Count: 10000000 Time: 347ms
Depth: 4 Count: 10000000 Time: 378ms Depth: 4 Count: 10000000 Time: 437ms
Depth: 5 Count: 10000000 Time: 443ms Depth: 5 Count: 10000000 Time: 509ms
Depth: 6 Count: 10000000 Time: 514ms Depth: 6 Count: 10000000 Time: 573ms
Depth: 7 Count: 10000000 Time: 579ms Depth: 7 Count: 10000000 Time: 649ms
Depth: 8 Count: 10000000 Time: 644ms Depth: 8 Count: 10000000 Time: 727ms
Depth: 9 Count: 10000000 Time: 978ms Depth: 9 Count: 10000000 Time: 1278ms
Depth: 10 Count: 10000000 Time: 1546ms Depth: 10 Count: 10000000 Time: 1075ms
use and use and
Depth: 1 Count: 10000000 Time: 181ms Depth: 1 Count: 10000000 Time: 202ms
Depth: 2 Count: 10000000 Time: 200ms Depth: 2 Count: 10000000 Time: 234ms
Depth: 3 Count: 10000000 Time: 228ms Depth: 3 Count: 10000000 Time: 267ms
Depth: 4 Count: 10000000 Time: 245ms Depth: 4 Count: 10000000 Time: 303ms
Depth: 5 Count: 10000000 Time: 267ms Depth: 5 Count: 10000000 Time: 335ms
Depth: 6 Count: 10000000 Time: 289ms Depth: 6 Count: 10000000 Time: 364ms
Depth: 7 Count: 10000000 Time: 312ms Depth: 7 Count: 10000000 Time: 397ms
Depth: 8 Count: 10000000 Time: 326ms Depth: 8 Count: 10000000 Time: 432ms
Depth: 9 Count: 10000000 Time: 366ms Depth: 9 Count: 10000000 Time: 462ms
Depth: 10 Count: 10000000 Time: 375ms Depth: 10 Count: 10000000 Time: 493ms
根据这些结果,性能显着下降,这表明您应该避免将Linq中的where子句链接到对象。
或者我缺少什么?
答案 0 :(得分:3)
是的,尝试避免将.Where()
条款链接在另一个之上,这是一个很好的建议,当它是一个明智的选择并且你有机会时,就像在这个微基准测试中一样。
请注意,.NET的LINQ-to-Objects实现非常智能,可以在执行此操作时为您组合谓词。它并没有它本来那么糟糕,但是运行这个花哨的链式委托并不像运行一个具有其中所有逻辑的委托一样快速或优雅。
但是,假设您有一个IEnumerable<T>
的任意实例,它可能已经或可能不是某种.Where()
方法的结果,并且您正在编写一个需要对其进行过滤的方法一些谓词。
根据这个微基准测试的结果,您是否真的要重构整个代码库,以便“避免将Linq中的where子句链接到对象”,或者您只是要再添加一个.Where()
继续你的生活?
与往常一样,如果您对具有性能问题的应用程序(意味着性能超出定义为“可接受”的范围)运行实际性能测试,结果表明链接.Where()
子句是瓶颈那么你可能更有理由尝试重新思考正在发生的事情。
另外,出于好奇,我在RunTestAnd中将重复的“3”子句更改为“4”子句,在.NET 4.5.1中的4核Windows 8.1 x64机器上运行代码(发布模式,无调试器) ,然后在将input
初始化为:
var input = Enumerable.Repeat("value", size).AsParallel();
这些结果(输出按摩为美观,但我保证这些是实际数字):
chain clauses (parallel) chain clauses (serial)
Depth: 1 Count: 10000000 Time: 284ms Depth: 1 Count: 10000000 Time: 185ms
Depth: 2 Count: 10000000 Time: 241ms Depth: 2 Count: 10000000 Time: 248ms
Depth: 3 Count: 10000000 Time: 267ms Depth: 3 Count: 10000000 Time: 308ms
Depth: 4 Count: 10000000 Time: 256ms Depth: 4 Count: 10000000 Time: 370ms
Depth: 5 Count: 10000000 Time: 371ms Depth: 5 Count: 10000000 Time: 432ms
Depth: 6 Count: 10000000 Time: 345ms Depth: 6 Count: 10000000 Time: 667ms
Depth: 7 Count: 10000000 Time: 342ms Depth: 7 Count: 10000000 Time: 569ms
Depth: 8 Count: 10000000 Time: 465ms Depth: 8 Count: 10000000 Time: 627ms
Depth: 9 Count: 10000000 Time: 434ms Depth: 9 Count: 10000000 Time: 862ms
Depth: 10 Count: 10000000 Time: 416ms Depth: 10 Count: 10000000 Time: 1235ms
use and (parallel) use and (serial)
Depth: 1 Count: 10000000 Time: 263ms Depth: 1 Count: 10000000 Time: 182ms
Depth: 2 Count: 10000000 Time: 265ms Depth: 2 Count: 10000000 Time: 217ms
Depth: 3 Count: 10000000 Time: 239ms Depth: 3 Count: 10000000 Time: 228ms
Depth: 4 Count: 10000000 Time: 255ms Depth: 4 Count: 10000000 Time: 255ms
Depth: 5 Count: 10000000 Time: 272ms Depth: 5 Count: 10000000 Time: 275ms
Depth: 6 Count: 10000000 Time: 255ms Depth: 6 Count: 10000000 Time: 295ms
Depth: 7 Count: 10000000 Time: 268ms Depth: 7 Count: 10000000 Time: 320ms
Depth: 8 Count: 10000000 Time: 268ms Depth: 8 Count: 10000000 Time: 339ms
Depth: 9 Count: 10000000 Time: 305ms Depth: 9 Count: 10000000 Time: 363ms
Depth: 10 Count: 10000000 Time: 267ms Depth: 10 Count: 10000000 Time: 386ms
这对我来说意味着,如果你确实发现你的10个.Where()
条款链是一个瓶颈,那么就没有简单的直接重构,你的具体使用场景允许它,考虑尝试PLINQ