1. Lợi ích của biểu thức lambda
Trong lập trình thường xuyên lặp lại những nhiệm vụ tương tự. Ví dụ: "lọc danh sách người dùng trên 18 tuổi", "tính tổng tất cả số thỏa điều kiện", "sắp xếp sản phẩm theo giá". Nếu không có lambda thì phải tạo method riêng — và đó là tiếng ồn thừa trong mã, đặc biệt khi hành động đó chỉ dùng ở một chỗ. Lambda làm mã ngắn gọn hơn và gần với cách ta nghĩ về bài toán.
Biểu thức lambda là công cụ tiêu chuẩn trong nhiều ngôn ngữ hiện đại (không chỉ C#), vì chúng cho phép "truyền hành vi" dưới dạng giá trị, bất kể là filter, handler hay function chuyển đổi.
1. Ngắn gọn và súc tích
Lambda cho phép loại bỏ các khai báo dài của method thừa hoặc delegate ẩn danh khi bạn viết một đoạn chức năng nhỏ "ngay tại chỗ". Ví dụ, đây là cách lọc danh sách số trước khi có lambda:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// Trước khi có lambda:
List<int> evenNumbers = numbers.FindAll(delegate(int x) { return x % 2 == 0; });
// Với biểu thức lambda:
List<int> evenNumbers2 = numbers.FindAll(x => x % 2 == 0);
Kết quả giống nhau, nhưng mã có lambda ngắn gọn hơn nhiều. Trong dự án lớn, tiết kiệm số dòng sẽ đáng kể.
2. Tăng khả năng đọc và biểu đạt
Lambda giúp tập trung vào bản chất của thao tác, loại bỏ "nhiễu" cú pháp. Mã của bạn trở nên gần ngôn ngữ tự nhiên hơn:
var adults = users.Where(user => user.Age >= 18);
So sánh với việc khai báo method riêng bool IsAdult(User user), mà bạn chỉ viết để dùng cho bộ lọc này.
3. Tích hợp thuận tiện với LINQ và API collection
Điểm mạnh chính của lambda là khi kết hợp với LINQ và collection. Nhiều method trong collection chuẩn và operator LINQ mong đợi một function làm tham số (ví dụ Func<T, bool> cho việc lọc). Lambda cho phép khai báo function ngay tại chỗ:
var expensive = products.Where(p => p.Price > 1000);
var firstBook = books.FirstOrDefault(b => b.Title.StartsWith("C#"));
var doubled = numbers.Select(n => n * 2);
4. Capture biến từ scope ngoài (closures)
Biểu thức lambda có thể dùng biến khai báo bên ngoài. Điều này cho phép linh hoạt và tạo function động "ngay tại chỗ":
int minAge = 18;
var filtered = users.Where(u => u.Age >= minAge); // minAge được "capture" bởi lambda
Điều này mở ra những pattern thú vị để sinh các function "đã cấu hình" theo tham số.
Sự thật thú vị: Bên trong lambda bạn không chỉ đọc mà đôi khi còn thay đổi biến ngoài, mặc dù nên làm cẩn thận — phần này sẽ nói rõ hơn trong bài về closures!
5. Mã nhúng và có ngữ cảnh
Lambda "sống" ở chỗ chúng được dùng, không cần tìm khắp dự án giữa các method. Điều này làm mã gần với nguyên tắc "nhiều thông tin trong không gian nhỏ".
Trong ví dụ phát triển ứng dụng nhỏ (như hệ thống quản lý sách trong thư viện), giả sử ta có danh sách sách như sau:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public double Price { get; set; }
}
// Ở đâu đó trong mã:
List<Book> books = new List<Book>
{
new Book { Title = "C# 9.0 in a Nutshell", Author = "Skeet", Year = 2022, Price = 350 },
new Book { Title = "CLR via C#", Author = "Richter", Year = 2019, Price = 250 },
// ...
};
// Tìm tất cả sách đắt hơn 300:
var expensiveBooks = books.Where(b => b.Price > 300).ToList();
Bạn thấy điều kiện lọc ngay cạnh lời gọi, không mất công tìm hàm khác trong mã.
6. Dùng làm callback, event, timer
Lambda rất phù hợp cho hành động một lần, ví dụ một event handler (callback):
button.Click += (sender, args) => Console.WriteLine("Nút được nhấn!");
Lambda loại bỏ nhu cầu viết method riêng nếu handler rất đơn giản.
7. Mở rộng khả năng của delegate
Trước đây để truyền hành vi phải khai báo method có tên; giờ bạn viết function tại chỗ:
Timer timer = new Timer(_ => Console.WriteLine("Tick!"), null, 0, 1000);
8. Đơn giản hóa test và dependency injection
Với lambda bạn dễ tạo các implementation giả (mock) cho test mà không làm bẩn mã chính bằng class tạm hay tiện ích. Ví dụ nếu constructor nhận một delegate thì trong test bạn truyền một lambda với hành vi mong muốn.
2. Những nhược điểm chính của biểu thức lambda
Như mọi công cụ mạnh, lambda không hoàn hảo. Hãy bàn về những khó khăn và giới hạn chúng có thể gây ra.
1. Mất khả năng đọc khi lồng quá nhiều
Lambda tốt miễn là không quá nhiều ở một chỗ. Lambda lồng nhau hoặc lambda dài khiến mã khó hiểu:
var result = items.Select(x => x.Children.Where(y => y.Value > 10)
.Select(z => z.Name.ToUpper())
.ToList());
Thêm vài lớp nữa — và xin chào "câu đố đọc mã dân gian".
Lời khuyên: Nếu lambda dài hơn 3–4 dòng — hãy tách nó ra thành method có tên. Đừng ngại bị gọi là bảo thủ: đọc hiểu quan trọng hơn mốt rút gọn.
2. Khó debug
Lambda không quá thân thiện với debugger, đặc biệt khi viết "dòng đơn" trong chuỗi LINQ. Đôi khi khó đặt breakpoint bên trong lambda hoặc kiểm tra giá trị tại bước cụ thể.
Để dễ debug, tạm thời bạn có thể đưa thân lambda ra method có tên hoặc tách chuỗi LINQ dài thành các biến trung gian.
3. Kiểu tham số và kiểu trả về không rõ ràng
Lambda thường được truyền như delegate (Func<...>, Action<...>, Predicate<T>). Đôi khi khó hiểu ngay lập tức tham số vào và kiểu trả về phải là gì, nhất là với method generic.
Ví dụ:
Func<int, string, double> myFunc = (a, b) => a + b.Length; // Ối! Trả về int, nhưng cần double.
Compiler sẽ báo lỗi, nhưng với người mới thường khó hiểu ngay là lambda "không phù hợp form".
4. Vấn đề với capture biến
Capture biến từ scope ngoài (closure) là con dao hai lưỡi. Dùng biến bị capture không cẩn thận sẽ dẫn tới kết quả bất ngờ. Ví dụ trong vòng lặp:
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions) action();
Nhiều người mong ra 0 1 2, nhưng nhận được 3 3 3. Tại sao? Khi lambda chạy thì biến i đã là 3! Lambda "capture" biến chứ không phải giá trị của nó.
Đây là lỗi điển hình cho người mới, tài liệu chính thức giải thích chi tiết tại official documentation. Có cách giải quyết — nhưng cần cẩn thận.
5. Mất tên rõ ràng và khó tái sử dụng
Lambda phù hợp cho hành động dùng một lần. Nếu cùng điều kiện/hàm dùng ở nhiều chỗ, nên tách logic ra method có tên. Nếu không bạn sẽ bị trùng lặp và lỗi khi sửa.
6. Bất tiện khi thêm XML-comments
Lambda không thể có XML-documentation để auto-generate help (như method). Bình luận cho lambda phải viết bằng comment thông thường trong mã.
7. Có thể gây vấn đề hiệu năng
Trong hầu hết trường hợp lambda không chậm hơn method rõ rệt. Tuy nhiên, khi tạo nhiều lambda có capture biến sẽ sinh thêm object (closure) và dẫn tới allocation. Ở chỗ cần hiệu năng cao (ví dụ tight loop hoặc service chịu tải lớn) hãy cân nhắc dùng static method.
8. Không thể dùng các toán tử goto, break, continue đối với vòng lặp bên ngoài
Nếu lambda khai báo trong vòng lặp, bên trong lambda không thể dùng break hay continue để tác động vòng lặp ngoài — về cú pháp là không hợp lệ.
9. Lambda không thể miêu tả mọi hành vi
Lambda không làm việc trực tiếp với attributes, không thể khai báo modifiers truy cập, và không thực hiện một số thao tác đặc biệt — ví dụ không thể khai báo local function có tên.
3. Lựa chọn giữa hai điều
Khi nào lambda hữu ích
| Tình huống | Lambda — tiện? | Tại sao |
|---|---|---|
| Lọc/transform ngắn | 👍 | Nhanh và rõ |
| Hoạt động lồng nhiều tầng | 👎 | Sẽ trở nên khó đọc |
| Re-use (tái sử dụng) | 👎 | Tốt hơn tách thành method |
| Logic callback, sự kiện | 👍 | Gọn |
| Mô tả logic business phức tạp | 👎 | Cần tên + comment |
| Làm việc với LINQ | 👍 | Tình huống lý tưởng |
Khi nào nên tránh dùng lambda
- Nếu logic dài và có nhiều nhánh/tính toán.
- Nếu lambda làm điều gì đó không rõ cho người đọc và không có giải thích.
- Nếu function cần có tài liệu, dùng nhiều nơi hoặc cần một tên "nói rõ".
- Nếu lambda nằm quá sâu trong các lời gọi lồng — có nguy cơ mất khả năng đọc.
4. Lỗi điển hình khi làm việc với lambda
Lỗi capture biến trong vòng lặp:
List<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act(); // Tất cả sẽ in ra 5!
Cách đúng:
for (int i = 0; i < 5; i++)
{
int captured = i; // capture một biến riêng
actions.Add(() => Console.WriteLine(captured));
}
Lambda quá dài:
books.Where(b => b.Price > 1000 && b.Title.Contains("C#") && b.Author.Length > 4 && bla-bla-bla...);
// Mã trở nên khó đọc, hãy tách thành method!
Dùng lambda nơi cần method có tài liệu:
Nếu function được dùng nhiều lần hoặc cần comment chi tiết, tốt hơn viết method có tên:
bool IsExpensiveBook(Book book) => book.Price > 1000;
books.Where(IsExpensiveBook);
GO TO FULL VERSION