假设我有一些字符串:
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[]
上调用First
和Where
的具体情况下,调用的方法是Enumerable.Where
和Enumerable.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
对象,这是完全不同的故事。
然而,这是最糟糕的微优化。除非这样做数百万次,否则不会节省太多时间。即使我现在知道了这一点,我仍然会使用更清晰的阅读和理解。