Introduction

Có rất nhiều lập trình viên thường tìm cách tránh sử dụng delegate trong công việc của mình. Tất cả chúng ta đều biết rằng chúng ta có thể phát triển bất cứ thứ gì chúng ta muốn bằng rất nhiều các công cụ cũng như các phương thức lập trình rất quen thuộc. Vậy tại sao chúng ta lại phải mất thời gian tìm hiểu sử dụng delegate trong khi có nhiều cách khác cũng có thể thực hiện được chức năng tương tự.
Theo tôi thì đây chỉ đơn giản là lời ngụy biện mà thôi – nó chỉ ra một thực tế rằng đôi khi nhiều (hoặc thậm chí là rất nhiều) lập trình viên không hiểu rõ ràng về delegate.
Challenge

Có hàng ngàn các chủ đề, tutorials và blog giải thích về delegates. Đó quả thực là một thách thức không nhỏ cho những ai đang cố gắng giải thích lại một cái gì đó đã được giải thích rất nhiều lần. Tuy vậy, tôi vẫn quyết định viết một vài bài viết với mục tiêu giúp cho programmer có thể hiểu hơn về nó, nhờ vậy bỏ đi cảm giác ngại ngùng khi sử dụng delegate trong các dự án của họ.
The Essence of Delegates

Trước hết, chúng ta sẽ bắt đầu từ nghĩa tiếng anh của từ Delegate. Theo từ điển của Google, delegate được giải thích như sau:
Delegate: a person sent or authorized to represent others, in particular an elected. Representative sent to a conference.”
Ở đây, tôi muốn nhấn mạnh cụm từ “sent or authorized to represent others”. (tạm hiểu là được gởi gắm hoặc có quyền đại diện cho một người khác). So, delegate trong ngôn ngữ lập trình C# là một thực thể có quyền đại diện cho một đối tượng khác nào đó. Nghĩa là chúng ta đang có một thứ gì đó và nó cần được mô tả lại (hoặc đại diện). Vậy thì một câu hỏi được đặt ra đó là: What is “thứ gì đó”?
Trong trường hợp của chúng ta thì nó chính là các phương thức (method), hoặc hàm (function). Các method được đề cập ở đây có thể nằm ở bất kì đâu. Nó có thể thuộc sở hữu của một đối tượng, hoặc chỉ đơn giản là một static method. Nếu một method cần phải trình diễn từ xa (remote representation), nó sẽ phải dùng delegate. Delegate có thể gọi một phương thức dễ dàng mà không cần quan tâm đến khoảng cách xa hay gần giữa nó và method. Vậy thì tại sao Microsoft lại dùng cái tên delegate để đặt tên cho loại thực thể này? Đơn giản bởi vì chức năng chủ yếu của nó là gọi và thực thi bất kì phương thức nào từ xa, tương tự với định nghĩa tiếng Anh của nó. Ngoài ra, có thể hiểu một cách đại khái rằng nó giúp chúng ta thực hiện việc truyền method như một tham số. Chúng ta đã quá quen thuộc với việc truyền một đối tượng vào hàm với vai trò là tham số, nhưng còn đối với method thì sao? Liệu chúng ta có được phép truyền method/function vào hàm như một parameter? Câu trả lời là KHÔNG. Nhưng (và đây chính là sức mạnh thần kì của delegate), đại diện hợp pháp của nó (ở đây là delegate) thì có thể. Khá là dông dài phải không? Cho đến thời điểm này thì chúng ta đã biết rằng một delegate đại diện cho một method (function). Bây giờ chúng ta sẽ đi vào phân tích cách sử dụng delegate, step by step.
Step 1: Declare a delegate

Public delegate void RemoteOperation();

Microsoft tạo ra delegate theo một cách “rất hướng đối tượng”. Khác với con trỏ hàm trong C++, C# sẽ tạo ra một class khi compiler bắt gặp đoạn code trên. Bạn đừng quá ngạc nhiên. Khi bạn khai báo một delegate thì một class đang được tạo ra bởi compiler mà bạn không biết đó thôi. Tên của class được tạo ra sẽ là RemoteOperation. Tùy thuộc vào compiler version mà nó có thể kế thừa từ class MulticastDelegate (với các phiên bản mới nhất) hoặc từ class Delegate. Class MulticastDelegate kế thừa từ class Delegate. Class này cũng có các contructor, điều này có nghĩa là bạn cần phải khởi tạo nó bằng từ khóa new(). Giữa Delegate và MulticastDelegate cũng có những mối quan hệ nhất định. Nếu bạn là một lập trình viên chuyên nghiệp về OOP, hẳn bạn đã từng nghe qua về programming patterns lần đầu tiên được giới thiệu bởi Gang of Four (GOF). MulticastDelegate -> Delegate là một ví dụ hoàn hảo của Structural Composite Pattern (thiệt tình là chưa có biết cái GOF với cái Structural Composite là gì ^^). Nói chung là khái niệm về nó thì rất đơn giản, Delegate class giới thiệu component. MulticastDelegate giới thiệu composite. MulticastDelegate có cài đặt một mảng có kiểu Delegate dùng để lưu trữ các đối tượng delegate (private array). Bạn có thể thêm hoặc bớt các đối tượng của MulticastDelegate từ mảng này theo ý thích. Base class Delegate có một số các properties và methods. Trong đó nó có một method gọi là Invoke(), method này có các dấu hiệu đặc trưng của delegate (đó là kiểu trả về, danh sách các tham số truyền vào, các thành phần này gọi chung là signature). Signature này được lấy từ câu lệnh khai báo delegate của bạn. Trong ví dụ của chúng ta, phương thức Invoke không nhận bất cứ tham số nào và có kiểu trả về là kiểu void. Base class delegate có 2 property đặc biệt quan trọng:
  1. Target – dùng để lưu trữ tham chiếu đến đối tượng mà Method được cài đặt
  2. Method – lưu trữ MethodInfo của đối tượng này
Chúng ta sẽ đi sâu vào các properties này sau.
Note: nếu chúng ta khai báo delegate khác (nhìn bên dưới) thì Invoke method cũng phải có kiểu trả về là kiểu int và nhận 2 tham số, một kiểu integer và một kiểu string. Nói cách khác, Invoke method bắc chước định nghĩa delegate của bạn.
public delegate int DifferentSignatureDelegate (int id , string name );

Bây giờ chúng ta sẽ chuẩn bị một method mà chúng ta muốn delegate mô tả lại. Đây chỉ đơn giản là một static function với tên là DoNothing(), function này không có tham số và return void:
public static void DoNothing() { Console.WriteLine( "Nothing in nothing out, no action taken"); }

Step 2: Instantiate the delegate


RemoteOperation operationVoid = new RemoteOperation(DoNothing);

Lúc này, compiler sẽ tạo ra một instance hoặc class RemoteOperation. Như các bạn thấy, chúng ta sử dụng từ khóa new và một tham số truyền vào contructor của nó, tham số này chính là tên của function, không phải là một string. Bạn còn nhớ Invoke method chứ? Trước khi bắt đầu tạo ra instance, compiler sẽ kiểm tra xem signature của hàm DoNothing có giống với signature của Invoke method của class RemoteOperation hay không. Nếu sai, compiler sẽ trả về một lỗi với nội dung là không tìm thấy cái hàm “somename” nào trùng với delegate “delegatename”. Còn nếu đúng, một đối tượng delegate thuộc kiểu RemoteOperation được tạo ra. Sau đó, compiler tạo một thể hiện mới của class Delegate. Thể hiện này sẽ set value của Target property thành null bởi vì delegate đang là đại diện của một static function. Nếu function mà delegate đại diện không phải là static thì lúc này, chúng ta sẽ đưa vào đối tượng delegate này một hàm thuộc thể hiện của object sở hữu hàm đó. Property còn lại là Method sẽ có giá trị là Void DoNothing(). Sau đó, nó sẽ thêm thể hiện của class Delegate mà ta nói ở trên vào mảng Delegates được cài đặt bên trong đối tượng kiểu RemoteOperation.