where lambda vs. first lambda



假设我有一些字符串:

string[] strings = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };

string startsWithO = strings.First(s => s[0] == 'o');

:

string startsWithO = strings.Where(s => s[0] == 'o').First();

既然Where()被延迟了,它不应该减慢执行速度,对吗?

使用.Where(filter).First()而不是.First(filter)的性能损失通常非常小。

然而,它们是不一样的- Where生成一个新的迭代器,First可以简单地取一个元素,而First(filter)可以通过只使用一个循环和直接返回来微优化filter匹配。

因此,尽管两种方法具有相同的语义,并且都同样频繁地执行filter(只是在必要时执行),使用带有filter形参的First不需要创建中间迭代器对象,而且可能也避免了对该迭代器的一些非常简单的方法调用。

换句话说,如果你执行这样的代码数百万次,你会看到一个轻微的性能差异 -但不是很大;我绝不会为此担心。当这个微小的性能差异真的很重要时,你最好只写(非常简单的)foreach with-if语句,它是等价的,避免了LINQ中固有的额外调用和对象分配——但请记住,这是一个你很少需要的微优化。

编辑:基准测试演示效果:

这需要0.78秒:

for(int i=0;i<10*1000*1000;i++)
  Enumerable.Range(0,1000).First(n=> n > 2);
GC.Collect();

但是这需要1.41秒:

for(int i=0;i<10*1000*1000;i++)
  Enumerable.Range(0,1000).Where(n=> n > 2).First();
GC.Collect();

而普通循环更快(0.13秒):

long bla = 0;
for(int i=0;i<10*1000*1000;i++)
    for(int n=0;n<1000;n++)
        if(n > 2) { bla+=n; break; }
GC.Collect();
Console.WriteLine(bla);//avoid optimizer cheating.

请注意,这个基准测试只显示了这种极端的差异,因为我有一个微不足道的过滤器和一个非常短的不匹配前缀。

基于一些快速实验,差异似乎主要归因于所采用的代码路径的细节。因此,对于数组和List<> s,第一个变体实际上更快,可能会在.Where中对First没有的类型进行特殊大小写;对于自定义迭代器,第二个版本比预期的要快一点。

<标题>简介:

.Where(...).First()大致和.First(...)一样快——不要为了优化而选择其中一个。在一般情况下, .First(...)非常快,但在某些常见情况下它会更慢。如果你真的需要微优化,那么使用普通循环,这比两者都快。

这里没有区别。

调用Where first返回一个迭代器,该迭代器在first开始循环之前不会被使用。

如果谓词不匹配任何元素,则抛出相同的异常InvalidOperationException。

唯一的区别是代码的冗长性,所以.First不.Where应该首选

string[]上调用FirstWhere的具体情况下,调用的方法是Enumerable.WhereEnumerable.First的扩展方法。

Enumerable.Where这样做:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
{
  // null checks omitted
  if (source is TSource[]) 
     return new WhereArrayIterator<TSource>((TSource[])source, predicate); 
  //the rest of the method will not execute
}

WhereArrayIterator的构造函数只做:

public WhereArrayIterator(TSource[] source, Func<TSource, bool> predicate) {
  this.source = source; 
  this.predicate = predicate;
} 

除了创建一个迭代器之外,这里实际上什么也没做。

第一个First方法,没有谓词,这样做:

public static TSource First<TSource>(this IEnumerable<TSource> source) { 
  //null check
  IList<TSource> list = source as IList<TSource>;
  if (list != null) {
     //this branch is not taken as string[] does not implement IList<string>
     if (list.Count > 0) return list[0]; 
  }
  else { 
    //this is actually the WhereArrayIterator from before
    using (IEnumerator<TSource> e = source.GetEnumerator()) { 
      if (e.MoveNext()) 
        return e.Current;
    } 
  }
  throw Error.NoElements();
}

然而,第二个First做了这个

public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
   //null checks
   foreach (TSource element in source) {
     if (predicate(element)) return element; 
   }
   throw Error.NoMatch();
}

对于数组,它与直接线性访问一样快。
简而言之,这意味着在数组上调用First(predicate)会更快一些,虽然不是很大,但仍然可以检测到。这可能不适用于列表,当然也不适用于IQueryable对象,这是完全不同的故事。

然而,这是最糟糕的微优化。除非这样做数百万次,否则不会节省太多时间。即使我现在知道了这一点,我仍然会使用更清晰的阅读和理解。

相关内容

  • 没有找到相关文章

最新更新