LINQ:GroupBy
public static IEnumerable<IGrouping<TKey,TElement>> GroupBy<TSource,TKey,TElement> (
this IEnumerable<TSource> source,
Func<TSource,TKey> keySelector,
Func<TSource,TElement> elementSelector);
类型参数
TSource
source 的元素类型。
TKey
keySelector 返回的键的类型。
TElement
IGrouping<TKey,TElement> 中元素的类型。
参数
source IEnumerable
要对其元素进行分组的 IEnumerable。
keySelector Func<TSource,TKey>
用于提取每个元素的键的函数。
elementSelector Func<TSource,TElement>
用于将每个源元素映射到 IGrouping<TKey,TElement> 中的元素的函数。
基础数据
// 定义宠物类和测试数据
public enum PetType { Cat, Dog, Bird }
public class Pet
{
public string Name { get; set; }
public PetType Type { get; set; }
public float Weight { get; set; }
}
List<Pet> pets = new()
{
new Pet { Name = "咪咪", Type = PetType.Cat, Weight = 4.2f },
new Pet { Name = "旺财", Type = PetType.Dog, Weight = 12.5f },
new Pet { Name = "花花", Type = PetType.Cat, Weight = 3.8f },
new Pet { Name = "啾啾", Type = PetType.Bird, Weight = 0.5f },
new Pet { Name = "来福", Type = PetType.Dog, Weight = 9.8f }
};
分组
第一种,每组包含完整的 Pet 对象:
var petGroupByType = pets.GroupBy(pet => pet.Type);
相当于,查询表达式
var petGroupByType =
from pet in pets
group pet by pet.Type;
相当于,SQL
SELECT *
FROM Pet
GROUP BY Type;
遍历
foreach (var group in petGroupByType)
{
Console.WriteLine($"Type: {group.Key}");
foreach (var pet in group)
{
Console.WriteLine($" {pet.Name} - {pet.Weight}kg");
}
}
第二种,每组只包含体重值:
/* IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
* this IEnumerable<TSource> source,
* Func<TSource, TKey> keySelector);
*/
//TKey = PetType:分组的键类型(你按宠物类型 pet.Type 分组,键的类型是 PetType 枚举 / 类);
//TElement = float:分组内元素的类型(你指定只提取宠物的 Weight 字段,类型是 float)。
//简单来说:IGrouping<PetType, float> 代表「一组宠物的体重数据」,这组数据有一个统一的标识
IEnumerable<IGrouping<PetType, float>> petGroupByType02 = pets
.GroupBy(pet => pet.Type, // 分组的键
pet => pet.Weight); //分组后保留的元素
相当于,查询表达式
IEnumerable<IGrouping<PetType, float>> query =
from pet in pets
group pet.Weight by pet.Type;
相当于,SQL
SELECT Type, Weight
FROM Pet
GROUP BY Type;
遍历
foreach (var group in petGroupByType02)
{
Console.WriteLine($"Type: {group.Key}"); // 分组键(PetType)
// 遍历当前分组中的所有体重值
foreach (float weight in group)
{
Console.WriteLine($" Weight: {weight}kg");
}
Console.WriteLine($" Count: {group.Count()}"); // 组内元素数量
Console.WriteLine($" Average: {group.Average():F2}kg"); // 平均体重
Console.WriteLine();
}
第三种:分组 + 自定义投影(键 + 元素都自定义)
核心逻辑
使用 GroupBy 的重载方法,同时指定:
keySelector:按什么分组(如pet.Type);elementSelector:分组内元素要转换成什么(如匿名类型 / 自定义类);- (可选)
resultSelector:最终返回的结果结构(自定义分组整体的输出)。
public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, // 按什么分组(如pet.Type)
Func<TSource, TElement> elementSelector, // 组内元素转换(如只取Name+Weight)
Func<TKey, IEnumerable<TElement>, TResult> resultSelector // 最终返回的结构
);
var query03 = pets.
GroupBy(
pet => pet.Type,
pet => pet.Weight,
(type, group) => new
{
petType = type,
petWeightSum = group.Sum()
});
foreach (var group in query03)
{
Console.WriteLine($"type: {group.petType}, wightSum: {group.petWeightSum}");
}
SQL
SELECT Type as petType,
SUM(Weight) as petWeightSum
FROM Pet
GROUP BY Type;
三种用法对比
| 用法 | 方法重载特点 | 返回类型 | 核心场景 |
|---|---|---|---|
| 第一种 | 仅指定 keySelector | IGrouping<PetType, Pet> |
需要保留完整对象,后续灵活处理 |
| 第二种 | 指定 keySelector + elementSelector | IGrouping<PetType, float> |
仅需分组后的单个字段(如体重) |
| 第三种 | 加 resultSelector 自定义结果 | 自定义类型(如匿名类型) | 直接输出 “分组 + 聚合” 的最终结构 |
匿名类型分组
IEnumerable<IGrouping<int, string>> query =
pets.GroupBy(pet => new { pet.Age }, pet => new { pet.Name} );
查询表达式
IEnumerable<IGrouping<int, string>> query =
from pet in pets
group new { pet.Name} by new { pet.Age };

浙公网安备 33010602011771号