CodeGym /Các khóa học /C# SELF /Hàm tổng hợp: Sum, ...

Hàm tổng hợp: Sum, Count, Average, Max, Min

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

1. Giới thiệu

Khi làm việc với collection, nhiều lúc mình không chỉ muốn duyệt từng phần tử mà còn cần lấy một kiểu "tóm tắt" cho cả bộ dữ liệu. Ví dụ:

  • Đếm xem có bao nhiêu sinh viên được điểm 5.
  • Biết tổng số học sinh trong trường.
  • Tính tổng điểm mà tất cả sinh viên đạt được trong kỳ thi.
  • Tìm điểm cao nhất và thấp nhất.
  • Tính điểm trung bình của lớp.

Tất nhiên, mấy cái này hoàn toàn làm được bằng vòng lặp với biến đếm. Nhưng nói thật nhé: ai mà thích viết 20 dòng code cho mấy việc đơn giản như này đâu.

LINQ có sẵn bộ hàm tổng hợp — mấy method này nhận collection, duyệt hết các phần tử rồi trả về kết quả: tổng, trung bình, lớn nhất, nhỏ nhất, đôi khi còn nhiều trò hay ho khác nữa.

Bảng "tổng hợp" nhanh:

Method Làm gì Trả về
Count()
Đếm số phần tử
int
Sum()
Tính tổng giá trị số Phụ thuộc vào kiểu phần tử (
int
,
double
, ...)
Average()
Tính trung bình cộng
double
hoặc kiểu số khác
Max()
Tìm phần tử lớn nhất Phần tử trong collection
Min()
Tìm phần tử nhỏ nhất Phần tử trong collection

Tất cả mấy method này là extension-method cho collection implement IEnumerable<T>. Xem thêm — tài liệu chính thức về LINQ aggregate methods.

2. Chuẩn bị dữ liệu để thực hành

Tiếp tục với app quản lý sinh viên đơn giản nhé. Giả sử mình có class như này:


// Model sinh viên
public class Student
{
    public string Name { get; set; }
    public int Grade { get; set; } // Điểm theo thang 5
    public string Email { get; set; }
}

Và một list:


// Danh sách sinh viên cơ bản
List<Student> students = new List<Student>
{
    new Student { Name = "Ivan", Grade = 5, Email = "ivan@example.com" },
    new Student { Name = "Olga", Grade = 4, Email = "olga@example.com" },
    new Student { Name = "Artem", Grade = 3, Email = "artem@example.com" },
    new Student { Name = "Darya", Grade = 5, Email = "darya@example.com" },
    new Student { Name = "Petr", Grade = 2, Email = "petr@example.com" }
};

3. Đếm phần tử: Count()

Thống kê đơn giản

Ví dụ, bạn muốn biết tổng số sinh viên:


int totalStudents = students.Count(); // 5
Console.WriteLine($"Tổng số sinh viên: {totalStudents}");

LINQ-magic: Quá đơn giản! Method Count() trả về số phần tử trong collection.

Đếm có điều kiện

Có bao nhiêu sinh viên xuất sắc nhỉ?


int excellentStudents = students.Count(s => s.Grade == 5);
Console.WriteLine($"Số sinh viên điểm 5: {excellentStudents}");

Ở đây mình truyền vào Count một lambda — nó chỉ trả về những ai thỏa mãn điều kiện (s.Grade == 5). Trong LINQ thì nó giống Where(...).Count(), nhưng ngắn hơn và chạy nhanh hơn chút.

Nếu collection rỗng thì sao?

Nếu collection rỗng, Count sẽ trả về 0 — không lỗi gì cả.

4. Tính tổng giá trị: Sum()

Lấy tổng điểm của cả lớp

Giả sử bạn muốn biết tổng điểm mà cả lớp đạt được:


int sumOfGrades = students.Sum(s => s.Grade); // 5+4+3+5+2 = 19
Console.WriteLine($"Tổng điểm: {sumOfGrades}");

Sum nhận một selector (lambda), trả về giá trị cho từng phần tử.

Tính tổng cho mảng số

Nếu bạn chỉ có một list số, không cần selector:


int[] numbers = { 1, 2, 3, 4, 5 };
int sum = numbers.Sum(); // 15

Lỗi phổ biến và lưu ý

Nếu collection rỗng, Sum() với kiểu số sẽ trả về 0. Nhưng nếu collection là kiểu nullable (int?, double?), Sum cũng chạy ổn: nó bỏ qua giá trị null.

5. Trung bình cộng: Average()

Tính điểm trung bình của lớp

Kinh điển: mỗi sinh viên trung bình được bao nhiêu điểm?


double averageGrade = students.Average(s => s.Grade); // (5+4+3+5+2)/5 = 3.8
Console.WriteLine($"Điểm trung bình: {averageGrade:F2}");

Average — cực kỳ hữu ích cho thống kê. Lưu ý: giá trị trả về luôn là double, dù giá trị gốc là int. Như vậy không bị mất phần thập phân.

Nếu không có phần tử nào

Nếu collection rỗng, gọi Average() sẽ ném ra exception InvalidOperationException. Đây là bẫy rất hay gặp: nếu không chắc collection có phần tử, hãy kiểm tra trước!


if (students.Any())
    Console.WriteLine(students.Average(s => s.Grade));
else
    Console.WriteLine("Không có dữ liệu để tính trung bình!");

Trung bình cho mảng số


double avg = numbers.Average();

6. Lớn nhất và nhỏ nhất: Max()Min()

Ai là "ngôi sao" và ai "đội sổ"

Muốn biết ai kéo cả lớp lên, ai thì... làm thầy cô lo lắng? Dễ thôi:


int maxGrade = students.Max(s => s.Grade); // 5
int minGrade = students.Min(s => s.Grade); // 2

Console.WriteLine($"Điểm cao nhất: {maxGrade}");
Console.WriteLine($"Điểm thấp nhất: {minGrade}");

Ai là người đó?

Đôi khi quan trọng không chỉ là số, mà là tên người đạt điểm đó. Lấy object sinh viên có điểm cao nhất:


// Lấy sinh viên điểm cao nhất sau khi sort giảm dần
var bestStudent = students.OrderByDescending(s => s.Grade).First();
Console.WriteLine($"Sinh viên xuất sắc nhất: {bestStudent.Name} ({bestStudent.Grade})");

Trong .NET mới có MaxBy cho việc này đẹp hơn. Từ .NET 6 đã có, .NET 9 còn tiện hơn nữa (xem MaxBy trên Microsoft Docs). Nhưng không có MaxBy thì sort rồi .First() cũng ổn.

Bẫy và khác biệt

Nếu collection rỗng, gọi Max()Min() cũng sẽ ném InvalidOperationException. Nên nhớ kiểm tra trước (nhất là khi chưa kiểm tra collection):


if (students.Any())
    Console.WriteLine($"Điểm cao nhất: {students.Max(s => s.Grade)}");
else
    Console.WriteLine("Không có sinh viên để tìm điểm cao nhất.");

7. Ví dụ "chuỗi" các hàm tổng hợp

Thường các hàm tổng hợp dùng chung với filter và select:


// Trung bình điểm của sinh viên điểm 5
double avgExcellent = students
    .Where(s => s.Grade == 5)
    .Average(s => s.Grade); // luôn là 5, nhưng ví dụ cho dễ hiểu

// Tổng điểm của sinh viên điểm >= 4
int sumGood = students
    .Where(s => s.Grade >= 4)
    .Sum(s => s.Grade);

// Số email unique (phòng trường hợp trùng)
int uniqueEmails = students
    .Select(s => s.Email)
    .Distinct()
    .Count();

LINQ lúc này mới "phát huy" hết sức mạnh: kết hợp các thao tác đơn giản giúp code vừa ngắn vừa dễ đọc, đến con mèo nhà bạn cũng hiểu (nếu nó là Junior C# Developer).

8. So sánh với cách "thủ công": tại sao nên dùng aggregate?

So sánh LINQ với vòng lặp thường, ví dụ tính điểm trung bình:

Cách thường:


int sum = 0;
int count = 0;
foreach (var s in students)
{
    sum += s.Grade;
    count++;
}
double average = (count != 0) ? (double)sum / count : 0;

LINQ:


double average = students.Average(s => s.Grade);

Thêm xử lý collection rỗng cũng dễ hơn!

9. Một số lưu ý hay ho

Về hiệu năng và cách thực thi

LINQ-aggregate, khác với đa số thao tác LINQ, sẽ chạy ngay lập tức! Tức là khi bạn gọi Sum(), Count(), Average(), Max(), Min(), việc duyệt collection đã diễn ra ngay lúc đó. Các method này trả về kết quả cuối cùng, không phải collection.

Điều này rất quan trọng: nếu bạn làm gì nặng trước aggregate, ví dụ filter hoặc select phức tạp — nó chỉ chạy một lần, đúng lúc gọi aggregate.

Có dùng được aggregate trong query-syntax không?

Nhiều bạn mới hỏi: "Có viết được mấy cái này bằng query-syntax không?" Trả lời nhanh: query-syntax không có từ khóa cho aggregate, nhưng bạn luôn có thể mix với method syntax:


var avg = (from s in students where s.Grade > 3 select s.Grade).Average();

Trong ngoặc là query-syntax, sau đó gọi method aggregate. Cách này dùng nhiều hơn bạn nghĩ đấy!

10. Lỗi phổ biến khi dùng LINQ

Lỗi #1: Dùng Average(), Max() hoặc Min() với collection rỗng.
Nếu collection rỗng, Sum()Count() trả về 0, còn Average(), Max()Min() sẽ ném exception. Trước khi gọi mấy method này, nhớ kiểm tra collection có phần tử không nhé.

Lỗi #2: Truyền lambda sai kiểu.
Ví dụ, truyền string thay vì số vào hàm tổng hợp (Sum, Max...), sẽ bị lỗi compile. Đặc biệt dễ nhầm khi dùng anonymous method.

Lỗi #3: Kiểm tra số phần tử không tối ưu.
Method Where(...).Count() tạo collection mới rồi mới đếm. Thay vào đó, dùng Count(predicate) — nó đếm luôn số phần tử thỏa điều kiện, nhanh hơn.

Lỗi #4: Không để ý nullable khi tổng hợp.
Nếu bạn tính tổng với int?, Sum() sẽ bỏ qua null. Đây là behavior đúng, nhưng đôi khi sẽ ra kết quả bất ngờ nếu bạn không để ý trước.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tính tổng các phần tử trong mảng
Tính tổng các phần tử trong mảng
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION