Hướng dẫn sử dụng C# Delegate và Event
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Delegate là gì?

Trong C# mỗi một hàm (phương thức, hoặc constructor) đều có một kiểu hàm. Hãy xem phương thức SayHello dưới đây:
Xem ví dụ đầy đủ:
HelloProgram.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
   class HelloProgram
   {

       // Đây là phương thức có một tham số string và trả về string
       // Kiểu hàm:  (string) -> (string)
       public string SayHello(string name)
       {
           return "Hello " + name;
       }

       // Đây là một phương thức có 2 tham số string, và trả về string
       // Kiểu hàm: (string, string) -> (string)
       public string SayHello(string firstName, string lastName)
       {
           return "Hello " + firstName + " " + lastName;
       }


       // Đây là một phương thức có 1 tham số, và không trả về gì.
       // Kiểu hàm: (string) -> ()
       public void Silent(string name)
       {

       }
   }


}
Hai phương thức dưới đây có cùng một kiểu hàm:
C# sử dụng từ khóa delegate để định nghĩa ra một thực thể đại diện cho các hàm (phương thức, hoặc constructor) có cùng một kiểu hàm.
Cú pháp:
// Cú pháp định nghĩa một delegate:

delegate <return_type> <delegate_name> <parameter_list>
Ví dụ:
// Định nghĩa một kiểu đại diện cho các hàm có kiểu
// (string,string) -> (string).

private delegate string MyDelegate(string s1, string s2);

2- Ví dụ với delegate

MathUtils.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class MathUtils
    {

        // (int, int)  -> (int)
        public static int sum(int a, int b)
        {
            return a + b;
        }

        // (int, int)  -> (int)
        public static int minus(int a, int b)
        {
            return a - b;
        }

        // (int, int)  -> (int)
        public static int multiple(int a, int b)
        {
            return a * b;
        }
 

    }

}
Ví dụ dưới đây, định nghĩa ra một delegate IntIntToInt đại diện cho các hàm có kiểu hàm là (int, int) -> (int).
MyFirstDelegate.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class MyFirstDelegate
    {

        // Định nghĩa ra một delegate
        // đại diện cho các hàm có kiểu (int, int) -> (int)
        delegate int IntIntToInt(int a, int b);



        public static void Main(string[] args)
        {

            // Tạo một đối tượng delegate.
            // Truyền vào tham số là một hàm có cùng kiểu hàm với delegate.
            IntIntToInt iiToInt = new IntIntToInt(MathUtils.sum);

            // Khi bạn thực thi một delegate.
            // Nó sẽ gọi hàm (hoặc phương thức) mà nó đại diện.
            int value = iiToInt(10, 20); // 30

            Console.WriteLine("Value = {0}", value);


            // Gán delegate đại diện cho phương thức khác.
            iiToInt = new IntIntToInt(MathUtils.multiple);

            value = iiToInt(10, 20); // 200

            Console.WriteLine("Value = {0}", value);

            Console.Read();

        }


    }


}
Chạy ví dụ:
Bạn cũng có thể tạo đối tượng delegate đại điện cho các hàm không tĩnh. Xem ví dụ:
HelloProgramTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
   class HelloProgramTest
   {

       // Tạo một Delegate
       // Đại diện cho các hàm kiểu (string) -> (string).
       private delegate string StringStringToString(string s);



       public static void Main(string[] args)
       {

           // Tạo một đối tượng HelloProgram.
           HelloProgram program = new HelloProgram();

           // Tạo một đối tượng Delegate đại diện cho hàm SayHello.
           StringStringToString ssToS = new StringStringToString(program.SayHello);


           // Test
           string greeting = ssToS("Tom");

           Console.WriteLine(greeting);

           Console.Read();
       }

   }


}

3- Hàm trả về một hàm

Trong C#, với Delegate bạn có thể tạo ra một hàm trả về một hàm (Thực tế là một hàm trả về một Delegate).
 
TaxFormulas.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
  class TaxFormulas
  {
      // Một Delegate đại diện cho các hàm kiểu (float) -> (float).
      public delegate float TaxFormula(float salary);

      // Công thức tính thuế của Mỹ (10% lương).
      public static float UsaFormula(float salary)
      {
          return 10 * salary / 100;
      }


      // Công thức tính thuế của Vietnam (5% lương).
      public static float VietnamFormula(float salary)
      {
          return 5 * salary / 100;
      }

      // Công thức tính thuế khác (7% lương).
      public static float DefaultFormula(float salary)
      {
          return 7 * salary / 100;
      }


      // Trả về một hàm tính lương dựa trên mã Quốc gia. (VN, USA, ..)
      public static TaxFormula GetSalaryFormula(string countryCode)
      {
          if (countryCode == "VN")
          {
              return TaxFormulas.VietnamFormula;
          }
          else if (countryCode == "USA")
          {
              return TaxFormulas.UsaFormula;
          }
          return TaxFormulas.DefaultFormula;
      }

  }

}
TaxFormulaTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
   class TaxFormulaTest
   {


       public static void Main(string[] args)
       {
           float salary = 1000f;


           // Công thức tính thuế theo quốc gia Việt Nam.
           TaxFormulas.TaxFormula formula = TaxFormulas.GetSalaryFormula("VN");

           float tax = formula(salary);

           Console.WriteLine("Tax in Vietnam = {0}", tax);

           // Công thức tính thuế tại Canada
           formula = TaxFormulas.GetSalaryFormula("CA");

           tax = formula(salary);

           Console.WriteLine("Tax in Canada = {0}", tax);

           Console.Read();
       }
   }

}
Chạy ví dụ:

4- Phương thức nặc danh

Dựa vào Delegate bạn có thể tạo ra một phương thức nặc danh, nó là một phương thức không có tên, chỉ có thân (body) của phương thức, nó là một khối lệnh (block) có 0 hoặc nhiều tham số, và có thể có kiểu trả về.
AnonymousMethod.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class AnonymousMethod
    {

        // Một Delegate đại diện cho các hàm kiểu: (float) -> (float).
        // Tính thuế dựa trên lương.
        public delegate float TaxFormula(float salary);


        public static TaxFormula GetTaxFormula(string countryCode)
        {
            if ("USA" == countryCode)
            {
                TaxFormula usaFormula = delegate(float salary)
                {
                    return 10 * salary / 100;
                };
                return usaFormula;
            }
            else if ("VN" == countryCode)
            {
                TaxFormula vnFormula = delegate(float salary)
                {
                    return 5 * salary / 100;
                };
                return vnFormula;
            }
            return delegate(float salary)
            {
                return 7 * salary / 100;
            };
        }


        public static void Main(string[] args)
        {
            string countryCode = "VN";
            float salary = 1000;

            TaxFormula formula = GetTaxFormula(countryCode);

            float tax = formula(salary);

            Console.WriteLine("countryCode = {0}, salary = {1} -> tax = {2}"
                                    , countryCode, salary, tax);

            Console.Read();
        }
    }

}
Chạy ví dụ:

5- Multicasting of a Delegate

C# cho phép bạn cộng (+) hai đối tượng Delegate với nhau để tạo thành một đối tượng Delegate mới. Chú ý rằng các đối tượng Delegate có thể cộng được phải có cùng kiểu hàm, và là hàm không có kiểu trả về. Khi đối tượng delegate mới được gọi, tất cả các delegate con cũng sẽ được thực thi. Đây được gọi là Multicasting của delegate.
Greetings.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class Greetings
    {

        // Kiểu hàm: (String) -> ()
        public static void Hello(String name)
        {
            Console.WriteLine("Hello " + name);
        }

        // Kiểu hàm: (String) -> ()
        public static void Bye(string name)
        {
            Console.WriteLine("Bye " + name);
        }


        // Kiểu hàm: (String) -> ()
        public static void Hi(string name)
        {
            Console.WriteLine("Hi " + name);
        }
    }

}
MulticastingTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class MulticastingTest
    {

        // Khai báo một kiểu Delegate

        public delegate void Greeting(string name);


        public static void Main(string[] args)
        {

            // Tạo các đối tượng Delegate.
            Greeting hello = new Greeting(Greetings.Hello);
            Greeting bye = new Greeting(Greetings.Bye);
            Greeting hi = new Greeting(Greetings.Hi);

            // Tạo một Delegate là hợp của 3 đối tượng trên.

            Greeting greeting = hello + bye;

            // Bạn cũng có thể sử dụng toán tử +=
            greeting += hi;

            // Gọi thực thi greeting
            greeting("Tom");

            Console.Read();


        }
    }

}
Chạy ví dụ:

6- Event là gì

Trong C#, Event là một đối tượng đặc biệt của Delegate, nó là nơi chứa các phương thức, các phương thức này sẽ được thực thi đồng loạt khi sự kiện xẩy ra. Bạn có thể thêm các phương thức sẽ được thực thi vào đối tượng Event của đối tượng phát ra sự kiện.

Ví dụ:

Class Button mô phỏng một button, nó định nghĩa ra một Event (sự kiện) để thông báo rằng nó vừa bị Click. Khi button bị click event sẽ được thực thi. Bạn cần thêm các phương thức cần thực thi vào cho đối tượng Event từ bên ngoài.
Button.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpEventsTutorial
{
   class Button
   {

       private string label;

       public delegate void ClickHander(Button source, int x, int y);

       // Định nghĩa ra sự kiện, nó chưa được gán giá trị.
       // Giá trị của nó được gán ở bên ngoài.
       public event ClickHander OnButtonClick;

       public Button(string label)
       {
           this.label = label;
       }


       // Mô phỏng Button này vừa bị Click.
       // Xác định vị trí x, y mà người dùng Click vào.
       public void Clicked()
       {
           Random random = new Random();

           // Một số ngẫu nhiên từ 1 -> 100
           int x = random.Next(1, 100);

           // Một số ngẫu nhiên từ 1 -> 20
           int y = random.Next(1, 20);

           // Thông báo sự kiện Button vừa bị Click.
           if (OnButtonClick != null)
           {
               OnButtonClick(this, x, y);
           }
         
       }
   }


}
Class MyApplication mô phỏng một ứng dụng có 2 button, "Open File" và "Save File".

Bạn cần viết phương thức để làm gì đó khi người dùng click vào "Open File", thêm phương thức này vào cho sự kiện OnButtonClick của nút openButton

Bạn cần viết phương thức để làm gì đó khi người dùng click vào "Save File", và thêm phương thức này vào cho sự kiện OnButtonClick của nút saveButton.
MyApplication.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpEventsTutorial
{
   class MyApplication
   {
       private Button openButton;
       private Button saveButton;
       private string fileName;

       // Mô phỏng một ứng dụng và có các Button.
       public MyApplication()
       {
           // Mô phỏng thêm 1 Button vào giao diện.
           this.openButton = new Button("Open File");

           // Mô phỏng thêm 1 Button vào giao diện.
           this.saveButton = new Button("Save File");


           // Khai báo sử lý khi có sự kiện 'Open Button' bị Click.
           // (Tính năng Multicasting của Delegate)
           this.openButton.OnButtonClick += this.OpenButtonClicked;

           // Khai báo sử lý khi sự kiện 'Save Button' bị Click.
           // (Tính năng Multicasting của Delegate)
           this.saveButton.OnButtonClick += this.SaveButtonClicked;
       }

       private void OpenButtonClicked(Button source, int x, int y)
       {
           // Mô phỏng mở ra một cửa sổ để chọn File để mở.
           Console.WriteLine("Open Dialog to Select a file");
           //
           this.fileName = "File" + x + "_" + y+".txt";
           Console.WriteLine("Openning file: "+ this.fileName);
       }

       private void SaveButtonClicked(Button source, int x, int y)
       {
           if(this.fileName== null)  {
               Console.WriteLine("No file to save!");
               return;
           }
           // Mô phỏng file đã được Save.
           Console.WriteLine("Saved file: " + this.fileName);
       }


       public static void Main(string[] args)
       {

           // Mô phỏng mở ứng dụng
           MyApplication myApp = new MyApplication();

           Console.WriteLine("User Click on Open Button ....");

           // Mô phỏng openButton bị click
           myApp.openButton.Clicked();

           Console.WriteLine("\n\n");
           Console.WriteLine("User Click on Save Button ....");


           // Mô phỏng saveButton bị click
           myApp.saveButton.Clicked();


           Console.Read();
         
       }
   }

}
 
Chạy ví dụ: