如果我们查看扩展方法 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;
}
我认为,如果集合是 IList
,例如在 SingleOrDefault
方法中,检查 Count
属性不是更好吗? :
public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
IList<TSource> sourceList = source as IList<TSource>;
if (sourceList != null)
{
switch (sourceList.Count)
{
case 0:
return default(TSource);
case 1:
return sourceList[0];
}
}
else
{
//...
}
throw Error.MoreThanOneElement();
}
我说,它可以看起来像这样:
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 |
对于 IList
,它大约好两倍,对于 IEnumerable
,它大约差 20%。
那么,问题是:在 SingleOrDefault
方法中使用优化而不在 Any
方法中使用它的原因是什么?
最佳答案
您的问题背后的假设可能是:
Count is fast, why not use it?
为什么 Any
不使用它的一个合理的答案是 Count
并不总是快。他们选择的实现的优点是它会具有相对稳定和低成本(即大约 O(1)
)。 它可能不会在所有情况下都像 Count
一样快(正如您所确定的那样)。
没有保证实现IList
或ICollection
的类将具有快速 计数
属性。 ConcurrentDictionary
,例如,Count > 0
的速度通常比现有的 Any
实现要慢。
此外,您使用 IList
的代码应该可能使用 ICollection
,因为您的代码不需要 IList
的额外功能提供访问权限。
关于c# - 为什么 LINQ 方法 Any 不检查计数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49663052/