C#:Any()vs Count()表示空列表

Vim*_*tan 27 .net c#

之前发布的一个问题让我思考.在空列表中使用时会Any()Count()执行类似吗?

正如这里所解释的,两者都应该经历相同的步骤GetEnumerator()/MoveNext()/Dispose().

我在LINQPad上使用快速程序测试了这个:

static void Main()
 {
    var list = new List<int>();

    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();

    for (int i = 0; i < 10000; i++)
        list.Any();

    stopwatch.Stop();
    Console.WriteLine("Time elapsed for Any()   : {0}", stopwatch.Elapsed);


    stopwatch = new Stopwatch();
    stopwatch.Start();

    for (int i = 0; i < 10000; i++)
        list.Count();

    stopwatch.Stop();
    Console.WriteLine("Time elapsed for Count(): {0}", stopwatch.Elapsed);
}
Run Code Online (Sandbox Code Playgroud)

一般结果似乎表明Count()在这种情况下更快.这是为什么?

我不确定我的基准是否合适,如果不是,我会很感激.


编辑:我明白它在语义上会更有意义.我已经张贴在问题中的第一个链接显示的情况下它是有意义的做用Count(),直接原因是该值将被使用,因此这个问题.

Cyr*_*don 22

Count()方法针对ICollection<T>类型进行了优化,因此GetEnumerator()/MoveNext()/Dispose()不使用该模式.

list.Count();
Run Code Online (Sandbox Code Playgroud)

被翻译成

((ICollection)list).Count;
Run Code Online (Sandbox Code Playgroud)

Any()必须建立一个普查员.所以Count()方法更快.

这里是4个不同IEnumerable实例的基准.的MyEmpty模样IEnumerable<T> MyEmpty<T>() { yield break; }

iterations : 100000000

Function                      Any()     Count()
new List<int>()               4.310     2.252
Enumerable.Empty<int>()       3.623     6.975
new int[0]                    3.960     7.036
MyEmpty<int>()                5.631     7.194
Run Code Online (Sandbox Code Playgroud)

正如casperOne在评论中所说的那样Enumerable.Empty<int>() is ICollection<int>,因为它是一个数组,并且数组对Count()扩展名不好,因为强制转换并不是一件容易的事ICollection<int>.

无论如何,对于一个自制的空IEnumerable,我们可以看到我们所期望的,Count()比慢Any(),因为测试的开销IEnumerable是一个ICollection.

完整基准:

class Program
{
    public const long Iterations = (long)1e8;

    static void Main()
    {
        var results = new Dictionary<string, Tuple<TimeSpan, TimeSpan>>();
        results.Add("new List<int>()", Benchmark(new List<int>(), Iterations));
        results.Add("Enumerable.Empty<int>()", Benchmark(Enumerable.Empty<int>(), Iterations));
        results.Add("new int[0]", Benchmark(new int[0], Iterations));
        results.Add("MyEmpty<int>()", Benchmark(MyEmpty<int>(), Iterations));

        Console.WriteLine("Function".PadRight(30) + "Any()".PadRight(10) + "Count()");
        foreach (var result in results)
        {
            Console.WriteLine("{0}{1}{2}", result.Key.PadRight(30), Math.Round(result.Value.Item1.TotalSeconds, 3).ToString().PadRight(10), Math.Round(result.Value.Item2.TotalSeconds, 3));
        }
        Console.ReadLine();
    }

    public static Tuple<TimeSpan, TimeSpan> Benchmark(IEnumerable<int> source, long iterations)
    {
        var anyWatch = new Stopwatch();
        anyWatch.Start();
        for (long i = 0; i < iterations; i++) source.Any();
        anyWatch.Stop();

        var countWatch = new Stopwatch();
        countWatch.Start();
        for (long i = 0; i < iterations; i++) source.Count();
        countWatch.Stop();

        return new Tuple<TimeSpan, TimeSpan>(anyWatch.Elapsed, countWatch.Elapsed);
    }

    public static IEnumerable<T> MyEmpty<T>() { yield break; }
}
Run Code Online (Sandbox Code Playgroud)

  • -1:您的测试不正确.`Enumerable.Empty <T>()`返回一个空数组,数组实现`IList <T>`扩展`ICollection <T>`.你需要一种除了`yield break'之外什么都不做的方法.该调用基本上会在测试中嗅出相同的代码路径. (5认同)