假设我有一些字符串:
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()被推迟,它不应该减慢执行速度,对吗?
答案 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
上调用Where
和string[]
,调用的方法是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
个对象,这是一个完全不同的故事。
然而,这是最优化的微优化。除非这样做数百万次,否则不会节省太多秒。即使我现在知道这一点,我仍然会使用更清楚的东西来阅读和理解。