Delegate trong C#
Một phương thức có thể có một hoặc nhiều tham số có kiểu dữ liệu khác nhau, nhưng nếu bạn muốn truyền một phương thức làm tham số thì sao? Làm thế nào để C# xử lý các chức năng gọi lại (callback) hoặc xử lý sự kiện? Câu trả lời là sử dụng delegate.
Delegate trong C#
Một delegate giống như một con trỏ đến một phương thức (gọi là con trỏ hàm trong C và C++). Delegate là kiểu tham chiếu và nó chứa tham chiếu tới một phương thức. Tất cả các delegate đều bắt nguồn từ lớp System.Delegate
.
Một delegate có thể được khai báo bằng cách sử dụng từ khóa delegate, theo sau là chữ ký phương thức như dưới đây.
<access modifier> delegate <return type> <delegate_name>(<parameters>)
Chữ ký phương thức gồm tên phương thức, số lượng và kiểu dữ liệu của các tham số nhưng không bao gồm kiểu dữ liệu trả về. Tuy nhiên đối với delegate thì chứ ký phương thức bao gồm cả kiểu dữ liệu trả về.
Ví dụ sau khai báo một delegate Print
.
public delegate void Print(int value);
Delegate Print có thể được sử dụng để trỏ đến bất kỳ phương thức nào có cùng kiểu dữ liệu trả về và số lượng/kiểu dữ liệu của tham số được khai báo với Print. Ví dụ sau minh họa cách sử dụng delegate Print.
class Program
{
// declare delegate
public delegate void Print(int value);
static void Main(string[] args)
{
// Print delegate points to PrintNumber
Print printDel = PrintNumber;
// or
// Print printDel = new Print(PrintNumber);
printDel(100000);
printDel(200);
// Print delegate points to PrintMoney
printDel = PrintMoney;
printDel(10000);
printDel(200);
}
public static void PrintNumber(int num)
{
Console.WriteLine("Number: {0,-12:N0}",num);
}
public static void PrintMoney(int money)
{
Console.WriteLine("Money: {0:C}", money);
}
}
Đây là kết quả khi biên dịch và chạy chương trình:
Number: 10,000
Number: 200
Money: $ 10,000.00
Money: $ 200.00
Trong ví dụ trên, chúng tôi đã khai báo delegate Print
chấp nhận tham số kiểu int
và trả về kiểu void
. Trong phương thức Main()
, một biến printDel
kiểu Print được khai báo và gán tên phương thức PrintNumber
.
Bây giờ, mỗi khi gọi thực thi biến printDel
sẽ gọi tới phương thức PrintNumber
. Tương tự, nếu biến printDel
được gán cho phương thức PrintMoney
, thì nó sẽ gọi phương thức PrintMoney
.
Hình ảnh sau đây minh họa cho delegate.

Tùy chọn, một đối tượng delegate có thể được khởi tạo bằng từ khóa new
và chỉ định tên phương thức, như ví dụ dưới đây:
Print printDel = new Print(PrintNumber);
Gọi delegate
Delegate có thể được gọi như một phương thức vì nó là một tham chiếu đến một phương thức.
Khi gọi một delegate sẽ gọi tới phương thức mà delegate tham chiếu tới. Delegate có thể được gọi bằng hai cách: sử dụng toán tử ()
hoặc sử dụng phương thức ủy nhiệm Invoke()
như dưới đây:
Print printDel = PrintNumber;
printDel.Invoke(10000);
//or
printDel(10000);
Đây là kết quả khi biên dịch và chạy chương trình:
Number: 10000
Number: 10000
Truyền delegate như một tham số
Một phương thức có thể có một tham số kiểu delegate. Phương thức có thể gọi thực thi tham số delegate này. Ví dụ sau minh họa truyền delegate như một tham số của phương thức:
public static void PrintHelper(Print delegateFunc, int numToPrint)
{
delegateFunc(numToPrint);
}
Trong ví dụ trên, phương thức PrintHelper
có một tham số delegateFunc
kiểu Print là một delegate. Nó gọi thực thi delegate này: delegateFunc(numToPrint)
.
Ví dụ sau đây cho thấy cách sử dụng phương thức PrintHelper:
class Program
{
public delegate void Print(int value);
static void Main(string[] args)
{
PrintHelper(PrintNumber, 10000);
PrintHelper(PrintMoney, 10000);
}
public static void PrintHelper(Print delegateFunc, int numToPrint)
{
delegateFunc(numToPrint);
}
public static void PrintNumber(int num)
{
Console.WriteLine("Number: {0,-12:N0}",num);
}
public static void PrintMoney(int money)
{
Console.WriteLine("Money: {0:C}", money);
}
}
Đây là kết quả khi biên dịch và chạy chương trình:
Number: 10,000
Money: $ 10,000.00
Multicast delegate
Delegate có thể tham chiếu tới nhiều phương thức một lúc. Một delegate tham chiếu tới nhiều phương thức được gọi là multicast delegate. Toán tử +
thêm một phương thức vào đối tượng delegate và toán tử -
loại bỏ một phương thức hiện có ra khỏi một đối tượng delegate.
Ví dụ dưới đây sẽ minh họa về multicast delegate:
public delegate void Print(int value);
static void Main(string[] args)
{
Print printDel = PrintNumber;
printDel += PrintHexadecimal;
printDel += PrintMoney;
printDel(1000);
printDel -= PrintHexadecimal;
printDel(2000);
}
public static void PrintNumber(int num)
{
Console.WriteLine("Number: {0,-12:N0}",num);
}
public static void PrintMoney(int money)
{
Console.WriteLine("Money: {0:C}", money);
}
public static void PrintHexadecimal(int dec)
{
Console.WriteLine("Hexadecimal: {0:X}", dec);
}
Đây là kết quả khi biên dịch và chạy chương trình:
Number: 1,000
Hexadecimal: 3EB
Money: $ 1,000.00
Number: 2,000
Money: $2,000.00
Như bạn có thể thấy trong ví dụ trên, delegate Print
trở thành một multicast delegate vì nó tham chiếu đến ba phương thức: PrintNumber
, PrintMoney
và PrintHexadecimal
. Vì vậy, khi gọi thực thi printDel
sẽ gọi tuần tự tất cả các phương thức trên.
Delegate cũng được sử dụng với Event, anonymous method (phương thức ẩn danh), delegate Func, delegate Action.
Những điểm cần nhớ về delegate trong C#
- Delegate là một con trỏ phương thức (nó tham chiếu đến phương thức). Nó là kiểu dữ liệu tham chiếu.
- Một phương thức được gán cho delegate phải có cùng chữ ký với delegate.
- Delegate có thể được gọi như một phương thức bình thường sử dụng toán tử
()
hoặc sử dụng phương thứcInvoke()
. - Nhiều phương thức có thể được gán cho một delegate bằng toán tử
+
. Nó được gọi là multicast delegate.
Delegate Func trong C#
C# 3.0 có các kiểu generic delegate tích hợp sẵn như Func
và Action
do đó bạn không cần phải định nghĩa các delegate tùy chỉnh như ở trên.
Func
là một generic delegate thuộc namespace System
. Nó không có hoặc có nhiều tham số đầu vào và một tham số out. Tham số cuối cùng được coi là một tham số out.
Ví dụ: delegate Func
có một tham số đầu vào và một tham số out được định nghĩa trong namespace System
như dưới đây:
namespace System
{
public delegate TResult Func<in T, out TResult>(T arg);
}
Tham số cuối cùng trong ngoặc <>
được xem là kiểu trả về và các tham số còn lại được coi là loại tham số đầu vào như trong hình dưới đây.

Delegate Func có hai tham số đầu vào và một tham số out
sẽ được trình bày như dưới đây.

Biến sum dưới đây có kiểu delegate Func
với hai tham số đầu vào của kiểu int
và trả về giá trị của kiểu int
:
Func<int, int, int> sum;
Bạn có thể gán bất kỳ phương thức nào có hai tham số int
và trả về kiểu int
cho biến sum
ở trên. Hãy xem ví dụ sau:
class Program
{
static int Sum(int x, int y)
{
return x + y;
}
static void Main(string[] args)
{
Func<int,int, int> add = Sum;
int result = add(10, 10);
Console.WriteLine(result);
}
}
Đây là kết quả khi biên dịch và chạy chương trình:
20
Một kiểu delegate Func
có thể không có hoặc có đến 16 tham số đầu vào của các kiểu dữ liệu khác nhau. Tuy nhiên, nó phải bao gồm một tham số out
cho kết quả. Ví dụ, delegate Func
sau đây không có bất kỳ tham số đầu vào nào, nó chỉ có một tham số out
.
Func<int> getRandomNumber;
Delegate Func với phương thức ẩn danh trong C#
Bạn có thể chỉ định một phương thức ẩn danh cho delegate Func bằng cách sử dụng từ khóa delegate.
Chúng ta sửa đổi lại ví dụ trên một chút, sử dụng phương thức ẩn danh thay thế cho phương thức Sum:
class Program
{
static void Main(string[] args)
{
Func<int,int, int> add = delegate(int x, int y)
{
return x + y;
};
int result = add(10, 10);
Console.WriteLine(result);
}
}
Đây là kết quả khi biên dịch và chạy chương trình:
20
Delegate Func với biểu thức Lambda trong C#
Một delegate Func
cũng có thể được sử dụng với biểu thức lambda, chúng ta lại tiếp tục sửa ví dụ trên như sau:
class Program
{
static void Main(string[] args)
{
Func<int,int, int> add = (x, y) => x + y;
int result = add(10, 10);
Console.WriteLine(result);
}
}
Đây là kết quả khi biên dịch và chạy chương trình:
20
Những điểm cần nhớ về delegate Func trong C#
- Func là kiểu delegate được tích hợp sẵn trong C#.
- Delegate Func phải trả về một giá trị.
- Delegate Func có thể không có hoặc có đến 16 tham số đầu vào.
- Delegate Func không cho phép tham số
ref
vàout
. - Delegate Func có thể được sử dụng với một phương thức ẩn danh hoặc biểu thức lambda .
Delegate Action trong C#
Action cũng là một loại delegate được định nghĩa trong namespace System
. Delegate Action
cũng tương tự như delegate Func
ngoại trừ delegate Action
không trả về giá trị. Nói cách khác, một delegate Action
có thể được sử dụng với một phương thức có kiểu trả về là void
.
Ví dụ minh họa sử dụng delegate Action:
static void ConsolePrint(int i)
{
Console.WriteLine(i);
}
static void Main(string[] args)
{
Action<int> print = ConsolePrint;
print(10);
}
Đây là kết quả khi biên dịch và chạy chương trình:
10
Bạn có thể khởi tạo một delegate Action
bằng cách sử dụng từ khóa new
hoặc bằng cách chỉ định trực tiếp một phương thức như dưới đây:
Action<int> printActionDel = ConsolePrint;
//Or
Action<int> printActionDel = new Action<int>(ConsolePrint);
Một đại biểu hành động có thể mất tới 16 tham số đầu vào thuộc các loại khác nhau.
Delegate Action với phương thức ẩn danh trong C#
Một phương thức ẩn danh cũng có thể được chỉ định cho một delegate Action
:
static void Main(string[] args)
{
Action<int> print = delegate(int i)
{
Console.WriteLine(i);
};
print(10);
}
Đây là kết quả khi biên dịch và chạy chương trình:
10
Delegate Action với biểu thức Lambda trong C#
Biểu thức Lambda cũng có thể được sử dụng với delegate Action
:
static void Main(string[] args)
{
Action<int> print = i => Console.WriteLine(i);
print(10);
}
Đây là kết quả khi biên dịch và chạy chương trình:
10
Do đó, bạn có thể sử dụng bất kỳ phương thức nào không trả về giá trị cho delegate Action
.
Những điểm cần nhớ về delegate Action trong C#
- Delegate
Action
cũng giống như delegateFunc
ngoại trừ việc nó không trả về giá trị. Kiểu trả về phải làvoid
. - Delegate
Action
có thể không có hoặc có đến 16 tham số đầu vào. - Delegate
Action
có thể được sử dụng với các phương thức ẩn danh hoặc biểu thức Lambda.
Ưu điểm của delegate Func và Action
- Dễ dàng và nhanh chóng để định nghĩa các delegate.
- Làm cho mã ngắn gọn và dễ đọc.
- Kiểu dữ liệu tương thích trong suốt ứng dụng.
Delegate Predicate trong C#
Predicate cũng là một delegate như delegate Func
và Action
. Nó đại diện cho một phương thức chứa một tập hợp các tiêu chí và kiểm tra xem tham số đã truyền có đáp ứng các tiêu chí đó hay không. Một phương thức được gán cho delegate Predicate
phải có một tham số đầu vào và trả về kiểu boolean.
Delegate Predicate
được định nghĩa trong namespace System
.
Cũng giống như các loại delegate khác, delegate Predicate cũng có thể được sử dụng với bất kỳ phương thức, phương thức ẩn danh hoặc biểu thức lambda nào.
static bool IsUpperCase(string str)
{
return str.Equals(str.ToUpper());
}
static void Main(string[] args)
{
Predicate<string> isUpper = IsUpperCase;
bool result = isUpper("hello world!!");
Console.WriteLine(result);
}
Đây là kết quả khi biên dịch và chạy chương trình:
False
Delegate Predicate với phương thức ẩn danh trong C#
Một phương thức ẩn danh cũng có thể được gán cho loại đại biểu Dự đoán như dưới đây.
static void Main(string[] args)
{
Predicate<string> isUpper = delegate(string s)
{
return s.Equals(s.ToUpper());
};
bool result = isUpper("hello world!!");
}
Đây là kết quả khi biên dịch và chạy chương trình:
False
Delegate Predicate với biểu thức Lambda trong C#
Biểu thức lambda cũng có thể được gán cho kiểu delegate Predicate như dưới đây.
static void Main(string[] args)
{
Predicate<string> isUpper = s => s.Equals(s.ToUpper());
bool result = isUpper("hello world!!");
}
Đây là kết quả khi biên dịch và chạy chương trình:
False
Những điểm cần nhớ về delegate Predicate trong C#:
- Delegate Predicate có một tham số đầu vào và kiểu trả về boolean.
- Phương thức ẩn danh và biểu thức Lambda có thể được gán cho delegate Predicate.