为什么LINQ .Where(谓词).First()比.First(谓词)快?

daz*_*zza 69 .net c# linq performance

我正在做一些性能测试,并注意到一个LINQ表达式

result = list.First(f => f.Id == i).Property
Run Code Online (Sandbox Code Playgroud)

比...慢

result = list.Where(f => f.Id == i).First().Property
Run Code Online (Sandbox Code Playgroud)

这似乎反直觉.我原以为第一个表达式会更快,因为它可以在谓词满足后立即停止遍历列表,而我会认为.Where()表达式可能会在调用.First()结果子集之前迭代整个列表.即使后者发生短路,它也不应该比直接使用First更快,但它确实如此.

下面是两个非常简单的单元测试来说明这一点.当在TestWhereAndFirst上进行优化编译时,比.Net和Silverlight 4上的TestFirstOnly快30%.我尝试使谓词返回更多结果,但性能差异是相同的.

任何人都可以解释为什么.First(fn)慢于.Where(fn).First()?我看到一个类似的反直觉结果与之.Count(fn)相比.Where(fn).Count().

private const int Range = 50000;

private class Simple
{
   public int Id { get; set; }
   public int Value { get; set; }
}

[TestMethod()]
public void TestFirstOnly()
{
   List<Simple> list = new List<Simple>(Range);
   for (int i = Range - 1; i >= 0; --i)
   {
      list.Add(new Simple { Id = i, Value = 10 });
   }

   int result = 0;
   for (int i = 0; i < Range; ++i)
   {
      result += list.First(f => f.Id == i).Value;
   }

   Assert.IsTrue(result > 0);
}

[TestMethod()]
public void TestWhereAndFirst()
{
   List<Simple> list = new List<Simple>(Range);
   for (int i = Range - 1; i >= 0; --i)
   {
      list.Add(new Simple { Id = i, Value = 10 });
   }

   int result = 0;
   for (int i = 0; i < Range; ++i)
   {
      result += list.Where(f => f.Id == i).First().Value;
   }

   Assert.IsTrue(result > 0);
}
Run Code Online (Sandbox Code Playgroud)

arx*_*arx 50

我得到了相同的结果:第一个比第一个更快.

正如Jon指出的那样,Linq使用惰性评估,因此两种方法的性能应该(并且是)大致相似.

查看Reflector,First使用一个简单的foreach循环来遍历集合,但Where有各种迭代器专门用于不同的集合类型(数组,列表等).据推测这就是给小优势的地方.

  • 但是如果我是一个框架开发人员并且刚刚在内部实现了First(fn)作为返回Where(fn).First()它将完全像First的当前实现一样工作,除了更快!似乎是对微软的疏忽. (10认同)
  • 现在试试吧!https://dotnetfiddle.net/OrUUSG,你的答案是错误的!尝试任何组合并证明你的答案是正确的.真正的答案是可枚举缓存可用的数量比旧的List/Array迭代器更有优势,但这与快速无关. (6认同)
  • 看看https://dotnetfiddle.net/k11nX6,您会惊讶于答案是错误的. (5认同)
  • 还要将.Count(fn)与.Where(fn).Count()进行比较.后者由于使用专门的迭代器而不是.Count(fn)使用的foreach更快.使用.First(fn)和.Count(fn)之类的便利方法可以得到更简洁的代码,所以看起来是正确的do,但.Where(fn).Method()的速度要快得多.GRRRR! (4认同)