LINQ Any() 和 Count()效能差異比較

前言

最近在讀一本電子書:LINQ Succinctly
內容從LINQ觀念到實作都有介紹,以新手來說一百頁左右的內容算很好上手,其中內容有一段敘述引起我的好奇

Using Any() instead of Count() != 0 usually conveys the intent of the code better, and in some circumstances, can perform better.

因為本人過往在撰寫程式時,習慣使用Count()來判斷集合中是否有物件,所以就來測試看看

List比較

我們先建立一個Product類別,然後使用For迴圈建立一個Product的List集合,之後使用StopWatch去測量已耗用時間
除了上述兩種情況,這邊額外新增測試 List 的 Count屬性
我們新增一個主控台應用程式,範例程式碼如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace AnyCountTest
{
class Program
{
static void Main(string[] args)
{
//建立資料
List<Product> products = new List<Product>();
for (int i = 0; i < 1000000; i++)
{
products.Add(new Product { Id = Guid.NewGuid(), Name = $"Product{i}", No = i });
}

Stopwatch sw = new Stopwatch();
int num = 0;

Console.WriteLine($"------------------------------List集合開始測試");


for (int i = 0; i < 5; i++)
{
sw.Restart();
foreach (var item in products)
{
if (products.Count != 0)
{
num++;
}
}
sw.Stop();
num = 0;
Console.WriteLine($"Count屬性:{sw.Elapsed}");

sw.Restart();
foreach (var item in products)
{
if (products.Count() != 0)
{
num++;
}
}
sw.Stop();
num = 0;
Console.WriteLine($"Count() :{sw.Elapsed}");

sw.Restart();
foreach (var item in products)
{
if (products.Any())
{
num++;
}
}
sw.Stop();
num = 0;
Console.WriteLine($"Any :{sw.Elapsed}");
Console.WriteLine($"第{i + 1}次測試完畢--------------------------");
}

Console.ReadLine();
}
}

class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public int No { get; set; }
}
}

讓我們來看看輸出結果:

Count>Count()>Any()

從上述的結果可以看到如果有 Count 屬性時,效率最佳所以一定優先使用,Count()Any()好,因為如果來源的執行個體型別有實作ICollection<T>,則會直接取用Count屬性,但這邊100萬筆資料來看,三者效能差異不會差得太過誇張,再來我們看一下Linq-to-Entities

Linq-to-Entities比較

首先在專案的 NuGet 安裝 EntityFramework 套件,之後我們先在DB準備好5000筆的商品資料

過程不是這篇重點所以略過,這邊自己可以選擇哪種做法比較方便,我是採用DB First方式,再寫迴圈把資料灌入資料表
準備好資料後開始測試,程式碼範例如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace AnyCountTest
{
class Program
{

static void Main(string[] args)
{
using (LinqTestEntities1 db = new LinqTestEntities1())
{

//取得資料
var products = from i in db.Product
select i;


Stopwatch sw = new Stopwatch();
int num = 0;
Console.WriteLine($"------------------------------Entity集合開始測試");

for (int i = 0; i < 5; i++)
{

sw.Restart();
foreach (var item in products)
{
if (products.Count() != 0)
{
num++;
}
}
sw.Stop();
num = 0;
Console.WriteLine($"Count() :{sw.Elapsed}");

sw.Restart();
foreach (var item in products)
{
if (products.Any())
{
num++;
}
}
sw.Stop();
num = 0;
Console.WriteLine($"Any :{sw.Elapsed}");
Console.WriteLine($"第{i + 1}次測試完畢--------------------------");

}
Console.ReadLine();
}
}
}
}

結果:

Any()>Count()

這邊看到結果,因為第一次執行的時候會經過JIT編譯,這會造成第一次呼叫時比較花時間,所以第一次測試結果可以跳過不參考。總共資料有5000筆,可以看出效率上Count()就比Any()慢上許多,所以這邊就務必去使用Any()去作判斷

Linq-to-Entities比較(附加條件)

Count()Any() 各自都含有另一個多載方法

1
2
3
4
5
//傳回數字,代表指定之序列中符合條件的項目數目
public static int Count<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate);

//判斷序列的任何項目是否符合條件
public static bool Any<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate);

所以讓我們來改寫一下上面的程式碼,為兩個方法新增條件:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace AnyCountTest
{
class Program
{

static void Main(string[] args)
{
using (LinqTestEntities1 db = new LinqTestEntities1())
{

//取得資料
var products = from i in db.Product
select i;


Stopwatch sw = new Stopwatch();
int num = 0;
Console.WriteLine($"------------------------------Entity集合開始測試");

for (int i = 0; i < 5; i++)
{

sw.Restart();
foreach (var item in products)
{
if (products.Count(x => x.No > 1000) != 0)
{
num++;
}
}
sw.Stop();
num = 0;
Console.WriteLine($"Count() :{sw.Elapsed}");

sw.Restart();
foreach (var item in products)
{
if (products.Any(x => x.No > 1000))
{
num++;
}
}
sw.Stop();
num = 0;
Console.WriteLine($"Any :{sw.Elapsed}");
Console.WriteLine($"第{i + 1}次測試完畢--------------------------");

}
Console.ReadLine();
}
}
}
}

結果:

Any()>Count()

這邊可以看到,比起剛剛無條件的情況,加入條件時效能差異更大了,如果資料量變大,後果可不堪設想…所以這邊也務必使用Any()

另外當 Count() 遇到使用 yield return 時,也會出現效能問題,所以也應該盡量避免使用,詳細原因可以參考下面小朱大的文章

這邊提醒一下條件使用 Count() != 0 或 Count() > 0皆不影響上述的測試結果

結語

如果能用 Count 屬性時,一定要用,若是 Linq-to-Entities,沒有 Count 屬性可用,請務必優先使用 Any() 方法,在參考資料有更多詳細的解說,可以多參考,這篇文章用簡單的範例去做效能測試驗證,希望能用淺顯易懂的方式讓大家了解囉

參考資料

小朱® 的技術隨手寫 - Any() vs. Count() 何時可用? 何時不可用?

Which method performs better: .Any() vs .Count() > 0?

query result what should i use Count() or Any()

Count property vs Count() method?