CodeGym /Các khóa học /C# SELF /Kết hợp lambda expressions và delegates

Kết hợp lambda expressions và delegates

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

1. Giới thiệu

Delegate — là một kiểu mô tả “chữ ký” của function: nó nhận những parameter nào, trả về cái gì. Nó giống như hộ chiếu cho function hoặc vé vào cửa cho code: nếu chữ ký khớp — vào, không khớp — xin lỗi, bạn không được vào.

Ví dụ:

public delegate int Operation(int a, int b);

Delegate này biểu thị một hàm nhận hai int và trả về int. Trước khi có lambda thì code trông như thế này (đúng nhưng không tiện lắm):

Operation add = delegate (int x, int y)
{
    return x + y;
};

Khi có lambda, code ngắn gọn hơn nhiều:

Operation add = (x, y) => x + y;

Thông tin thú vị: Trong C# lambda expression chỉ là cách để tạo một “anonymous function”, nó tự động compile thành một instance của delegate có kiểu phù hợp. Khi đó ranh giới giữa chúng gần như biến mất!

Delegates tích hợp sẵn: Func, Action, Predicate

Để không phải định nghĩa hàng tá delegate cho mọi tình huống, C# có các kiểu “universal”:

  • Func<T1, T2, ..., TResult> — delegate tổng quát, nhận các tham số (T1, T2, ...) và trả về kiểu TResult.
  • Action<T1, T2, ...> — giống như trên nhưng không trả về gì (void).
  • Predicate<T> — delegate nhận một tham số và trả về bool (được dùng nhiều cho filter).
Delegate Kiểu trả về Ví dụ cú pháp
Func<int, int>
int
Func<int, int> f = x => x + 1;
Action<string>
void
Action<string> a = s => ...;
Predicate<double>
bool
Predicate<double> p = d => ...;

Ví dụ:

Func<int, int, int> add = (a, b) => a + b;
Action<string> show = s => Console.WriteLine(s);
Predicate<int> isEven = n => n % 2 == 0;

2. Lambda expressions “được đóng gói” trong delegate

Truyền lambda vào method nhận delegate

Bất kỳ method nào nhận delegate đều có thể “ăn” lambda, miễn chữ ký của lambda khớp với delegate mong đợi.

public static void CalculateAndShow(int a, int b, Func<int, int, int> operation)
{
    int result = operation(a, b);
    Console.WriteLine($"Kết quả: {result}");
}

// Gọi với lambda
CalculateAndShow(5, 7, (x, y) => x * y); // Kết quả: 35

Ví dụ với Action

void ProcessString(string message, Action<string> processor)
{
    processor(message);
}

// Dùng lambda
ProcessString("Xin chào, thế giới!", s => Console.WriteLine(s.ToUpper()));

Ví dụ với Predicate

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = numbers.FindAll(n => n % 2 == 0);

Đó là lúc bạn nhận ra: cần gì phải tự định nghĩa delegate khi đã có “bánh xe” đã được chứng minh từ Microsoft.

Cách delegates và lambda hoạt động cùng nhau

flowchart TD
    A[Biểu thức lambda] -->|Tạo ra| B((Delegate))
    B -->|Được truyền như arg| C[Method]
    B -->|Lưu trữ| D[Property/Field/Collection]
    C -->|Gọi| E[Mã của lambda]

3. Truyền lambda như delegate vào các method tự viết

Nếu method tự viết của bạn dùng delegates, lambda làm cho việc sử dụng cực kỳ linh hoạt.

Ví dụ: máy tính mini của chúng ta

// Định nghĩa method để làm việc với các operation
static int PerformOperation(int a, int b, Func<int, int, int> operation)
{
    return operation(a, b);
}

// Áp dụng các operation khác nhau (lambda vào cuộc!)
int sum = PerformOperation(3, 4, (x, y) => x + y);
int multiply = PerformOperation(3, 4, (x, y) => x * y);
int max = PerformOperation(3, 4, (x, y) => x > y ? x : y);

Console.WriteLine(sum);      // 7
Console.WriteLine(multiply); // 12
Console.WriteLine(max);      // 4

Thấy không, logic thay đổi rất duyên dáng mà không cần 3 method riêng biệt?

4. Lambda + delegates trong LINQ (và hơn thế)

Trong LINQ các method cần một “mini hàm logic”, tức là delegate. Hầu hết các LINQ methods nhận những kiểu này:

  • Func<T, bool> cho filter (Where)
  • Func<T, TResult> cho transform (Select)
  • Func<T, TKey> cho sort (OrderBy)
List<string> names = new List<string> { "Anna", "Oleg", "Viktor", "Yana" };

// Lấy những tên dài hơn 3 ký tự
var longNames = names.Where(name => name.Length > 3);

// Chuyển tên thành chữ hoa
var upperNames = names.Select(name => name.ToUpper());

LINQ là “lambda maxed out”, vì gần như mọi thứ có thể truyền như delegate thì đều có thể truyền như lambda!

5. Kết hợp: lưu trữ và truyền delegates-lambda

Danh sách các hàm

Lambda không chỉ là hàm ẩn thời vụ. Bạn có thể lưu một tập hợp lambdas trong collection và dùng chúng động.

List<Func<int, int, int>> operations = new List<Func<int, int, int>>
{
    (x, y) => x + y,
    (x, y) => x - y,
    (x, y) => x * y,
    (x, y) => x / y
};

foreach (var op in operations)
{
    Console.WriteLine(op(10, 2));
}

Cách này thường thấy trong tests, handlers và plugins.

Từ điển delegates

Nếu bạn thích kiến trúc plug-and-play thì sẽ thích cái này:

var mathFuncs = new Dictionary<string, Func<int, int, int>>
{
    { "plus", (x, y) => x + y },
    { "minus", (x, y) => x - y },
    { "pow", (x, y) => (int)Math.Pow(x, y) }
};

string command = "pow"; // Giả lập input của user

if (mathFuncs.TryGetValue(command, out var operation))
{
    Console.WriteLine(operation(2, 5)); // 32
}
else
{
    Console.WriteLine("Lệnh không tìm thấy");
}

6. Những chi tiết hữu ích

Lambda-delegate: trả về delegate từ method

Bạn có thể trả về delegate (và do đó là lambda) từ method — gần như tạo được một factory hàm!

Func<int, int> GetMultiplier(int factor)
{
    // Trả về lambda-delegate, nó sẽ nhân với factor
    return x => x * factor;
}

var triple = GetMultiplier(3);
Console.WriteLine(triple(5)); // 15

var quadruple = GetMultiplier(4);
Console.WriteLine(quadruple(5)); // 20

Như vậy closure biến từ món đồ chơi thành công cụ thực thụ!

Ghép hàm: Combine, Delegate.Combine và multicast delegates

Đôi khi muốn một lần gọi thôi nhưng chạy nhiều hàm. Ví dụ xử lý sự kiện GUI: nhấn nút — nhiều handlers cùng phản ứng.

Action và delegates cho phép “nối” nhiều hàm:

Action<string> pipeline = s => Console.WriteLine("Bước 1: " + s);
pipeline += s => Console.WriteLine("Bước 2: " + s.ToUpper());
pipeline += s => Console.WriteLine("Bước 3: " + s.Length);

pipeline("kiểm tra");
// In ra:
// Bước 1: kiểm tra
// Bước 2: KIỂM TRA
// Bước 3: 8

Tiện cho các kịch bản dạng “chuỗi” hoặc xử lý sự kiện.

Quan trọng: Với các hàm trả về giá trị, kết quả chỉ đến từ lần gọi cuối cùng (“đuôi” của chuỗi). Còn với Action — tất cả đều được thực thi, vì void.

Dùng lambda-delegate như callback parameter

Kịch bản kinh điển: “gọi tao khi xong”.

void DownloadFile(string url, Action<string> onFinish)
{
    // Giả lập tải file...
    System.Threading.Thread.Sleep(500);// (Giả lập công việc lâu, đừng dùng trong GUI app thật)
    onFinish($"Tải xuống '{url}' đã hoàn tất!");
}

DownloadFile("http://example.com", msg => Console.WriteLine(msg));

Trong app thật (ví dụ gọi API, DB, file) — đây là pattern tiêu chuẩn.

Higher-order function: ví dụ lọc theo predicate

Lambda và delegates cho phép viết các hàm generic nhận hàm khác làm tham số. Đây là nền tảng của pattern “higher-order function”.

List<int> Filter(List<int> source, Predicate<int> condition)
{
    List<int> result = new List<int>();
    foreach (var item in source)
        if (condition(item)) result.Add(item);
    return result;
}

// Dùng các lambda khác nhau:
var onlyPositive = Filter(new List<int> { -2, 0, 2, 7 }, x => x > 0);
var onlyEven = Filter(new List<int> { -2, 0, 2, 7 }, x => x % 2 == 0);

Còn có thể tạo “factory” cho lambda:

Func<int, Predicate<int>> GetRangeChecker(int min, int max) =>
    x => x >= min && x <= max;

Predicate<int> inRange = GetRangeChecker(1, 10);

var filtered = Filter(new List<int> { 0, 5, 10, 15 }, inRange); // 5, 10

7. Lỗi thường gặp và các lưu ý khi kết hợp

Nếu chữ ký tham số không khớp, compiler sẽ kêu (CS1660: "Cannot convert lambda expression").

Lambda có thể “ghi nhớ” biến mà sau đó thay đổi (closure) — chú ý khi bắt biến.

Multicast delegates trả về giá trị thì chỉ trả kết quả từ handler cuối cùng — điều này thường khiến người mới bất ngờ.

Ở những chỗ cần Expression<Func<int,bool>>, bạn không thể dùng statement-lambda — cần đúng expression-lambda.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sử dụng Func và Action
Sử dụng Func và Action
1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Delegate - kiểu cho lambda-expression
Mối liên hệ giữa lambda-expression và delegate
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION