C# cơ bản .NET Core
Lập trình C# (C Sharp)

Giới thiệu delegate

Delegate (hàm ủy quyền) là một kiểu dữ liệu, nó dùng để tham chiếu (trỏ đến) đến các hàm (phương thức) có tham số và kiểu trả về phù hợp với khai báo kiểu. Khi dùng đến delegate bạn có thể gán vào nó một, nhiều hàm (phương thức) có sự tương thích về tham số, kiểu trả về, sau đó dùng nó để gọi hàm (giống con trỏ trong C++), các event trong C# chính là các hàm được gọi thông qua delegate, bạn cũng có thể dùng delegate để xây dựng các hàm callback, đặc biệt là các Event

Delegate được dùng phổ biến khi gán các biểu thức lambda

Ví dụ sử dụng delegate

1 Đầu tiên cần khai báo một delegate, khai báo giống như cách khai báo phương thức nhưng có thêm từ khóa delegate và không có thân phương thức. Ví dụ sau khai báo một delegate có tên là ShowLog

public delegate void ShowLog(string message);

2 Khi đã có ShowLog, nó dùng như một kiểu dữ liệu để khai báo các biến, các biến này có thể gán vào nó các hàm có sự tương đồng về tham số và kiểu trả về với khai báo delegate ví dụ khai báo biến:

ShowLog showLog;

Thi hành delegate

Sau khi biến delegate được gán hàm vào, có thể dùng biến delegate để thi hành bằng cách gọi:
varDelegate.Invoke(các-tham-số) hoặc varDelegate(các-tham-số)

3 Tạo hai phương thức InfoWarning có tham số giống với ShowLog, nghĩa là có một tham số kiểu string, trả về void:

static public void Info(string s)
{
    // ...
}

static public void Warning(string s)
{
    // ...
}

Do Info, Warning có tương đồng về tham số với delegate trên, nên hai hàm này có thể dùng để gán vào biến kiểu ShowLog, xem đoạn mã đầy đủ sau:

Logs.cs

using System;

namespace CS008_Anonymous
{
    public class Logs
    {
        // Khai báo một delegate
        public delegate void ShowLog(string message);

        // Phương thức tương đồng với ShowLog (tham số, kiểu trả về)
        static public void Info(string s)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(string.Format("Info: {0}", s));
            Console.ResetColor();
        }

        // Phương thức tương đồng với ShowLog (tham số, kiểu trả về)
        static public void Warning(string s)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(string.Format("Waring: {0}", s));
            Console.ResetColor();
        }

        public static void TestShowLog()
        {
            ShowLog showLog;

            showLog = Info;         // showLog gán bằng phương thức Info
            showLog("Thông báo");   // Thi hành delegate chính là thi hành Info

            showLog = Warning;      // showLog gán bằng phương thức Warning
            showLog("Thông báo");   // Thi hành delegate chính là thi hành Info
        }
    }
}

Kết quả chạy đoạn, khi gọi hàm Logs.TestShowLog();:

Waring: Thông báo
Info: Thông báo

Gán nhiều phương thức vào delegate

Khi dùng delegate chạy một phương thức, cần đảm bảo biến delegate đó đã được gán phương thức (biến khác null), có thể bạn kiểm tra trước khi gọi ví dụ: if (showLog != null) showLog("Mgs") hoặc gắn gọn hơn showLog?.Invoke("Mgs");

4 Một delegate có thể đưa vào nó nhiều phương thức để một lần gọi thi hành tất cả các phương thức nó chứa

  • Toán tử += Nối thêm một phương thức vào delegate, ví dụ delegatevar += method1
  • Toán tử -= : Loại bỏ 1 phương ở cuối (nếu phương thức đó có trong delegate, tính từ cuối) , ví dụ delegatevar -= method1

Ví dụ:

public static void TestShowLogMulti()
{
    ShowLog showLog;

    showLog = null;
    showLog += Warning;         // Nối thêm Warning vào delegate
    showLog += Info;            // Nối thêm Info vào delegate
    showLog += Warning;         // Nối thêm Warning vào delegate

    //Một lần gọi thi hành tất cả các phương thức trong chuỗi delegate
    showLog("TestLog");         //Hoặc an toàn: showLog?.Invoke("TestLog");
}

Gọi phương thức TestShowLogMulti thì kết quả:

Waring: TestLog
Info: TestLog
Waring: TestLog

5 Ngoài cách gán cho delegate một hàm có tên cụ thể như trên, bạn cũng có thể gán một phương thức Anonymou, ví dụ:

showLog += (x) => Console.WriteLine(string.Format("===>{0}<===", x));

6 Các delegate cùng kiểu có thể kết hợp lại với nhau bằng toán tử +, ví dụ:

// Cộng nhiều Delegate
public static void TestShowLogPlus()
{
    ShowLog showLog1 = (x)=> {Console.WriteLine($"-----{x}-----");};
    ShowLog showLog2 = Warning;
    ShowLog showLog3 = Info;

    var all = showLog1 + showLog2 + showLog3 + showLog1;

    all("Xin Chào");
}

Gọi phương thức TestShowLogPlus kết quả là:

-----Xin Chào-----
Waring: Xin Chào
Info: Xin Chào
-----Xin Chào-----

Func và Action

FuncAction là hai mẫu delegate định nghĩa sẵn, giúp bạn nhanh chóng tạo ra biến kiểu delegate mà không mất công khai báo, xem lại ví dụ trên nếu sử dụng đến Func, Action thì không cần có dòng khai báo:

public delegate void ShowLog(string message);

Sử dụng Func

Func là mẫu delegate có kiểu trả về. Để khai báo biến delegate dùng cú pháp như sau:

Func<kiểu_tham_số_1, kiểu_tham_số_2, ..., kiểu_trả_về> var_delegate;

Kiểu cuối cùng trong khai báo Func là kiểu trả về của hàm, có thể thiếu tham số nhưng không được thiếu kiểu trả về

Ví dụ muốn có biến delegate tên bien1 tương đương với hàm có 2 tham số, tham số 1 kiểu int, tham số 2 kiểu string, và hàm trả về kiểu bool thì tạo biến đó như sau:

Func<int, string, bool> bien1;

Khai báo trên nếu bạn dùng cách thông thường tương ứng với:

// Khai báo delegate ở lớp
delegate bool DelegateName(int a, string b);
// Khai báo biến trong phương thức
DelegateName bien1;

Ví dụ:

using System;

namespace CS008_Anonymous
{
    class FuncAction
    {
        static int Sum(int x, int y)
        {
            return x + y;
        }

        public static void TestFunc(int x, int y)
        {
            Func<int,int,int> tinhtong;         // biến tinhtong phù hợp với các hàm trả về kiểu int, có 2 tham số kiểu int
            tinhtong = Sum;                     // Hàm Sum phù hợp nên có thể gán cho biến

            var ketqua = tinhtong(x, y);
            Console.WriteLine(ketqua);
        }
    }
}

Khi gọi phương thức TestFunc kết quả:

FuncAction.TestFunc(5, 6); // In ra: 11

Sử dụng Action

Action tương tự như Func, điều khác duy nhất là nó không có kiểu trả về, khai báo cơ bản như sau:

Action<kiểu_tham_số_1, kiểu_tham_số_2, ... > var_delegate;

Nghĩa là biến kiểu Action có thể gán bằng các hàm có kiểu trả về void

Trở lại ví dụ cho hai hàm InfoWarning ở trên, có thể sử dụng ngay đoạn code sau, để có kết quả tương tự:

public static void TestAction(string s)
{
    Action<string> showLog = null;

    showLog += Logs.Warning;         // Nối thêm Warning vào delegate
    showLog += Logs.Info;            // Nối thêm Info vào delegate
    showLog += Logs.Warning;         // Nối thêm Warning vào delegate

    // Một lần gọi thi hành tất cả các phương thức trong chuỗi delegate
    showLog("TestLog");
}

Sử dụng Delegate làm tham số hàm

Có thể sử dụng delegate làm tham số của phương thức, nó có vai trò như những hàm callback linh hoạt. Xem ví dụ sau:

// Sử dụng Delegate làm tham số phương thức, truyền callback
static void TinhTong(int a, int b, Action callback)
{
    int c = a + b;
    // Gọi callback
    callback(c.ToString());
}

public static void TestTinhTong()
{
    TinhTong(5,6, (x) => Console.WriteLine($"Tổng hai số là: {x}"));
    TinhTong(1,3, Logs.Info);
}
    

Source code: CS008_Anonymous (Git), hoặc tải ex008-1


Đăng ký nhận bài viết mới