【发布时间】:2018-10-29 09:51:34
【问题描述】:
在分析我们的一个应用程序时,我们发现在一些代码中出现了一个神秘的减速现象,其中我们为一个大型集合调用 Enumerable.Single(source, predicate),该集合在集合开头附近有多个与谓词匹配的项目。
调查发现the implementation of Enumerable.Single()如下:
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
TSource result = default(TSource);
long count = 0;
// Note how this always iterates through ALL the elements:
foreach (TSource element in source) {
if (predicate(element)) {
result = element;
checked { count++; }
}
}
switch (count) {
case 0: throw Error.NoMatch();
case 1: return result;
}
throw Error.MoreThanOneMatch();
}
该实现将遍历序列的每个元素,即使多个元素已经与谓词匹配。
以下实现似乎会产生相同的结果:
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
TSource result = default(TSource);
long count = 0;
foreach (TSource element in source) {
if (predicate(element)) {
if (count == 1) // Exit loop immediately if more than one match found.
throw Error.MoreThanOneMatch();
result = element;
count++; // "checked" is no longer needed.
}
}
if (count == 0)
throw Error.NoMatch();
return result;
}
有谁知道为什么实际的实现不使用这种明显的优化?有什么我想念的吗? (我无法想象如此明显的优化会被忽略,因此肯定有一些具体的原因。)
(注意:我意识到这个问题可能会吸引意见的答案;我希望答案能够提供迭代所有元素的具体原因。如果答案实际上是“因为设计师认为这样的优化不是必要”,那么这个问题是无法回答的,我想我应该删除它......)
为了比较,看一下不带谓词的Single()的实现:
public static TSource Single<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null) {
switch (list.Count) {
case 0: throw Error.NoElements();
case 1: return list[0];
}
}
else {
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (!e.MoveNext()) throw Error.NoElements();
TSource result = e.Current;
if (!e.MoveNext()) return result;
}
}
throw Error.MoreThanOneElement();
}
在这种情况下,他们努力为IList 添加优化。
【问题讨论】:
-
优化故障路径很少值得做。
-
@Damien_The_Unbeliever 既然如此,他们为什么优化
SingleOrDefault<TSource>(this IEnumerable<TSource> source)将源转换为IList并直接检查计数? -
第二次匹配后枚举序列时如果抛出异常会怎样?早点回来会改变行为。哪个是期望的结果是值得怀疑的。
-
有趣 - 所以
Where的性能实际上比Single更好(在完整的 .NET 框架上)与Single的谓词。很高兴找到@MatthewWatson。 -
由于某种原因,很难找到 2015/2016 年的重复文件...我们只有 2011 年和 2013 年 - stackoverflow.com/questions/17743231/…。有人需要决定以哪种方式关闭 - 可能是旧版的,因为它有新的答案。