Những chủ đề tiến bộ trong C#

Delegate – Phần 1

Delegate có thể được xem như là kiểu đối tượng mới trong C#, mà có môt số

điểm quen thuộc với lớp.chúng tồn tại trong tình huống mà ta muốn truyền

phương thức xung quanh những phương thức khác.để minh hoạ ta xem dòng

mã sau:

int i = int.Parse("99");

Chúng ta quen với việc truyền dữ liệu đến một phương thức như là thông

số,vì vậy ý tường truyền phương thức như là thông số nghe có vẻ hơi lạ đối

với chúng ta.tuy nhiên có trường hợp mà ta có 1 phương thức mà làm 1 điều

gì đó, nhiều hơn là xử lí dữ liệu, phương thức đó có thể cần làm điều gì đó

mà liên quan đến việc thực thi phương thức khác.phức tạp hơn, bạn không

biết vào lúc nào thì phương thức thứ hai sẽ được biên dịch. thông tin đó chỉ

biết vào lúc chạy , và chính vì lí do đó mà phương thức 2 sẽ cần truyền vào

như là thông số cho phương thức đầu tiên.điều này nghe có vẻ hơi khó

hiểu,nhưng nó sẽ được làm rõ hơn trong 1 vài ví dụ sau:

Luồng bắt đầu: C# có thể bảo máy tính bắt đầu một chuỗi thực thi mới song

song với việc thực thi đương thời.1 chuỗi liên tiếp này gọi là luồng,và việc

bắt đầu này được làm bằng cách dùng phương thức, Start() trên 1 thể hiện

của lớp cơ sở.System.Threading.Thread. ( chi tiết hơn về luồng ở chương

5).khi chương trình bắt đầu chạy,nơi nó bắt đầu là main(). tương tự như vậy

khi bạn muốn máy tính chạy một chuỗi thực thi thì bạn phải báo cho máy

tính biết bắt đầu chạy là ở đâu. bạn phải cung cấp cho nó chi tiết của phương

thức mà việc thực thi có thể bắt đầu.-nói cách khác , phương thức

Thread.Start() phải lấy 1 thông số mà định nghĩa phương thức được thi hành

bởi luồng.

Lớp thư viện chung . khi 1 nhiệm vụ chứa đựng nhiệm vụ con mà mã của

các nhiệm vụ con này được viết trong các thư viện chỉ có sử dụng thư viện

mới biết nó làm gì.ví dụ , chúng ta muốn viết một lớp chứa một mảng đối

tuợng và sắp nó tăng dần. 1 phần công việc được lặp lại là lấy 2 đối tượng

trong lớp so sánh với nhau để xem đối tượng nào đứng truớc.nếu ta muốn

lớp có khả năng sắp xếp bất kì đối tượng nào, không có cách nào có thể làm

được việc so sánh trên .mã client dùng mảng đối tượng của ta sẽ bảo cho ta

biết cách so sánh cụ thể đối tượng mà nó muốn sắp xếp.nói cách khác , mã

client sẽ phải truyền cho lớp của ta phương thức thích hợp mà có thể được

gọi, để làm việc so sánh.

Nguyên tắc chung là: mã của ta sẽ cần thông báo cho thời gian chạy .NET

biết phương thức nào xử lí tình huống nào.

Vì thế chúng ta phải thiết lập những nguyên tắc mà đôi lúc , những phương

thức cần lấy chi tiết của phương thức khác như là thông số.kế tiếp chúng ta

sẽ minh họa cách làm điều đó.cách đơn giản nhất là truyền tên của phương

thức như là thông số.giả sử chúng ta muốn bắt đầu một luồng mới, và chúng

ta có phương thức được gọi là entrypoint(), mà ta muốn luồng bắt đầu chạy

từ đó:

void EntryPoint()

{

// làm những gì luồng mới cần làm

}

Có thể chúng ta bắt đầu luồng mới với một đoạn mã :

Thread NewThread = new Thread();

Thread.Start(EntryPoint); // sai

Thật sự đây là cách đơn giản nhất. trong một vài ngôn ngữ dùng cách này

như c và c++ ( trong c và c++ thông số entrypoint là con trỏ hàm)

Không may, cách thực thi trực tiếp này gây ra một số vấn đề về an toàn

kiểu.nhớ rằng ta đang lập trình hướng đôí tượng, phương thức hiếm khi nào

tồn tại độc lập , mà thường là phải kết hợp với phương thức khác trưóc khi

được gọi.vì vậy .NET không cho làm điều này.thay vào đó nếu ta muốn

truyền phương thức ta phải gói chi tiết của phương thức trong một loại đối

tượng mới là 1 delegate. delegate đơn giản là một kiểu đối tượng đặc biệt-

đặc biệt ở chổ ,trong khi tất cả đối tượng chúng ta định nghĩa trước đây chứa

đựng dữ liệu , thì delegate chứa đựng chi tiết của phương thức.

Dùng delegate trong C#

Đầu tiên ta phải định nghĩa delegate mà ta muốn dùng ,nghĩa là bảo cho

trình biên dịch biết loại phương thức mà delegate sẽ trình bày.sau đó ta tạo

ra các thể hiện của delegate.

Cú pháp

delegate void VoidOperation(uint x);

Ta chỉ định mỗi thể hiện của delegate có thể giữ một tham chiếu đến 1

phương thức mà chứa một thông số uint và trả vầ kiểu void.

Ví dụ khác : nếu bạn muốn định nghĩa 1 delegate gọi là twolongsOp mà

trình bày 1 hàm có 2 thông số kiểu long và trả về kiểu double. ta có thể viết :

delegate double TwoLongsOp(long first, long second);

Hay 1 delegate trình bày phương thức không nhận thông số và trả về kiểu

string

delegate string GetAString();

Cú pháp cũng giống như phương thức , ngoại trừ việc không có phần thân

của phương thức,và bắt đầu với delegate.ta cũng có thể áp dụng các cách

thức truy nhập thông thường trên một định nghĩa delegate -

public,private,protected ...

public delegate string GetAString();

Mỗi lần ta định nghĩa một delegate chúng ta có thể tạo ra một thể hiện của

nó mà ta có thể dùng đề lưu trữ các chi tiết của 1 phưong thức cụ thể.

Lưu ý : với lớp ta có 2 thuật ngữ riêng biệt : lớp để chỉ định nghĩa chung ,

đối tượng để chỉ một thể hiện của 1 lớp, tuy nhiên đối với delegate ta chỉ có

một thuật ngữ là '1 delegate' khi tạo ra một thể hiện của delegate ta cũng gọi

nó là delegate. vì vậy cần xem xét ngữ cảnh để phân biệt.

Đoạn mã sau minh hoạ cho 1 delegate:

private delegate string GetAString();

static void Main(string[] args)

{

int x = 40;

GetAString firstStringMethod = new GetAString(x.ToString);

Console.WriteLine("String is" + firstStringMethod());

// With firstStringMethod initialized to x.ToString(),

// the above statement is equivalent to saying

// Console.WriteLine("String is" + x.ToString());

}

Trong mã này , ta tạo ra delegate GetAString, và khởi tạo nó để nó tham

khảo đến phương thức ToString() của một biến nguyên x .chúng ta sẽ biên

dịch lỗi nếu cố gắng khởi tạo FirstStringMethod với bất kì phương thức nào

có thông số vào và kiểu trả về là chuỗi.

1 đặc tính của delegate là an toàn- kiểu ( type-safe) để thấy rằng chúng phải

đảm bảo dấu ấn ( signature) của phương thức được gọi là đúng.tuy nhiên 1

điều thú vị là, chúng không quan tâm kiểu của đối tượng phương thức là gì

khi gọi hoặc thậm chí liệu rằng phương thức đó là static hay là một phưong

thức thể hiện.

Để thấy điều này ta mở rộng đoạn mã trên, dùng delegate FirstStringMethod

để gọi các phương thức khác trên những đối tượng khác - 1 phương thức thể

hiện và 1 phương thức tĩnh .ta cũng dùng lại cấu trúc currency, và cấu trúc

currency đã có overload riêng của nó cho phương thức ToString().để xem

xét delegate với phương thức tĩnh ta thêm 1 phương thức tĩnh với cùng dấu

ấn như currency:

struct Currency

{

public static string GetCurrencyUnit()

{

return "Dollar";

}

Bây giờ ta sử dụng thể hiện GetAString như sau:

private delegate string GetAString();

static void Main(string[] args)

{

int x = 40;

GetAString firstStringMethod = new GetAString(x.ToString);

Console.WriteLine("String is " + firstStringMethod());

Currency balance = new Currency(34, 50);

firstStringMethod = new GetAString(balance.ToString);

Console.WriteLine("String is " + firstStringMethod());

firstStringMethod = new GetAString(Currency.GetCurrencyUnit);

Console.WriteLine("String is " + firstStringMethod());

Đoạn mã này chỉ cho ta biết làm thế nào để gọi 1 phương thức qua trung

gian là delegate,đăng kí lại delegate để tham chiếu đến một phương thức

khác trên 1 thể hiện khác của lớp.

Tuy nhiên ta vẫn chưa nắm rõ được quy trình truyền 1 delegate đến 1

phương thức khác, cũng như chưa thấy được lợi ích của delegate qua ví dụ

trên. như ta có thể gọi trực tiếp ToString() từ int hay currency mà không cần

delegate.ta cần những ví dụ phức tạp hơn để hiểu rõ delegate. ta sẽ trình bày

2 ví dụ : ví dụ 1 đơn giản sử dụng delegate để gọi vào thao tác khác..nó chỉ

rõ làm thế nào để truyền delegate đến phương thức và cách sử dụng mảng

trong delegate . ví dụ 2 phức tạp hơn là lớp BubbleSorter, mà thực thi 1

phương thức sắp xếp mảng đối tượng tăng dần. lớp này sẽ rất khó viết nếu

không có delegate.

Ví dụ SimpleDelegate

Trong ví dụ này ta sẽ tạo lớp MathOperations mà có vài phương thức static

để thực thi 2 thao tác trên kiểu double, sau đó ta dùng delegate để gọi những

phương thức này.lớp như sau:

class MathsOperations

{

public static double MultiplyByTwo(double value)

{

return value*2;

}

public static double Square(double value)

{

return value*value;

}

}

Sau đó ta gọi phương thức này như sau:

using System;

namespace Wrox.ProCSharp.AdvancedCSharp

{

delegate double DoubleOp(double x);

class MainEntryPoint

{

static void Main()

{

DoubleOp [] operations =

{

new DoubleOp(MathsOperations.MultiplyByTwo),

new DoubleOp(MathsOperations.Square)

};

for (int i=0 ; i

{

Console.WriteLine("Using operations[{0}]:", i);

ProcessAndDisplayNumber(operations[i], 2.0);

ProcessAndDisplayNumber(operations[i], 7.94);

ProcessAndDisplayNumber(operations[i], 1.414);

Console.WriteLine();

}

}

static void ProcessAndDisplayNumber(DoubleOp action, double

value)

{

double result = action(value);

Console.WriteLine(

"Value is {0}, result of operation is {1}", value, result);

}

Trong đoạn mã này ta khởi tạo 1 mảng delegate doubleOp.mỗi phần tử của

mảng được khởi động để tham chiếu đến 1 thao tác khác được thực thi bởi

lớp MathOperations.sau đó , nó lặp xuyên suốt mảng,ứng dụng mỗi thao tác

đến 3 kiểu giá trị khác nhau.điều này minh họa cách sử dụng delegate- là có

thể nhóm những phương thức lại với nhau thành mảng để sử dụng, để ta có

thể gọi một vài phương thức trong vòng lặp.

Chỗ quan trọng trong đoạn mã là chỗ ta truyền 1 delegate vào phương thức

ProcessAndDisplayNumber(), ví dụ:

ProcessAndDisplayNumber(operations[i], 2.0);

Ở đây ta truyền tên của delegate,nhưng không có thông số nào.cho rằng

operation[i] là 1 delegate :

operation[i] nghĩa là 'delegate',nói cách khác là phương thức đại diện cho

delegate

operation[i](2.0) nghĩa là ' gọi thực sự phương thức này, truyền giá trị vào

trong ngoặc'.

Phương thức ProcessAndDisplayNumber() được định nghĩa để lấy 1

delegate như là thông số đầu tiên của nó :

static void ProcessAndDisplayNumber(DoubleOp action, double value)

Sau đó khi ở trong phương thức này , ta gọi:

double result = action(value);

Thể hiện delegate action được gọi và kết quả trả về được lưu trữ trong result

chạy ví dụ ta có:

SimpleDelegate

Using operations[0]:

Value is 2, result of operation is 4

Value is 7.94, result of operation is 15.88

Value is 1.414, result of operation is 2.828

Using operations[1]:

Value is 2, result of operation is 4

Value is 7.94, result of operation is 63.0436

Value is 1.414, result of operation is 1.999396

Ví dụ BubleSorter

Sau đây ta sẽ xem 1 ví dụ cho thấy sự hữu ích của delegate. ta sẽ tạo lớp

bublesorter. lớp này thực thi 1 phương thức tĩnh,Sort(), lấy thông số đầu là 1

mảng đối tượng, và sắp xếp lại chúng tăng dần.ví dụ để sắp xếp 1 mảng số

nguyên bằng thuật toán Bubble sort :

///đây không phải là 1 phần của ví dụ

for (int i = 0; i < sortArray.Length; i++)

{

for (int j = i + 1; j < sortArray.Length; j++)

{

if (sortArray[j] < sortArray[i]) // problem with this test

{

int temp = sortArray[i]; // swap ith and jth entries

sortArray[i] = sortArray[j];

sortArray[j] = temp;

}

}

}

Thuật toán này tốt cho số nguyên, nhưng ta muốn phương thức sort() sắp

xếp cho mọi đối tượng,ta thấy vấn đề nằm ở dòng if(sortArray[j] <

sortArray[i]) trong đoạn mã trên.bởi ta muốn so sánh 2 đối tượng trên mảng

mà cái nào là lớn hơn.chúng ta có thể sắp xếp kiểu int, nhưng làm thế nào để

sắp xếp những lớp chưa biết hoặc không xác định cho đến lúc chạy.câu trả

lời là mã client, mà biết về lớp muốn sắp xếp, phải truyền 1 delegate gói

trong một phương thức sẽ làm công việc so sánh