Delegate truyền thống và Generic Delegate trong C#

Delegate truyền thống và Generic Delegate trong C#

February 2, 2025 0 By Nam Vu

Delegate là một khái niệm quan trọng trong C# và được sử dụng để tham chiếu đến các phương thức. Bài viết này sẽ giúp bạn hiểu rõ hơn về delegate, cách sử dụng chúng, và cách các generic delegate như Func, Action, và Predicate tối ưu hóa quá trình phát triển phần mềm.

Delegate là gì?

Delegate là một kiểu dữ liệu đặc biệt trong C#, cho phép bạn lưu trữ tham chiếu đến một hoặc nhiều phương thức có cùng chữ ký. Hiểu đơn giản, delegate là một “biến” giữ chữ ký của phương thức.

Ba bước chính khi làm việc với delegate:

  1. Khai báo (Declare): Khai báo một delegate với chữ ký xác định
public delegate void MyDelegate(string message);
  1. Tạo thể hiện và tham chiếu đến phương thức (Create an instance and reference a method)
MyDelegate del = new MyDelegate(SomeMethod);
  1. Gọi delegate (Invoke):
del("Hello, world!");

Hạn chế của delegate truyền thống

Việc khai báo delegate cho mỗi phương thức riêng lẻ có thể dẫn đến mã nguồn phức tạp và giảm hiệu suất, đặc biệt khi ứng dụng có nhiều delegate với các kiểu dữ liệu khác nhau. Để khắc phục điều này, generic delegate ra đời, giúp đơn giản hóa quá trình khai báo và sử dụng.

Generic Delegate trong C#

C# cung cấp ba loại generic delegate phổ biến: Func, Action, và Predicate. Chúng giúp giảm bớt việc khai báo delegate truyền thống và tăng tính linh hoạt của mã.

1. Func

  • Chức năng: Func nhận từ 1 đến 16 tham số đầu vào và luôn trả về một giá trị.
  • Cú pháp: Func<T1, T2, …, TResult>
    • T1, T2, …: Kiểu của tham số đầu vào.
    • TResult: Kiểu dữ liệu trả về.
  • Ví dụ:
Func<string, string[]> splitString = value => value.Split(' ');
var result = splitString("Hello World");
// result = ["Hello", "World"]
  • Khi nào nên dùng Func?
    • Khi bạn cần delegate có trả về kết quả.

2. Action

  • Chức năng: Action nhận từ 1 đến 16 tham số đầu vào nhưng không trả về giá trị.
  • Cú pháp: Action<T1, T2, …>
    • T1, T2, …: Kiểu của tham số đầu vào.
  • Ví dụ:
Action<string> logMessage = message => Console.WriteLine(message);
logMessage("This is a log message.");
// Output: This is a log message.
  • Khi nào nên dùng Action?
    • Khi bạn cần thực thi một hành động hoặc phương thức mà không yêu cầu kết quả trả về.

3. Predicate

  • Chức năng: Predicate nhận một tham số đầu vào và luôn trả về giá trị Boolean.
  • Cú pháp: Predicate<T>
    • T: Kiểu của tham số đầu vào.
  • Ví dụ:
Predicate<int> isEven = value => value % 2 == 0;
var result = isEven(4);
// result = true
  • Khi nào nên dùng Predicate?
  • Khi bạn cần kiểm tra một điều kiện cụ thể và kết quả là true hoặc false.

Ứng dụng của generic delegate

1. Kết hợp với LINQ

LINQ (Language Integrated Query) dựa rất nhiều vào các generic delegate, đặc biệt là Predicate và Func. Ví dụ:

var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = numbers.Where(num => num % 2 == 0).ToList();
// evenNumbers = [2, 4, 6]

Trong đoạn mã trên:

  • num => num % 2 == 0 là một Func<int, bool>.

2. Tăng tính linh hoạt và tái sử dụng mã

Các generic delegate giúp đơn giản hóa mã nguồn và tái sử dụng tốt hơn:

void ProcessData<T>(Func<T, T> processor, T data)
{
var result = processor(data);
Console.WriteLine(result);
}
ProcessData<int>(x => x * x, 5); // Output: 25
ProcessData<string>(s => s.ToUpper(), "hello"); // Output: HELLO

Tóm lại

  • Sử dụng Func nếu bạn cần trả về một kết quả.
  • Sử dụng Action nếu bạn chỉ cần thực thi một hành động mà không quan tâm đến kết quả.
  • Sử dụng Predicate nếu bạn cần kiểm tra một điều kiện và nhận về true/false.

Lợi ích của generic delegate:

  1. Đơn giản hóa mã nguồn.
  2. Tăng hiệu suất và tính tái sử dụng.
  3. Tích hợp tốt với LINQ, giúp viết mã dễ đọc và ngắn gọn hơn.

Delegate là một công cụ mạnh mẽ trong C#, và với sự hỗ trợ của generic delegate, bạn có thể tận dụng chúng để xây dựng các ứng dụng hiệu quả, dễ duy trì và dễ mở rộng hơn.

#ntechdevelopers