Hì, đây là bài viết đầu tiên của năm 2011. Trước hết xin chúc bà con cô bác một năm mới vui vẻ, sức khỏe tràn đầy… chúc nhiêu đó thôi để dành tết mình chúc tiếp ^^. Còn nữa, chúc bà con học hành tấn tới nữa nha. Rùi, bây giờ quay trở lại phần chính.

List lại 3 bài viết trước cái đã:

Bài viết thứ 4, cũng là bài viết cuối cùng trong series bài viết về delegate này sẽ tiếp tục đào sâu hơn về delegate trong .NET.

More Delegates in .NET

Sau mỗi phiên bản .NET Framework ra đời, tính ứng dụng của delegates càng được mở rộng, cung cấp cho người dùng những giải pháp giúp đơn giản hóa công việc hơn. Bảng dưới đây sẽ liệt kê tên gọi và mô tả về các loại delegate được cung cấp trong phiên bản .NET 3.5 (trong .NET 4.0 còn có nhiều hơn thế này). Để có thể sử dụng delegate, trước hết chúng ta cần phải khai báo chúng. Trong quá trình khai báo, chúng ta quy định signature của function sẽ được đại diện bởi delegate mà chúng ta đang khởi tạo. Nhưng nhờ có những delegate được xây dựng sẵn, chúng ta có thể bỏ qua phần khai báo. Không chỉ cung cấp cho lập trình viên tập hợp delegates rất tiện ích này, .NET còn cung cấp các kiểu Generic delegate (delegate hoạt động với tham số có kiểu dữ liệu bất kì do người dùng chỉ định).

Delegate

Description

Action

Chứa một phương thức không có kiểu trả về và không có tham số.

Action<T>

Tương tự như Action nhưng được phép nhận một tham số

Action<T1, T2>>

Nhận được 2 tham số.

Action<T1, T2, T3>

Nhận được 3 tham số.

Action<T1, T2, T3, T4>

Nhận được 4 tham số.

AppDomainInitializer

Đại diện cho hàm callback, hàm này sẽ được gọi khi application domain được khởi tạo.

AssemblyLoadEventHandler

Represents the method that handles the AssemblyLoad event of an AppDomain.

Đại diện cho phương thức xử lý sự kiện AssemblyLoad của một AppDomain

AsyncCallback

Tham chiếu đến một phương thức sẽ được gọi khi một hoạt động không đồng thời nào đó hoàn tất.

Comparison<T>

Đại diện cho phương thức dùng để so sánh 2 đối tượng cùng kiểu dữ liệu.

ConsoleCancelEventHandler

Đại diện cho phương thức sẽ xử lý sự kiện CancelKeyPress của System.Console.

Converter<TInput, TOutput>

Đại diện cho một phương thức dùng để chuyển đổi kiểu của một đối tượng sang kiểu khác.

CrossAppDomainDelegate

Used by DoCallBack for cross-application domain calls.

Sử dụng DoCallBack trong các lời gọi tới các miền thuộc ứng dụng khác

EventHandler

Đại diện cho phương thức sẽ xử lý một sự kiện không có dữ liệu về sự kiện đó.

EventHandler<TEventArgs>

Đại diện cho một phương thức sẽ xử lý một sự kiện

Func<TResult>

Tham chiếu đến một phương thức có kiểu trả về là kiểu được quy định bởi tham số TResult, ngoài ra không nhận thêm tham số nào khác.

Func<T, TResult>

Tương tự như Func<TResult> nhưng được phép nhận một tham số.

Func<T1, T2, TResult>

Có thể nhận được 2 tham số.

Func<T1, T2, T3, TResult>

Có thể nhận được 3 tham số.

Func<T1, T2, T3, T4, TResult>

Có thể nhận được 4 tham số.

Predicate<T>

Đại diện cho một phương thức sẽ quy định một tập các yêu cầu, chuẩn mực và xác định liệu một đối tượng nhất định có đạt được yêu cầu này hay không.

ResolveEventHandler

Đại diện cho phương thức xử lý các sự kiện của một AppDomain: TypeResolve, ResourceResolve, và AssemblyResolve

UnhandledExceptionEventHandler

Đại diện cho phương thức sẽ xử lý sự kiện gây ra bởi một ngoại lệ không xác định.

Goal

Trong bài viết này, tôi muốn trình bày những kĩ thuật khác nhau khi sử dụng custom delegates hoặc pre-define delegates. Chúng ta có thể sắp xếp các loại delegates được liệt kê ở bảng trên ra thành các loại sau đây:

1. Generic type function and methods.

  • Action
  • Func
  • Converter
  • Comparision
  • Predicate

2. Event handler – xử lý sự kiện

  • AssemblyLoadEventHandler
  • ConsoleCancelEventHandler
  • EventHandler
  • EventHandler<TEventArgs>
  • ResolveEventHandler
  • UnhandledExceptionEventHandler

3. Others – các loại khác

  • AppDomainInitializer
  • AsyncCallback (chúng ta đã từng làm quen với delegate này trong phần 2)
  • CrossAppDomainDelegate

Trong bài viết này, tôi sẽ chỉ nói về 3 delegate đầu tiên là Action, Func, Converter. Tôi muốn mô tả cách sử dụng những delegate này bằng nhiều cách khác nhau. Mặc dù compiler sẽ sinh ra những đoạn code tương tự nhau, nhưng đứng từ góc độ lập trình, các kĩ thuật này khác nhau rất nhiều.

Action và Func

Action có thể đại diên cho bất kì phương thức nào return void và có thể nhận tối đa 4 tham số (trong .NET 4.0 là 8). Action<T1,T2>: T1 và T2 là các tham số và chúng có kiểu dữ liệu bất kì. Func cũng giống như Action, chỉ khác ở chỗ Func trả về giá trị của một kiểu bất kì. Func<T1,T2,TResult>: T1, T2 là các tham số có kiểu bất kì và TResult là kiểu trả của giá trị trả về. Như vậy, sẽ không có vấn đề gì nếu tôi chỉ giới thiệu về Action vì Func cũng tương tự như vậy.

Ví dụ sử dụng delegate thông thường

Bên trong namespace của project, tôi khai báo delegate của mình:

public delegate void ShootingHandler(int times);

delegate này return void và nhận một tham số kiểu int. Và bên trong class dưới đây sử dụng biến “shoot” có kiểu của delegate này.

Còn đây là class:

public class GunClassicDelegate
{
    private ShootingHandler shoot;

    public string Name { get; set; }
    public GunClassicDelegate(string name)
    {
        Name = name;
        switch (Name.ToLower())
        {
            case "automatic gun":
                shoot = new ShootingHandler(ShootAutomatic);
                break;
            case "paint gun":
                shoot = new ShootingHandler(ShootPaint);
                break;
            default:
                shoot = new ShootingHandler(ShootSingle);
                break;
        }
    }
    public void Fire(int times)
    {
        shoot(times);
    }

    private void ShootAutomatic(int times)
    {
        Console.WriteLine("Automatic shooting: ");
        for (int i = 0; i < times; i++)
            Console.Write("Biff...");
        Console.WriteLine();
    }

    private void ShootSingle(int times)
    {
        Console.WriteLine("Single action shooting: ");
        for (int i = 0; i < times; i++)
            Console.WriteLine("Bang");
    }

    private void ShootPaint(int times)
    {
        Console.WriteLine("Splashing paint ");
        for (int i = 0; i < times; i++)
            Console.WriteLine("Bloop");
    }
}

Trong constructor, tùy thuộc vào tên được truyền vào, tôi sẽ truyền vào thể hiện của delegate một trong các hàm sau:

  1. ShootAutomatic
  2. ShootSingle
  3. ShootPaint

Như các bạn thấy, tất cả đều diễn ra hợp lệ bởi các hàm này đều có cùng signature với delegate. Tôi sử dụng một hàm public Fire, trong hàm này delegate sẽ được gọi.

Sử dụng Action delegate do .NET xây dựng

public class GunGenericDelegate
{
    private Action<int> shoot;

    public string Name { get; set; }
    public GunGenericDelegate(string name)
    {
        Name = name;
        switch (Name.ToLower())
        {
            case "automatic gun":
                shoot = ShootAutomatic;
                break;
            case "paint gun":
                shoot = ShootPaint;
                break;
            default:
                shoot = ShootSingle;
                break;
        }
    }

    public void Fire(int times)
    {
        shoot(times);
    }

    private void ShootAutomatic(int times)
    {
        Console.WriteLine("Automatic shooting: ");
        for (int i = 0; i < times; i++)
            Console.Write("Biff...");
        Console.WriteLine();
    }

    private void ShootSingle(int times)
    {
        Console.WriteLine("Single action shooting: ");
        for (int i = 0; i < times; i++)
            Console.WriteLine("Bang");
    }

    private void ShootPaint(int times)
    {
        Console.WriteLine("Splashing paint ");
        for (int i = 0; i < times; i++)
            Console.WriteLine("Bloop");
    }
}

Như các bạn thấy, chúng ta không cần phải khai báo thêm bất kì delegate nào. Thay vào đó, tôi khai báo một biến private như sau:

public Action<int> shoot;

Việc này báo cho compiler biết rằng biến shoot là một thể hiện của một delegate kiểu void và nhận một tham số kiểu int. Constructor trở nên đơn giản hơn, ta chỉ việc gán function cho biến shoot:

shoot = ShootSingle;

Sử dụng Action với từ khóa delegate

public class GunGenericInLineDelegate
{
    public string Name { get; set; }
    private Action<int> shoot;
    public void Fire(int times) { shoot(times); }

    public GunGenericInLineDelegate(string name)
    {
        Name = name;
        switch (Name.ToLower())
        {

            case "automatic gun":
                shoot = delegate(int times)
                {
                    Console.WriteLine("Automatic shooting: ");
                    for (int i = 0; i < times; i++)
                        Console.Write("Biff...");
                    Console.WriteLine();
                };
                break;
            case "paint gun":
                shoot = delegate(int times)
                {
                    Console.WriteLine("Splashing paint ");
                    for (int i = 0; i < times; i++)
                        Console.WriteLine("Bloop");
                };
                break;
            default:
                shoot = delegate(int times)
                {
                    Console.WriteLine("Single action shooting: ");
                    for (int i = 0; i < times; i++)
                        Console.WriteLine("Bang");
                };
                break;
        }
    }
}

Trong ví dụ trên, tôi bỏ qua việc khai báo 3 private methods. Nó giúp cho chương trình của tôi ngắn hơn nhiều. Và thay vào đó, chức năng của các function bị lượt bỏ đi sẽ được khai báo ngay trong constructor. Cú pháp của kĩ thuật này rất đơn giản:

shoot = delegate(int times)
{ //the functionality goes here };

Compiler biết rằng “shoot” có kiểu delegate. Vì vậy nên nó cho phép bạn sử dụng từ khóa “delegate” và sử dụng nó để khai báo inline function.

Sử dụng Action delegate và LAMBDA expression

 

public class GunGenericInLineLambda
{

    public string Name { get; set; }
    public Action<int> shoot;
    public void Fire(int times) { shoot(times); }

    public GunGenericInLineLambda(string name)
    {
        Name = name;
        switch (Name.ToLower())
        {
            case "automatic gun":
                shoot = (times) =>
                {
                    Console.WriteLine("Automatic shooting: ");
                    for (int i = 0; i < times; i++)
                        Console.Write("Biff...");
                    Console.WriteLine();
                };
                break;
            case "paint gun":
                shoot = (times) =>
                {
                    Console.WriteLine("Splashing paint ");
                    for (int i = 0; i < times; i++)
                        Console.WriteLine("Bloop");
                };
                break;
            default:
                shoot = (times) =>
                {
                    Console.WriteLine("Single action shooting: ");
                    for (int i = 0; i < times; i++)
                        Console.WriteLine("Bang");
                };
                break;
        }
    }
}

Lambda expressions đã được giới thiệu từ nhiều năm trước, và đến thời điểm này nó đang rất được các lập trình viên C# ưa dùng. Cách sử dụng của nó cũng tương tự như ví dụ trên. Thay vì sử dụng delegate function, tôi sử dụng Lamda expression. Các bạn có thể tìm hiểu thêm về Lambda expression từ rất nhiều các tài liệu trên mạng. Cú pháp của sử dụng lambda expression như sau:

(i) => { //functionality goes here}

Trong đó, (i) là danh sách các tham số mà chúng ta cần sử dụng bên trong hàm. Nếu delegate function này không yêu cầu tham số, ta chỉ cần khai báo (). => {…….} .

Bên trong cặp dấu ngoặc nhọn, bạn sẽ cần phải khai báo các chức năng cho delegate function, và các chức năng này sử dụng tham số mà bạn truyền vào. Một lần nữa, bởi vì giữa việc sử dụng Action và Func chỉ khác nhau ở giá trị trả về, do đó tôi sẽ không đưa ví dụ về việc sử dụng Func.

Dưới đây là đoạn chương trình chính:

public class ActionExample
{
    static void Main(string[] args)
    {
        //part - classic:
        Console.WriteLine("Shooting classic delegate:");
        GunClassicDelegate gunClassic1 = new GunClassicDelegate("automatic gun");
        GunClassicDelegate gunClassic2 = new GunClassicDelegate("paint gun");
        GunClassicDelegate gunClassic3 = new GunClassicDelegate("hand gun");

        gunClassic1.Fire(4);
        gunClassic2.Fire(4);
        gunClassic3.Fire(4);

        //part - generic delegate:
        Console.WriteLine("Shooting generic delegate:");
        GunGenericDelegate gunGeneric1 = new GunGenericDelegate("automatic gun");
        GunGenericDelegate gunGeneric2 = new GunGenericDelegate("paint gun");
        GunGenericDelegate gunGeneric3 = new GunGenericDelegate("hand gun");

        gunGeneric1.Fire(4);
        gunGeneric2.Fire(4);
        gunGeneric3.Fire(4);

        //part - generic inline delegate:
        Console.WriteLine("Shooting generic inline delegate:");
        GunGenericInLineDelegate gunGenericInLine1 =
              new GunGenericInLineDelegate("automatic gun");
        GunGenericInLineDelegate gunGenericInLine2 =
              new GunGenericInLineDelegate("paint gun");
        GunGenericInLineDelegate gunGenericInLine3 =
              new GunGenericInLineDelegate("hand gun");

        gunGenericInLine1.Fire(4);
        gunGenericInLine2.Fire(4);
        gunGenericInLine3.Fire(4);

        //part - generic lambda delegate:
        Console.WriteLine("Shooting generic lambda delegate:");
        GunGenericInLineLambda gunGenericInLineLambda1 =
              new GunGenericInLineLambda("automatic gun");
        GunGenericInLineLambda gunGenericInLineLambda2 =
              new GunGenericInLineLambda("paint gun");
        GunGenericInLineLambda gunGenericInLineLambda3 =
              new GunGenericInLineLambda("hand gun");

        gunGenericInLineLambda1.Fire(4);
        gunGenericInLineLambda2.Fire(4);
        gunGenericInLineLambda3.Fire(4);

        Console.Read();
    }
}

Khi chạy chương trình bạn sẽ thấy không hề có sự khác biệt trong kết quả xuất ra màn hình ở mỗi ví dụ.

Converter

Delegate Converter<TInput,TOutput> là một tham số trong phương thức Array.ConvertAll<TInput,TOutput> (TInput [] arraytoconvert, Converter<TInput, TOutput> converter). Mới nhìn vào có thể bạn sẽ nghĩ nó khá phức tạp, tuy nhiên nó lại thực sự rất đơn giản. Nếu bạn muốn convert một mảng chứa các đối tượng từ kiểu này sang kiểu khác, bạn chỉ cần sử dụng generic function ConvertAll của đối tượng Array. Phương thức này nhận 2 tham số. Tham số đầu tiên là mảng chứa các đối tượng mà bạn muốn chuyển đổi kiểu. Tham số thứ 2 là delegate trỏ vào hàm sẽ thực hiện việc chuyển đổi kiểu đó, tuy nhiên chúng ta không cần thiết phải tạo thêm một delegate nào khác bởi .NET đã xây dựng sẵn delegate Converter đảm nhiệm chức năng này rồi.

Dưới đây là những class mà chúng ta cần:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(string firstname, string lastname)
    {
        FirstName = firstname;
        LastName = lastname;
    }
}

public class PersonInfo
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName { get { return FirstName + " " + LastName; } }

    public static PersonInfo ConvertToPersonInfo(Person person)
    {
        PersonInfo res = new PersonInfo();
        res.FirstName = person.FirstName;
        res.LastName = person.LastName;
        return res;
    }
}

Ở đây có 2 class, Person và PersonInfo. Chỉ có một điểm khác biệt nhỏ giữa 2 class này, tuy nhiên đối với compiler thì nó hoàn toàn khác nhau. Nếu tôi muốn chuyển đổi một mảng Person thành mảng chứa các đối tượng kiểu PersonInfo, tôi phải sử dụng delegate Converter. Do đó, tôi đã tạo ra một static function ConvertToPersonInfo, hàm này sẽ nhận tham số kiểu Person và return đối tượng kiểu PersonInfo. Hàm này thuộc về class PersonInfo. Trong đoạn code ví dụ dưới đây, tôi sẽ trình bày 3 hàm Main khác nhau: MainClassic, MainLambda, MainDelegate. Mỗi hàm Main có cách sử dụng delegate khác nhau. Bạn cần phải chọn một trong số 3 hàm Main này để biên dịch chương trình.

Trong hàm MainClassic sẽ tham chiếu đến phương thức ConvertToPersonInfo. Còn 2 hàm Main còn lại sử dụng từ khóa delegate và lambda expression thay vì tham chiếu đến một hàm đã tạo ra sẵn.

class Program
{
    static void MainClassic(string[] args)
    {

        Person[] people = { new Person("ed", "g"), 
                            new Person("al", "g") };

        PersonInfo[] newpeople = Array.ConvertAll(people,
            new Converter<Person, PersonInfo>(PersonInfo.ConvertToPersonInfo));
    }

    static void MainLambda(string[] args)
    {

        Person[] people = { new Person("ed", "g"), 
                            new Person("al", "g") };

        PersonInfo[] newpeople = Array.ConvertAll(people, (p) =>
        {
            PersonInfo pi = new PersonInfo();
            pi.LastName = p.LastName;
            pi.FirstName = p.FirstName;
            return pi;
        });
    }

    static void MainDelegate(string[] args)
    {

        Person[] people = { new Person("ed", "g"), 
                            new Person("al", "g") };

        PersonInfo[] newpeople = Array.ConvertAll(people, delegate(Person p)
        {
            PersonInfo pi = new PersonInfo();
            pi.LastName = p.LastName;
            pi.FirstName = p.FirstName;
            return pi;
        });
    }
}

Conclusions

Còn có rất nhiều chuyện để nói về delegate trong C#, nhưng tôi sẽ kết thúc series bài viết này tại đây. Tôi chỉ muốn mang lại cho các bạn một cách nhìn đơn giản về delegate. Như các bạn đã thấy, bạn có thể lựa chọn rất nhiều cách khác nhau để sử dụng delegate trong chương trình của mình. Nếu không có delegate được xây dựng sẵn, bạn có thể tạo ra một cái để sử dụng cho mình. Ngoài ra, bạn có thể tận dụng những tiện lợi của các hàm inline thay vì phải tạo ra một hàm, đặt tên cho nó và gán nó cho delegates. Bạn cũng có thể sử dụng của lambda expression. Một lần nữa, đây là bài viết cuối cùng trong series bài viết của tôi về delegate, rất cảm ơn vì các bạn đã ủng hộ.

Bài viết nguyên mẫu: Delegate in C# – Part 4.