Để làm chủ LINQ, không nhất thiết phải nắm toàn bộ các cải tiến của C# 3.0 — bởi bản thân LINQ không yêu cầu thay đổi nào ở cấp độ Common Language Runtime (CLR). Thay vào đó, nó dựa trên trình biên dịch mới (C# 3.0 hoặc VB 9.0), sinh ra mã IL tương thích hoàn toàn với .NET Framework 2.0, và tận dụng thư viện LINQ được cung cấp sẵn.
Tuy nhiên, để hiểu sâu cách LINQ hoạt động — đặc biệt là cú pháp truy vấn, biểu thức lambda, và cơ chế xử lý dữ liệu — người đọc cần làm quen với một số tính năng nền tảng được giới thiệu từ C# 2.0 trở đi. Chương này tóm lược ngắn gọn bốn thành phần then chốt: generics, delegates, anonymous methods, và yield return. Đây không phải là tài liệu tham khảo đầy đủ về từng tính năng, mà là bản tóm tắt có trọng tâm nhằm hỗ trợ việc học LINQ hiệu quả hơn.
Generics: Loại bỏ sự lặp lại và tăng hiệu năng
Trước khi có generics, lập trình viên thường phải viết nhiều phiên bản của cùng một hàm cho từng kiểu dữ liệu — ví dụ như MinInt, MinDouble, MinString — hoặc dùng kiểu object kèm ép kiểu, dẫn đến mất an toàn kiểu và chi phí hiệu năng do boxing/unboxing.
Generics giải quyết điều này bằng cách hoãn việc xác định kiểu cụ thể cho đến thời điểm chạy — nhờ JIT compiler tạo ra các bản triển khai chuyên biệt cho từng kiểu sử dụng. Dưới đây là phiên bản generic của hàm tìm giá trị nhỏ hơn:
static T Smaller<T>(T first, T second) where T : IComparable<T>
{
return first.CompareTo(second) <= 0 ? first : second;
}
Khi gọi, bạn có thể viết:
int result = Smaller(7, 12); // Trình biên dịch suy luận T là int
string winner = Smaller("Alpha", "Beta"); // T là string
Không cần ép kiểu, không mất an toàn kiểu, và không có overhead do chuyển đổi kiểu động. Generics cũng áp dụng cho lớp và interface — ví dụ IEnumerable<T>, List<T> — đóng vai trò nền tảng cho toàn bộ kiến trúc LINQ.
Delegates: Mô hình hóa hành vi như giá trị
Một delegate là kiểu dữ liệu tham chiếu mô tả chữ ký của một hoặc nhiều phương thức. Nó cho phép truyền hàm như tham số — một yếu tố thiết yếu để xây dựng các hàm bậc cao (higher-order functions) như Where, Select, hay OrderBy.
Ở C# 1.0, việc khởi tạo delegate khá dài dòng:
delegate int Calculator(int x, int y);
class MathOps
{
public static int Add(int a, int b) => a + b;
public static int Multiply(int a, int b) => a * b;
}
// Khởi tạo tường minh
Calculator op1 = new Calculator(MathOps.Add);
Calculator op2 = new Calculator(MathOps.Multiply);
C# 2.0 đơn giản hóa bằng cú pháp "method group conversion":
Calculator op1 = MathOps.Add; // Không cần new, không cần tên delegate
Calculator op2 = MathOps.Multiply;
Điều này giúp code súc tích hơn và dễ đọc hơn — đồng thời vẫn giữ được tính an toàn kiểu tại thời điểm biên dịch.
Anonymous Methods: Đóng gói logic tại chỗ
Khi chỉ cần một lần sử dụng hàm, việc khai báo riêng một phương thức ngoài lớp là dư thừa. Anonymous methods cho phép định nghĩa logic ngay tại vị trí gọi — không cần tên, không cần khai báo riêng.
Ví dụ, thay vì tạo lớp phụ để đếm số lần gọi:
// Cách cũ: cần lớp trung gian
class CounterLogger
{
public int Count { get; private set; }
public void Log() { Console.WriteLine($"Log #{++Count}"); }
}
var logger = new CounterLogger();
RepeatFiveTimes(logger.Log);
Bạn có thể viết trực tiếp:
int count = 0;
RepeatFiveTimes(delegate {
Console.WriteLine($"Log #{++count}");
});
Trình biên dịch tự động tạo một lớp ẩn và phương thức ẩn để lưu trữ biến count — đảm bảo phạm vi sống (lifetime) của biến kéo dài đúng bằng thời gian tồn tại của delegate. Đây chính là cơ sở cho lambda expressions trong C# 3.0.
Yield Return: Xây dựng trình lặp theo cách khai báo
Thay vì viết toàn bộ lớp triển khai IEnumerator như trong ví dụ CountdownEnumerator, C# 2.0 cung cấp từ khóa yield return để định nghĩa trình lặp một cách khai báo và trực quan.
Ví dụ, một dãy giảm dần từ n về 0 có thể được viết như sau:
public static IEnumerable<int> CountdownFrom(int start)
{
for (int i = start; i >= 0; i--)
{
yield return i;
}
}
Khi gọi CountdownFrom(3), trình biên dịch tự động sinh ra một lớp trạng thái (state machine) triển khai IEnumerator<int>, quản lý vòng lặp và tạm dừng thực thi mỗi khi gặp yield return. Điều này loại bỏ hoàn toàn boilerplate code, đồng thời đảm bảo hiệu năng và khả năng debug tốt hơn.
Phiên bản mạnh kiểu (IEnumerable<int>) còn loại bỏ nhu cầu ép kiểu và tránh boxing — đặc biệt quan trọng khi làm việc với kiểu giá trị như int, DateTime, hay cấu trúc tùy chỉnh.
LINQ sử dụng triệt để tất cả bốn tính năng trên: IEnumerable<T> làm giao diện cơ sở, delegates làm đầu vào cho các toán tử truy vấn, anonymous methods (và sau này là lambda) làm cách viết ngắn gọn, và yield return làm cơ chế triển khai lazy evaluation — cho phép xử lý tập dữ liệu lớn mà không tải toàn bộ vào bộ nhớ.