Enumerable Count 方法在循环中的性能

SeR*_*eGa 3 .net c# performance ienumerable for-loop

我有一系列由以下表示的项目IEnumerable

我需要循环遍历这些项目,它应该是 afor-loop因为索引很重要。

我的问题是,以下两个选项之间的性能有区别吗?

1.

for (int i = 0; i < items.Count(); i++)
{
    //Do something
}
Run Code Online (Sandbox Code Playgroud)
var itemsLength = items.Count();

for (int i = 0; i < itemsLength; i++)
{
    //Do something
}
Run Code Online (Sandbox Code Playgroud)

换句话说,该方法是否items.Count()在选项 1 中的每次迭代中一次又一次地运行?

Tho*_*and 7

调用Count()可枚举源将耗尽可枚举对象,直到枚举所有元素。因此,在 for 循环中调用Count()条件块中的可枚举项将在每次迭代时耗尽可枚举项。例如,调用

var numbers = VerboseRange(1, 5);
for (var index = 0; index < numbers.Count(); index++)
{
    Console.WriteLine($"For-loop is at index {index}...");
}

IEnumerable<int> VerboseRange(int start, int count)
{
    foreach (var number in Enumerable.Range(start, count))
    {
        Console.WriteLine($"Yielded number {number}.");
        yield return number;
    }
}
Run Code Online (Sandbox Code Playgroud)

将输出

Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 0...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 1...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 2...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 3...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 4...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
Run Code Online (Sandbox Code Playgroud)

因此,提前数比较好。

但是,我建议您使用计数器和foreach循环

var count = 0;
foreach (var item in items)
{
    // do something
    count++;
}
Run Code Online (Sandbox Code Playgroud)

在 C# 7.0 中,你终于可以做到

foreach (var (item, index) in items.WithIndex())
{
    // do something
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*anP 6

通常,当涉及到这些类型的性能问题时,测试它会更容易。使用 BenchmarkDotnet :

|  Method |      Mean |     Error |    StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|----------:|----------:|------:|--------:|----------:|
| Option2 |  49.40 ns |  0.700 ns |  0.654 ns |  1.00 |    0.00 |         - |
| Option1 | 955.00 ns | 16.993 ns | 15.064 ns | 19.30 |    0.34 |         - |
Run Code Online (Sandbox Code Playgroud)

选项 1 慢 20 倍。这里IEnumerable是使用生成的Enumerable.Range(0,100)

然后我还测试了如果您的实际类型是 a 会发生什么List以及是否Count()优化为仅返回Count属性。结果如下:

|  Method |      Mean |    Error |   StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|---------:|---------:|------:|--------:|----------:|
| Option2 |  40.53 ns | 0.246 ns | 0.192 ns |  1.00 |    0.00 |         - |
| Option1 | 452.07 ns | 4.906 ns | 4.097 ns | 11.14 |    0.12 |         - |
Run Code Online (Sandbox Code Playgroud)

这次 option1 慢了 11 倍,似乎只是因为不必生成完整的 100 个可枚举项。

使用的测试用例:

|  Method |      Mean |     Error |    StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|----------:|----------:|------:|--------:|----------:|
| Option2 |  49.40 ns |  0.700 ns |  0.654 ns |  1.00 |    0.00 |         - |
| Option1 | 955.00 ns | 16.993 ns | 15.064 ns | 19.30 |    0.34 |         - |
Run Code Online (Sandbox Code Playgroud)