LINQ Enumerable策略比较:Any、Count、SingleOrDefault
LINQ Enumerable strategy comparison: Any, Count, SingleOrDefault
我们在 Code Review of LINQ Enumerable 进行了一些反省,发现了迭代 IEnumerable<TSource>
的不同策略,从而为我们提供了一些关于它的信息。
任意
public static bool Any<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
foreach (TSource element in source) {
if (predicate(element)) return true;
}
return false;
}
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (e.MoveNext()) return true;
}
return false;
}
计数
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
ICollection<TSource> collectionoft = source as ICollection<TSource>;
if (collectionoft != null) return collectionoft.Count;
ICollection collection = source as ICollection;
if (collection != null) return collection.Count;
int count = 0;
using (IEnumerator<TSource> e = source.GetEnumerator()) {
checked {
while (e.MoveNext()) count++;
}
}
return count;
}
单一或默认
public static TSource SingleOrDefault<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
TSource result = default(TSource);
long count = 0;
foreach (TSource element in source) {
if (predicate(element)) {
result = element;
checked { count++; }
}
}
switch (count) {
case 0: return default(TSource);
case 1: return result;
}
throw Error.MoreThanOneMatch();
}
如您所见,使用了 3 种不同的策略。
- 任意:迭代枚举器,提前退出
- Count:迭代枚举数,除非是ICollection,调用Count
- SingleOrDefault:迭代枚举器,不提前退出
一些观察:
- 'Any()' 也可以针对 ICollection
进行优化
- 'SingleOrDefault' 也可以优化为提前退出
- none 的方法关心 IReadOnlyCollection,即使它也有一个 属性 'Count'
问题:
- 每种策略的优缺点是什么?
- LINQ 的实现是这样的吗?
好吧,前两种方法是不言自明的,不是吗?它们以尽快停止的方式进行优化,并检查类型是否具有 Count
属性 以避免循环。
但是SingleOrDefault
with predicate 确实有一个奇怪的实现。它可以在第二个匹配项目处停止,因为很明显必须抛出 InvalidOperationException
(Single...
而不是 First...
确保最多有 1 个项目)。但它会检查每个项目并计算匹配项。
version without predicate有这个优化。
所以问题是:到底是什么?而且似乎 is really a bug 不会被修复,因为它只会降低错误情况下的性能。我简直不敢相信。
顺便说一句,Enumerable.Single
.
中存在相同的错误
我们在 Code Review of LINQ Enumerable 进行了一些反省,发现了迭代 IEnumerable<TSource>
的不同策略,从而为我们提供了一些关于它的信息。
任意
public static bool Any<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
foreach (TSource element in source) {
if (predicate(element)) return true;
}
return false;
}
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (e.MoveNext()) return true;
}
return false;
}
计数
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
ICollection<TSource> collectionoft = source as ICollection<TSource>;
if (collectionoft != null) return collectionoft.Count;
ICollection collection = source as ICollection;
if (collection != null) return collection.Count;
int count = 0;
using (IEnumerator<TSource> e = source.GetEnumerator()) {
checked {
while (e.MoveNext()) count++;
}
}
return count;
}
单一或默认
public static TSource SingleOrDefault<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
TSource result = default(TSource);
long count = 0;
foreach (TSource element in source) {
if (predicate(element)) {
result = element;
checked { count++; }
}
}
switch (count) {
case 0: return default(TSource);
case 1: return result;
}
throw Error.MoreThanOneMatch();
}
如您所见,使用了 3 种不同的策略。
- 任意:迭代枚举器,提前退出
- Count:迭代枚举数,除非是ICollection,调用Count
- SingleOrDefault:迭代枚举器,不提前退出
一些观察:
- 'Any()' 也可以针对 ICollection 进行优化
- 'SingleOrDefault' 也可以优化为提前退出
- none 的方法关心 IReadOnlyCollection,即使它也有一个 属性 'Count'
问题:
- 每种策略的优缺点是什么?
- LINQ 的实现是这样的吗?
好吧,前两种方法是不言自明的,不是吗?它们以尽快停止的方式进行优化,并检查类型是否具有 Count
属性 以避免循环。
但是SingleOrDefault
with predicate 确实有一个奇怪的实现。它可以在第二个匹配项目处停止,因为很明显必须抛出 InvalidOperationException
(Single...
而不是 First...
确保最多有 1 个项目)。但它会检查每个项目并计算匹配项。
version without predicate有这个优化。
所以问题是:到底是什么?而且似乎 is really a bug 不会被修复,因为它只会降低错误情况下的性能。我简直不敢相信。
顺便说一句,Enumerable.Single
.