1. Giới thiệu
Có thể bạn chưa biết, nhưng một trong những câu hỏi ưa thích ở phỏng vấn là “Bạn biết gì về biểu thức lambda?”. Tại sao lại hỏi? Vì đó là một thứ rất tiện và "thời thượng" trong C#, cho phép viết ít code hơn và biểu đạt rõ ràng hơn. Nếu anonymous methods là viết thư tay, thì biểu thức lambda là gửi tin nhắn trên messenger: nhanh, gọn, và đôi khi còn có emoji (đúng hơn là gần như vậy).
Biểu thức lambda xuất hiện trong C# từ phiên bản 3.0 và kể từ đó lan rộng khắp nơi: trong LINQ, event handlers, threads, collections và thậm chí các thư viện AI trên .NET.
Biểu thức lambda — là cách khai báo một method ngay trong thân code, "tại chỗ", không cần tên, siêu ngắn và rõ ràng. Cú pháp cơ bản luôn trông như sau:
(tham_số) => biểu_thức_hoặc_block_code
Mũi tên này => được gọi là “lambda operator” hoặc “arrow”.
Một ví dụ ví von nhỏ
Hãy nhớ lại cách bạn viết công thức nấu ăn: “Lấy táo, cắt nó, cho vào bát”.
Trong C# sẽ là:
(tao) => cat(tao)
2. Chuyển anonymous method sang lambda
Giả sử chúng ta có một delegate:
delegate int SquareDelegate(int x);
Anonymous method sẽ trông như sau:
SquareDelegate sq = delegate(int x) {
return x * x;
};
Còn bây giờ tương đương bằng biểu thức lambda:
SquareDelegate sq = (int x) => { return x * x; };
Nhưng C# có thể suy ra kiểu, nên ta có thể rút ngắn thêm:
SquareDelegate sq = x => x * x;
Ngắn gọn tuyệt vời!
So sánh với phương thức "bình thường" và anonymous function
| Cách | Khai báo | Chỗ dùng | Nhược điểm |
|---|---|---|---|
| Named method | Trong lớp | Mọi nơi | Cần tên, nhiều code hơn |
| Anonymous method | Trong code | Chỉ với delegates | Cú pháp cồng kềnh hơn |
| Lambda expression | Trong code | Mọi chỗ với delegates | Đôi khi kiểu không rõ ràng |
3. Cú pháp biểu thức lambda: các biến thể
Tham số
Không có tham số:
Action hello = () => Console.WriteLine("Xin chào, thế giới!");
Một tham số (có thể bỏ ngoặc):
Func<int, int> inc = x => x + 1;
Nhiều tham số (cần ngoặc):
Func<int, int, int> sum = (a, b) => a + b;
Thân lambda
Một biểu thức — không cần braces và return:
x => x * x
Block code — dùng braces, cần return:
(x, y) =>
{
int z = x + y;
return z * z;
}
Kiểu tham số
Thường không cần chỉ định kiểu — compiler sẽ suy. Nhưng nếu muốn, cũng được:
(x, y) => x + y // Compiler tự suy kiểu.
(int x, int y) => x + y // Có thể chỉ định rõ.
4. Một số lưu ý và ví dụ thực tế hơn
Sử dụng với collections
Nhớ ứng dụng mini học của chúng ta (ví dụ danh sách users)? Giả sử có mảng số:
int[] numbers = { 1, 2, 3, 4, 5 };
Cần chọn các số chẵn:
var evenNumbers = numbers.Where(n => n % 2 == 0);
Ở đây Where — extension method của LINQ, còn điều kiện là lambda filter của chúng ta.
Truyền vào method
Giả sử ta có một delegate method:
public delegate bool Filter(int number);
public static int[] FilterNumbers(int[] data, Filter predicate)
{
var result = new List<int>();
foreach (var n in data)
if (predicate(n))
result.Add(n);
return result.ToArray();
}
Bây giờ truyền lambda:
int[] evens = FilterNumbers(numbers, n => n % 2 == 0);
Lambda làm event handler
button.Click += (sender, args) => Console.WriteLine("Nút được nhấn!");
5. Lambda và các delegate generic chuẩn
Khó tưởng tượng lambda mà thiếu Func<>, Action<> và Predicate<>. Đó là các kiểu delegate tiện dụng:
- Action — không trả về gì.
- Func — trả về giá trị.
- Predicate — trả về bool, thường dùng cho filtering.
Ví dụ với Action:
Action<string> log = message => Console.WriteLine(message);
log("Đây là thông điệp qua lambda!");
Ví dụ với Func:
Func<int, int, int> multiply = (a, b) => a * b;
int product = multiply(3, 5); // 15
Ví dụ với Predicate:
Predicate<int> isNegative = n => n < 0;
bool test = isNegative(-7); // true
6. Lambda với nhiều biểu thức: khi nào cần block
Nếu lambda phải thực hiện nhiều thao tác, dùng braces và return rõ ràng:
Func<int, int, string> describeSum = (a, b) =>
{
int sum = a + b;
return $"Tổng: {sum}";
};
Không có return thì compiler sẽ complain (nhưng thường không giải thích rõ lắm — chỉ báo "not all code paths return a value").
7. Trả về void: Action
Khi lambda không trả gì, dùng Action:
Action greet = () => Console.WriteLine("Cười lên — code đang compile!");
Lambda có thể chứa bao nhiêu câu lệnh tùy thích:
Action<int> printSquare = x =>
{
int sq = x * x;
Console.WriteLine($"Bình phương của {x} là {sq}");
};
8. Hạn chế và lỗi thường gặp
Đôi khi muốn viết lambda rất tự do, nhưng compiler không luôn đồng ý.
Không thể khai báo biến có cùng tên với biến bị captured từ bên ngoài.
Cần chú ý kiểu: nếu signature của delegate và lambda không khớp — sẽ lỗi.
Phải trả giá trị nếu delegate yêu cầu return value.
Trong lambda phức tạp nhớ dùng braces. Một biểu thức — không dùng braces và return. Nhiều lệnh — dùng braces và return.
GO TO FULL VERSION