C# 为什么LINQ方法Any不检查计数?
如果我们查看扩展方法C# 为什么LINQ方法Any不检查计数?,c#,.net,linq,C#,.net,Linq,如果我们查看扩展方法Any的源代码,就会发现它总是使用枚举器: public static bool Any<TSource>(this IEnumerable<TSource> source) { if (source == null) throw Error.ArgumentNull(nameof (source)); using (IEnumerator<TSource> enumerator = source.GetEn
Any
的源代码,就会发现它总是使用枚举器:
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
throw Error.ArgumentNull(nameof (source));
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
return true;
}
return false;
}
我说,它可以看起来像这样:
private static bool Any<TSource>(IEnumerable<TSource> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
IList<TSource> sourceList = source as IList<TSource>;
if (sourceList != null)
{
return sourceList.Count != 0;
}
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
return true;
}
return false;
}
对于IList
来说,它大约好两倍,对于IEnumerable
来说,它大约差20%
那么,问题是:在
SingleOrDefault
方法中使用优化,而在Any
one中不使用优化的原因是什么?问题背后的假设可能是:
数数很快,为什么不用呢
为什么Any
不使用它,一个看似合理的答案是Count
并不总是快。他们选择的实现的优点是它将具有相对稳定和低成本(即大致O(1)
)。但是,在所有情况下(如您所确定的),它可能不如Count
快
无法保证实现IList
或ICollection
的类将具有fastCount
属性,例如,Count>0
的速度通常比现有的任何
实现慢
此外,使用
IList
的代码可能应该使用ICollection
,因为您的代码不需要IList
提供访问的额外功能。。LINQ是为IEnumerable而不是IList构建的。然而,我认为在.NET框架中支持最常见的集合类是微不足道的(但无论如何,SingleOrDefault
使用Count
。为什么,如果它可以很慢的话?只是不明白在哪里可以使用“slow”方法,其中notYep,这意味着SingleOrDefault
对于任何实现IList
且具有慢速计数
属性@Backs的类型来说都会很慢。LINQ的本质是它是一个稍微泄漏的抽象-对于他们正在进行权衡的每个方法。有时,他们会得到正确的权衡“错误”(例如)。由于接口(IList
,ICollection
或其他任何东西),很难构建这种东西告诉您某件事是否可能,但不告诉您它的性能成本。现有的Any
的好处是它有一个相对稳定的成本。@mjwills我写了一篇关于这个问题的小文章:.net核心存储库中有一个关于这个问题的问题:。您可以在那里阅读关于它的优缺点的第一手信息(特别是Jon Hannas的评论)。您会发现一些令人惊讶的事情,例如,对于数组,当前版本实际上比检查ICollection.Count
(而对于列表,它稍微慢一点)。
private static bool Any<TSource>(IEnumerable<TSource> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
IList<TSource> sourceList = source as IList<TSource>;
if (sourceList != null)
{
return sourceList.Count != 0;
}
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
return true;
}
return false;
}
namespace AnyTests
{
class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<Test>();
}
}
public class Test
{
private readonly List<int> list1 = new List<int>(new[] { 1, 2, 3, 4, 5 });
private readonly IEnumerable<int> list2 = GetCollection();
private static IEnumerable<int> GetCollection()
{
yield return 1;
}
[Benchmark]
public void TestLinqAnyList()
{
Enumerable.Any(list1);
}
[Benchmark]
public void TestNewAnyList()
{
NewAny(list1);
}
[Benchmark]
public void TestLinqAnyEnumerable()
{
Enumerable.Any(list2);
}
[Benchmark]
public void TestNewAnyEnumerable()
{
NewAny(list2);
}
private static bool NewAny<TSource>(IEnumerable<TSource> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
IList<TSource> sourceList = source as IList<TSource>;
if (sourceList != null)
{
return sourceList.Count != 0;
}
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
return true;
}
return false;
}
}
}
// * Summary *
BenchmarkDotNet=v0.10.13, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical cores and 4 physical cores
Frequency=3515624 Hz, Resolution=284.4445 ns, Timer=TSC
[Host] : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2600.0
DefaultJob : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2600.0
Method | Mean | Error | StdDev |
---------------------- |---------:|----------:|----------:|
TestLinqAnyList | 26.80 ns | 0.1382 ns | 0.1154 ns |
TestNewAnyList | 12.75 ns | 0.0480 ns | 0.0426 ns |
TestLinqAnyEnumerable | 18.03 ns | 0.0947 ns | 0.0886 ns |
TestNewAnyEnumerable | 23.51 ns | 0.0913 ns | 0.0762 ns |