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 的重载方法,同时指定:

  1. keySelector:按什么分组(如 pet.Type);
  2. elementSelector:分组内元素要转换成什么(如匿名类型 / 自定义类);
  3. (可选)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 };

参考

对查询结果进行分组
Enumerable.GroupBy 方法

posted @ 2022-05-19 23:08  【唐】三三  阅读(126)  评论(0)    收藏  举报