lambda与第一个lambda的关系

时间:2013-01-10 16:00:57

标签: .net linq c#-3.0

假设我有一些字符串:

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()被推迟,它不应该减慢执行速度,对吗?

3 个答案:

答案 0 :(得分:12)

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

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

因此,虽然两种方法都具有相同的语义,并且两者都经常执行filter(仅在必要时经常执行),但使用带有First参数的filter不需要创建一个中间迭代器对象和可能也避免了对迭代器的一些非常简单的方法调用。

换句话说,如果您执行此类代码数百万次,您会看到轻微的性能差异 - 但没有什么大不了的;我从不担心它。每当这个微小的性能差异真正重要时,你只需要编写(非常简单的)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(...)非常快,但在一些常见情况下它会更慢。如果你真的需要微优化,那么使用比任何一个都快的普通循环。

答案 1 :(得分:1)

这里没有区别。

调用Where首先返回一个在第一次开始循环之前未使用的迭代器。

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

唯一的区别是代码的详细程度,所以。首先没有。应该首选

答案 2 :(得分:1)

在特定情况下,在First上调用Wherestring[],调用的方法是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个对象,这是一个完全不同的故事。

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