关于C#:IEnumerable.Count()或ToList().Count

IEnumerable.Count() or ToList().Count

我得到了我自己类的对象列表,如下所示:

1
2
3
4
5
6
public class IFFundTypeFilter_ib
{
    public string FundKey { get; set; }
    public string FundValue { get; set; }
    public bool IsDisabled { get; set; }
}

属性IsDisabled通过查询collection.Where(some condition)并计算匹配对象的数目来设置。结果是IEnumarable不包含属性计数。我想知道,有什么会更快。

这一个:

1
collection.Where(somecondition).Count();

或者这个:

1
collection.Where(someocondition).ToList().Count;

集合可以包含很少的对象,但也可以包含,例如700。我要打两次计数电话,还有其他条件。在第一个条件中,我检查fundkey是否等于某个键,在第二个条件中,我也这样做,但我将它与其他键值进行比较。


你问:

I wonder, what would be faster.

每当你问你应该实际计时并找出答案。

我开始测试获得计数的所有变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var enumerable = Enumerable.Range(0, 1000000);
var list = enumerable.ToList();

var methods = new Func<int>[]
{
    () => list.Count,
    () => enumerable.Count(),
    () => list.Count(),
    () => enumerable.ToList().Count(),
    () => list.ToList().Count(),
    () => enumerable.Select(x => x).Count(),
    () => list.Select(x => x).Count(),
    () => enumerable.Select(x => x).ToList().Count(),
    () => list.Select(x => x).ToList().Count(),
    () => enumerable.Where(x => x % 2 == 0).Count(),
    () => list.Where(x => x % 2 == 0).Count(),
    () => enumerable.Where(x => x % 2 == 0).ToList().Count(),
    () => list.Where(x => x % 2 == 0).ToList().Count(),
};

我的测试代码显式地运行每个方法1000次,使用Stopwatch度量每个执行时间,并忽略发生垃圾收集的所有结果。然后它得到每个方法的平均执行时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var measurements =
    methods
        .Select((m, i) => i)
        .ToDictionary(i => i, i => new List<double>());

for (var run = 0; run < 1000; run++)
{
    for (var i = 0; i < methods.Length; i++)
    {
        var sw = Stopwatch.StartNew();
        var gccc0 = GC.CollectionCount(0);
        var r = methods[i]();
        var gccc1 = GC.CollectionCount(0);
        sw.Stop();
        if (gccc1 == gccc0)
        {
            measurements[i].Add(sw.Elapsed.TotalMilliseconds);
        }
    }
}

var results =
    measurements
        .Select(x => new
        {
            index = x.Key,
            count = x.Value.Count(),
            average = x.Value.Average().ToString("0.000")
        });

以下是结果(顺序从最慢到最快):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+---------+-----------------------------------------------------------+
| average |                          method                           |
+---------+-----------------------------------------------------------+
| 14.879  | () => enumerable.Select(x => x).ToList().Count(),         |
| 14.188  | () => list.Select(x => x).ToList().Count(),               |
| 10.849  | () => enumerable.Where(x => x % 2 == 0).ToList().Count(), |
| 10.080  | () => enumerable.ToList().Count(),                        |
| 9.562   | () => enumerable.Select(x => x).Count(),                  |
| 8.799   | () => list.Where(x => x % 2 == 0).ToList().Count(),       |
| 8.350   | () => enumerable.Where(x => x % 2 == 0).Count(),          |
| 8.046   | () => list.Select(x => x).Count(),                        |
| 5.910   | () => list.Where(x => x % 2 == 0).Count(),                |
| 4.085   | () => enumerable.Count(),                                 |
| 1.133   | () => list.ToList().Count(),                              |
| 0.000   | () => list.Count,                                         |
| 0.000   | () => list.Count(),                                       |
+---------+-----------------------------------------------------------+

这里有两件事很重要。

第一,任何一种方法,如果使用.ToList()内联,其速度都要比不使用它的等效方法慢得多。

第二,LINQ运算符利用可枚举的基础类型(如果可能)来简化计算。enumerable.Count()list.Count()方法表明了这一点。

list.Countlist.Count()调用之间没有区别。因此,关键的比较是enumerable.Where(x => x % 2 == 0).Count()enumerable.Where(x => x % 2 == 0).ToList().Count()调用之间的比较。由于后者包含一个额外的操作,我们希望它需要更长的时间。它大约长2.5毫秒。

我不知道你为什么说你要两次调用计数代码,但如果你这样做,最好是建立列表。如果不只是在查询后执行普通的.Count()调用。


一般来说,具体化到列表中的效率会降低。

另外,如果您使用两个条件,那么就没有必要缓存结果或将查询具体化到List

您应该只使用接受谓词的Count的重载:

1
collection.Count(someocondition);

正如@codecaster在评论中提到的,它相当于collection.Where(condition).Count(),但更易读和简洁。


就是这样用的

1
var count = collection.Where(somecondition).ToList().Count;

没有意义-填充一个列表只是为了得到计数,所以使用IEnumerable.Count()是这种情况的合适方法。

在这种情况下,使用ToList是有意义的。

1
2
3
var list = collection.Where(somecondition).ToList();
var count = list.Count;
// do something else with the list