Series C# hay ho: Callback trong C# – Delegate, Action, Predicate, Func

1. Nhắc lại về khái niệm callback

Nếu chưa có khái niệm rõ ràng về callback, các bạn nên đọc bài viết về Callback trong javascript mà mình đã viết.

Trong javascript, để callback, ta chỉ cần truyền 1 function vào như 1 parameter như sau:


function tangQua(qua) {
   return console.log("Đã tặng " + qua);
}

function oNha(vo, tangQua){
   var qua = "Quà đã nhận";
   tangQua(qua);
}

2. Delegate trong C#

Trong javascript, function cũng xem như là 1 object, nên ta có thể truyền function vào như 1 param. Khi ta thử viết lại chương trình trên trong C#, ta phải khai báo kiểu dữ liệu cho parameter truyền vào, vậy kiểu dữ liệu của function “tangqua” là gì?


public void tangQua(string qua) {
   Console.Write("Da tang " + qua);
}

 //Kiểu dữ liệu cho params "vo" là Person, kiểu dữ liệu cho tangQua là gì???
public void oNha(Person vo, tangQua){
   var qua = "Quà đã nhận";
   tangQua(qua);
}

Ở đây, để truyền function tangQua vào, ta phải sử dụng kiểu dữ liệu Delegate (Con trỏ hàm). Ta khai báo delegate theo cú pháp sau:

 public delegate void TangQuaDelegate(string qua);
 //delage + kiểu trả về (void) + tên delegate + (tham số truyền vào)

 public void tangQua(string qua) {
    Console.Write("Da tang " + qua);
 }

 public void oNha(Person vo, TangQuaDelegate tangQua)
 {
    var qua = "Quà đã nhận";
    tangQua(qua);
 }

Trong C#, delegate thường được sử dụng phối hợp với event. Các bạn làm Winform chắc ko lạ lùng với những đoạn code gọi event thế này:

 this.button1.Click += new System.EventHandler(this.button1_Click);

//Thật sự thì event handler chỉ là 1 delegate
 public delegate void EventHandler(object sender, EventArgs e);

//Function chúng ta truyền vào chính là function với 2 param sender, a, kiểu trả về la void
 private void button1_Click(object sender, EventArgs e) { }

Bài viết này chỉ tập trung về delegate nên các bạn có thể tìm hiểu thêm về delegate và event ở trang khác nhé.

http://tapchilaptrinh.vn/2012/07/16/delegates-va-events-trong-c/

3. Action, Predicate, Func trong C#

Ở phần này, mình xin giới thiệu về Action, Predicate, Func (Viết tắt là APF là cho nhanh) trong C#. Như các bạn đã đọc trong series C# hay ho, các lão developer trong Microsoft rất lười. Do đó họ luôn thêm thắt nhiều thứ trong C# để các developer chúng ta cũng lười như họ. APF thật ra là 1 cách lười hơn để chúng ta khai báo delegate.

Dưới đây là khái niệm cũng như cách dùng APF :

  • Action: Action<T in1, T in2, …>. Action tương đương 1 delegate với kiểu trả về là void, với in1, in2 là các params nhận vào.
  • Predicate: Predicate<T in>. Predicate tương đương 1 delegate với kiểu trả về là bool, với in là các param nhận vào. Predicate chỉ có thể nhận vào 1 param duy nhất.
  • Func: Func<T in1, T in2, … , T result>. Function tương đương 1 delegate với kiểu trả về do ta khai báo (result), in1, in2 là các params nhận vào. Func bắt buộc phải trả ra giá trị, không thể trả void.

Để dễ hiểu, các bạn hãy tham khảo bảng sau. Đây là bảng so sánh các khai báo bằng delegate, cùng với cách khai báo tương ứng bằng Action, Predicate, Func:

Delegate Action Predicate Func
delegate void VoidDelegate(int input1, bool input2) Action<int, bool>
delegate bool BoolDelegate(int input1) Predicate<int> Func<int, bool>
delegate int intDelegate(bool input2) Func<bool, int>
delegate void HelloWorldDelegate() Action
delegate bool HelloWorldBoolDelegate() Predicate  Func<bool>

Đoạn code ban đầu có thể được viết lại như sau, ngắn gọn hơn

public void tangQua(string qua) {
   Console.Write("Da tang " + qua);
}

public void oNha(Person vo, Action tangQua)
{
   var qua = "Quà đã nhận";
   tangQua(qua);
}

Có thể bạn sẽ thắc mắc: Ủa, mình có thấy người khác dùng Action, Predicate, … mấy đâu, biết làm gì?

pr-2

Bạn sẽ ngạc nhiên khi biết AFP, kết hợp với lambda expression và vài thứ khác nữa …., đã tạo nên sự “bá đạo” của LINQ, thứ mà Java thèm đỏ mắt mà không có, phải thêm vào ở Java SDK 8. Nhớ đón đọc bài sau “Series C# hay ho: Lambda Expression” để biết thêm chi tiết nhé.

13 thoughts on “Series C# hay ho: Callback trong C# – Delegate, Action, Predicate, Func”

  1. Cảm ơn bạn vì bài chia sẽ rất hay, từ ngữ viết cuốn hút dể hiểu, hi vọng bạn sẽ có nhiều bài viết hay như thế này.

    Like

  2. Anh chị cho em hỏi nhược điểm của delegate là gì ạ? Và ví dụ minh họa về nhược điểm đó ạ.Em cám ơn!

    Like

    1. Mình thấy không dùng từ nhược điểm được,có lẽ bất cập thì có.Mình tự học c# trong 3 tuần vừa đọc vừa rối..Dù khá là lởm do khi biên dịch xcode nó báo vài thứ thừa thải trong đám rối.Với hộ trở khá nhiều từ thư viện unity mình cũng xong.
      cũng gặp qua delegate này.
      delegate đại khái là phát sự kiện cho mọi lớp nào đó thích thì lắng nghe để làm việc gì đó..bất cập ở đây sự kiện là tĩnh static nên đám nào nghe nó cũng tĩnh luôn và không trở lại trạng thái cũ.Phần nữa nếu có nhiều sự kiện thì mình chưa rõ phải làm thế lào chắc phải viết lớp event trung gian.
      vd :phòng khách báo được phép tắt đèn thì một phòng ngủ nào đó tắt ngay nếu có đăng kí tắt.Và nó tắt luôn không thèm mở lại nếu lỡ bạn có cần nó tắt một lần rồi mở lại ngay thì mình thấy nó mệt chỗ này.
      vd code thử, và cách giải quyết cái ngu này theo kiểu ngu nốt :

      public class moveenemy1 : MonoBehaviour {
      public delegate void enemy1died();
      public static enemy1died baoe1chet;//hàm tĩnh đấy
      void OnCollisionEnter(Collision other)
      { iTween.Stop(gameObject);
      den.transform.position=mot;
      baoe1chet();//đây là cách nó báo qua cho con lính 2 và là tĩnh
      }

      **tiếp theo con lính 2 lắng nghe vụ con lính 1 chết ở môt lớp khác dưới đây thì lớp của con lính 2 sẽ như vầy:
      public class movee2 : MonoBehaviour {

      public delegate void enemy2died();
      public static enemy2died baoe2chet;
      //hai cái trên tương tự để báo cho con thứ 3.không liên quan.
      void OnEnable()
      {
      moveenemy1.baoe1chet -=Start;
      moveenemy1.baoe1chet += chaythat;
      }
      void OnDisable()
      {
      moveenemy1.baoe1chet -=Start;
      moveenemy1.baoe1chet -= chaythat;
      }
      hàm onenable và ondisable là của unity thì phải.^^.
      Lớp moveenemy1 sẽ được viết như trên và kích hoạt hàm star và chaythat…
      nếu sự kiện baoe1chet xảy ra.và việc của nó ở trên là gọi hàm star và hàm chaythat.Đồng thời mình tắt mở nó bằng cách viết dấu – hoặc + như trên theo tình trạng disable hay enable.

      Like

  3. em mới hiểu chắc dùng delegate này, có điều này em chưa rõ:
    /========code goc =======/
    public void tangQua(string qua) {
    Console.Write(“Da tang ” + qua);
    }

    //Kiểu dữ liệu cho params “vo” là Person, kiểu dữ liệu cho tangQua là gì???
    public void oNha(Person vo, tangQua){
    var qua = “Quà đã nhận”;
    tangQua(qua);
    }
    /======== =======/

    tại sao phải dùng hàm oNha() 2 parament. nếu hàm này khỏi tham số cũng chạy được phải ko a? em chưa rõ chỗ này.
    em nghĩ delegate như thế này. hình thức nó như biến int string, char… ép kiểu cho 1 tên nào đó. để gọi lại nó khi sử dụng.

    Like

      1. bạn ơi cho mình hỏi ở ví dụ cuối cùng bạn viết hàm oNha truyền vào Action tangQua tại sao Action này không truyền vào parameter nhỉ bạn vì mình đang hiểu phải là Action tangQua sau đó mới gán hàm tangQua bên trên cho nó được chứ. Mong bạn trả lời hic. Cảm ơn bài viêt rất hay nha bạn.

        public void tangQua(string qua) {
        Console.Write(“Da tang ” + qua);
        }

        public void oNha(Person vo, Action tangQua)
        {
        var qua = “Quà đã nhận”;
        tangQua(qua);
        }

        Like

Leave a comment