CodeGym /Các khóa học /C# SELF /Phương thức LINQ mới: CountBy và AggregateBy

Phương thức LINQ mới: CountBy và AggregateBy

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

Nếu bạn từng viết LINQ "cổ điển" với group, kiểu như:


var cityCounts = students.GroupBy(s => s.City)
                         .Select(g => new { City = g.Key, Count = g.Count() });

thì chắc bạn cũng thấy, chỉ để đếm số lượng theo nhóm mà code dài dòng quá trời. Từ .NET 9, team Microsoft đã quyết định "giải cứu" dev và thêm vào LINQ hai phương thức rất hay ho:

  • CountBy — cách nhanh để đếm số phần tử theo key.
  • AggregateBy — tổng hợp đa năng theo key (không chỉ đếm mà còn cộng, v.v.).

Fun fact, mấy method này xuất hiện nhờ cộng đồng request quá nhiều, và bản tương tự đã có lâu trong nhiều thư viện LINQ "pro" như MoreLINQ.

2. Phương thức CountBy: Đếm nhóm siêu gọn

Mô tả

CountBy đúng nghĩa là "group xong đếm luôn". Method này trả về collection các cặp "key-số lượng", cực kỳ hữu dụng khi phân tích dữ liệu: top thành phố, số sinh viên theo điểm, tần suất xuất hiện cái gì đó.

Chữ ký (rút gọn):


IEnumerable<(TKey Key, int Count)> CountBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)

Áp dụng thực tế

Ví dụ 1: Số sinh viên theo thành phố

Giả sử có class như này:


class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
    public string City { get; set; }
}

Và trong app của mình — danh sách sinh viên:


var students = new List<Student>
{
    new Student { Name = "Ivan", Grade = 5, City = "Neonvill" },
    new Student { Name = "Anna", Grade = 4, City = "Los-Santos" },
    new Student { Name = "Egor", Grade = 3, City = "Neonvill" },
    new Student { Name = "Maria", Grade = 5, City = "Rouzwoter" },
    new Student { Name = "Oleg", Grade = 4, City = "Neonvill" }
};

Trong .NET 8 trở về trước thì phải viết:


var group = students.GroupBy(s => s.City)
                    .Select(g => new { City = g.Key, Count = g.Count() });

foreach (var cityInfo in group)
{
    Console.WriteLine($"{cityInfo.City}: {cityInfo.Count} sinh viên");
}

Với .NET 9 thì chỉ cần 1 method siêu gọn:


var cityCounts = students.CountBy(s => s.City);
foreach (var (city, count) in cityCounts)
{
    Console.WriteLine($"{city}: {count} sinh viên");
}

Chuẩn luôn — không cần GroupBy thủ công, không cần Count() nữa — code cực kỳ đơn giản!

Ví dụ 2: Đếm theo điểm số

Bài toán kinh điển cho thầy cô — biết có bao nhiêu sinh viên giỏi, trung bình trong nhóm:


var gradeCounts = students.CountBy(s => s.Grade);

foreach (var (grade, count) in gradeCounts)
{
    Console.WriteLine($"Điểm {grade}: {count} sinh viên");
}

Ví dụ 3: Tần suất ký tự trong chuỗi

CountBy không chỉ dùng cho object mà còn cho mấy thứ đơn giản hơn:


string word = "supercalifragilisticexpialidocious";
var charFrequencies = word.CountBy(ch => ch);

foreach (var (letter, count) in charFrequencies)
{
    Console.WriteLine($"{letter} : {count}");
}

Nhìn trong bộ nhớ (minh họa)

Key Count (trước CountBy) Count (với CountBy)
"Neonvill"
3 3
"Rouzwoter"
1 1
"Los Santos"
1 1

Cách này giúp code dễ đọc và giảm lỗi khi group.

Lỗi thường gặp và lưu ý

Nhớ là CountBy trả về tuple (Key, Count), không phải anonymous type. Đôi khi nhìn hơi "thô", nhưng lại rất rõ ràng. Ngoài ra, không chỉ định được kiểu số cho Count — luôn trả về int.

3. Phương thức AggregateBy: Tổng hợp đa năng theo nhóm

Mô tả

Nếu CountBy chỉ đếm theo key, thì AggregateBy đúng kiểu "dao đa năng"! Muốn tính tổng doanh số theo nhân viên, điểm cao nhất theo lớp, hay bất cứ thứ gì có thể tích lũy? AggregateBy cân hết.

Chữ ký (rút gọn mạnh):


IEnumerable<(TKey Key, TAccumulate Result)> AggregateBy<TSource, TKey, TAccumulate>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func)
  • keySelector — group theo tiêu chí nào.
  • seed — giá trị khởi tạo để tích lũy.
  • func — hàm mô tả cách "cộng dồn" kết quả.

Áp dụng thực tế

Ví dụ 1: Tổng điểm theo thành phố

Thay vì LINQ dài dòng kiểu này:


var sums = students.GroupBy(s => s.City)
                   .Select(g => new { City = g.Key, Sum = g.Sum(s => s.Grade) });

Bạn chỉ cần:


var sums = students.AggregateBy(
    s => s.City, // group theo city
    0,           // tổng ban đầu
    (acc, s) => acc + s.Grade // cộng điểm
);

foreach (var (city, sum) in sums)
{
    Console.WriteLine($"{city}: tổng điểm = {sum}");
}

Ví dụ 2: Điểm cao nhất theo thành phố

Muốn lấy max — chỉ cần đổi accumulator:


var maxByCity = students.AggregateBy(
    s => s.City,
    int.MinValue, // giá trị khởi tạo nhỏ nhất
    (max, s) => Math.Max(max, s.Grade)
);
foreach (var (city, maxGrade) in maxByCity)
{
    Console.WriteLine($"{city}: điểm cao nhất = {maxGrade}");
}

Ví dụ 3: Gom tên thành chuỗi

Cập nhật lại danh sách sinh viên:


var namesByCity = students.AggregateBy(
    s => s.City,
    "", // chuỗi ban đầu
    (acc, s) => string.IsNullOrEmpty(acc) ? s.Name : acc + ", " + s.Name
);

foreach (var (city, names) in namesByCity)
{
    Console.WriteLine($"{city}: {names}");
}

Minh họa "bên trong" (sơ đồ)


.   {Student, Student, Student, ...}
               |
               | (group theo city)
               v
["Neonvill"]  ["Rouzwoter"]  ["Los Santos"]
      |           |               |
      |     (tích lũy)            |
      |___________________________|
            |
            v
{("Neonvill", tổng), ("Rouzwoter", tổng), ...}

So sánh với cách "cũ"

Trước đây, mấy bài toán kiểu này phải viết GroupBy + Select, trong đó lại có Sum, Aggregate hoặc gì đó phức tạp. Giờ AggregateBy lo hết, code ngắn gọn, dễ hiểu.

Thực hành: áp dụng vào app

Dự án học tập của mình — nhật ký sinh viên. Thêm báo cáo điểm trung bình theo thành phố.


// Tính điểm TB bằng AggregateBy — tích lũy tổng và số lượng, rồi chia ra
var avgByCity = students.AggregateBy(
    s => s.City,
    (Sum: 0, Count: 0),
    (acc, s) => (acc.Sum + s.Grade, acc.Count + 1)
).Select(x => (x.Key, Average: (double)x.Result.Sum / x.Result.Count));

foreach (var (city, avg) in avgByCity)
{
    Console.WriteLine($"{city}: điểm TB = {avg:F2}");
}

Ở đây mình tích lũy tổng và số lượng trong tuple, rồi chia tổng cho số lượng để ra trung bình.

Lưu ý và bẫy thường gặp

Nếu accumulator của bạn là reference type, nhớ nhé: trong AggregateBy cùng một reference sẽ dùng cho mọi vòng lặp trong group, nên đừng mutate object, kẻo ảnh hưởng kết quả cả nhóm.

Và nữa: nếu cần group xong lại muốn transform tiếp — dùng luôn .Select. Hãy chọn seed (giá trị khởi tạo) hợp lý, kẻo ra kết quả bất ngờ (ví dụ, đừng dùng 0 cho string).

4. So sánh cách cũ và mới

Bài toán LINQ cũ LINQ mới .NET 9
Số sinh viên theo thành phố
GroupBy + Select + Count
students.CountBy(s => s.City)
Tổng điểm theo thành phố
GroupBy + Select + Sum
students.AggregateBy(..., 0, ...)
Điểm cao nhất theo thành phố
GroupBy + Select + Max
students.AggregateBy(..., ...)
Tần suất ký tự trong chuỗi
GroupBy + Select + Count
word.CountBy(ch => ch)

Lợi ích thực tế cho công việc và dự án

Những method như CountBy và AggregateBy cực kỳ hữu ích khi viết báo cáo thống kê, chuẩn bị dữ liệu cho visualization, làm bảng tổng hợp và các bài toán phổ biến khác. Chúng giúp code ngắn hơn, dễ đọc hơn và quan trọng nhất là đáng tin cậy hơn: ít phải "vọc" group và aggregate thủ công, giảm nguy cơ sai logic.

Khi đi phỏng vấn vị trí Junior/Middle, biết mấy method LINQ mới này chứng tỏ bạn theo sát nền tảng, còn khi làm production — khỏi phải tự chế, dùng luôn mấy cú pháp gọn đẹp, dễ đọc, dễ bảo trì.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Xác định điểm số cao nhất theo thành phố
Xác định điểm số cao nhất theo thành phố
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION