Linq 新功能 (6) CountBy、AggregateBy 与 Index

跳过 .NET 7 与 .NET 8,因为这两版没有新增方法【注1】,因此我们直接来到 .NET9

本集提要
  • 框架 : .NET 9
  • 功能 : CountBy、AggregateBy 与 Index
CountBy

CountBy 用于取代过去 GroupBy 后分别计数 (Count) 的功能,可以缩短程式码的长度以及更明白的表示意图。

举个例子比较容易体会,例如我们有以下的序列,目的是要取得『各个城市的人口数』:

 static IEnumerable<Person> GetPersons()
 {
     yield return new Person { Name = "Bill", City = "Seattle", Age = 18 };
     yield return new Person { Name = "Mark", City = "Taipei", Age = 21 };
     yield return new Person { Name = "Steve", City = "New York", Age = 32 };
     yield return new Person { Name = "James", City = "Taipei", Age = 16 };
     yield return new Person { Name = "John", City = "Seattle", Age = 52 };
     yield return new Person { Name = "Tom", City = "Taipei", Age = 37 };
     yield return new Person { Name = "David", City = "New York", Age = 26 };
     yield return new Person { Name = "Peter", City = "Seattle", Age = 23 };
     yield return new Person { Name = "Paul", City = "Taipei", Age = 45 };
     yield return new Person { Name = "Mary", City = "New York", Age = 38 };
 }

过去我们可能会这么写:

var oldCountByCity = persons.GroupBy(p => p.City)
                            .Select(g => new { City = g.Key, Count = g.Count() });

有了 CountBy 可就方便多了,程式码看起来很直觉:

 var newCountByCity = persons.CountBy(p => p.City);

 但是有一点要注意的是这两个的回传值型别其实不一样,当我们用 GroupBy → Select 的时候,由于使用了匿名型别所以回传值是 IEnumerable<匿名型别> (在 VS 上通常标示成 IEnumerable<'a>);但我们用 CountBy 的时候,回传值是 IEnumerable<KeyValuePair<string, int>>。

AggregateBy

AggregateBy 的情况和 CountBy 差不多,也是因应简化 GroupBy 而生。

原始资料同上面的 CountBy 範例,现在我们想要取得的是各城市的年龄总和,一般我们会这么写:

var oldAggregateByCity = persons.GroupBy(p => p.City)
                                .Select(g => new { City = g.Key, Age = g.Sum(p => p.Age) });

为了对照上的原因,把 Sum 改成 Aggregate:

 oldAggregateByCity = persons.GroupBy(p => p.City)
                             .Select(g => new { City = g.Key, Age = g.Aggregate(0, (acc, p) => acc + p.Age) });

写起来还挺麻烦一把,但有了 AggregateBy,直接融合 GroupBy ,就会简洁许多:

var newAggregateByCity = persons.AggregateBy(p => p.City, 0, (acc, p) => acc + p.Age);

AggregateBy 回传值的部分和 CountBy 一样,是 IEnumerable<KeyValuePair<string, int>>。

Index

新增的 Index 方法主要是用来取代 Select 的其中一个多载 – IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector),这个方法的用途很简单,就是『利用迭代製造出整数索引值』。

以前会这么写 (为了刻意和新的 Index 回传型别看起来一样,所以刻意在 ValueTuple 的栏位命名上动点手脚):

IEnumerable<(int Index, Person Item)> oldIndex = persons.Select((p, index) => (Index: index, Item: p));

有了 Index 后,就没这么麻烦了:

 IEnumerable<(int Index, Person Item)> newIndex = persons.Index();
Benchmark

CountBy 和 AggregateBy 除了让程式码更好写且看起来更简洁以外,在效能上也有不错的表现,在效能测试程式码中为求公平都换成使用 KeyValuePair<string, int> 作为回传型别 :

// * Summary *

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4890/23H2/2023Update/SunValley3)
12th Gen Intel Core i7-1265U, 1 CPU, 12 logical and 10 physical cores
.NET SDK 9.0.200-preview.0.25057.12
  [Host]     : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2
  DefaultJob : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2


| Method              | Mean      | Error     | StdDev    | Gen0   | Allocated |
|-------------------- |----------:|----------:|----------:|-------:|----------:|
| CountBy             |  1.975 ns | 0.0242 ns | 0.0226 ns |      - |         - |
| GroupByAndCount     | 20.195 ns | 0.4122 ns | 0.4411 ns | 0.0217 |     136 B |
| AggregateBy         |  2.306 ns | 0.0257 ns | 0.0228 ns |      - |         - |
| GroupByAndSum       | 20.600 ns | 0.2657 ns | 0.2486 ns | 0.0217 |     136 B |
| GroupByAndAggregate | 20.782 ns | 0.1831 ns | 0.1623 ns | 0.0217 |     136 B |

本篇範例连结,效能测试连结。

注1:虽说 .NET 7 没有增加新的方法,但是在效能上的改善是有的,而且某些函式的效能增进很惊人。

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章

5 点赞(415) 阅读(67)