Try   HackMD

LOSoMan的委派(Delegate)講解(C#為主)

tags: LOSo講解 C#

委派的定義

Microsoft手冊上所說,委派(Delegate)為一種類型,只要有相同簽章和傳回型別的方法,都可與定義好的委派產生關聯。之後要使用時,先定義一個委派呼叫方法,然後帶入參數即可。

Microsoft手冊也說委派是可以安全封裝方法的類型。這句話對我而言,就好比委派一個可以裝載方法的容器,你可以把方法放入委派這個容器之中,自然也可以將方法從容器中卸載。而當我們要使用這個容器裡所有存放的方法,就只需要呼叫(Invoke)這個容器就行了。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

宣告委派

話不多說,先來看看如何宣告一個委派:

public delegate void NonDelegate(); public delegate void MyDelegate(int A); public delegate int MyIntDelegate(string B); public delegate string MyGenericDelegate<T>(T t1, int A);

就以上四個例子,可以看出delegate的宣告是很多變的。解說如下:

第1行,為一個無回傳值(void)也無須給值的委派宣告。
第2行,為一個無回傳值(void)但在使用時須給定一整數A的委派宣告。
第3行,為一個有整數回傳值(int),且須帶入字串B的委派宣告。
第4行,為一個泛型的委派宣告,回傳字串(string),輸入一泛型參數t1及一整數A。

不使用委派的程式

在不使用委派的情況下,我們要使用兩個靜態方法,則程式可能會是這樣:

static void Main(string[] args) { DoConsole(0); DoConsole(2); DoConsole(1); DoConsole2(0); DoConsole2(2); DoConsole2(1); } public static void DoConsole(int a) { switch (a) { case 0: Console.WriteLine("I"); break; case 1: Console.WriteLine("LOSo"); break; case 2: Console.WriteLine("am"); break; } } public static void DoConsole2(int b) { switch (b) { case 0: Console.WriteLine("I"); break; case 1: Console.WriteLine("hungry"); break; case 2: Console.WriteLine("am"); break; } }

之所以看起來簡潔,是因為我們只執行了兩種方法,而且是靜態的方法。但是試著想想,如果我們要執行多個方法,如果要一個一個使用,那就會非常麻煩。

委派的使用與時機

當我們將方法以參數的形式在丟時,為了安全就會使用委派的方式去使用。以下以簡短例子及說明示範。首先,宣告一個委派:

public delegate void MyDelegate(int A);

這個委派跟我們剛才第2行的程式一模一樣,意思也就當然一樣。

再來,我們宣告委派要呼叫的方法;

public static void DoConsole(int a) { switch (a) { case 0: Console.WriteLine("I"); break; case 1: Console.WriteLine("LOSo"); break; case 2: Console.WriteLine("am"); break; } }

以及

public static void DoConsole2(int b) { switch (b) { case 0: Console.WriteLine("I"); break; case 1: Console.WriteLine("hungry"); break; case 2: Console.WriteLine("am"); break; } }

這兩個靜態方法的輸入為整數a、b。

在委派宣告好且要呼叫的方法也宣告了以後,我們可以在主程式寫入:

static void Main(string[] args) { MyDelegate md; md = new MyDelegate(DoConsole); md(0); md(2); md(1); md = new MyDelegate(DoConsole2); md(0); md(2); md(1); }

我們先將DoConsole方法指定給委派md,然後分別對他做帶值的動作。
之後再將DoConsole2方法指定給委派md,然後分別對他做帶值的動作。

我們在執行後,主控台的輸出結果為:

由此可知,方法被我們利用委派的方式變成類似參數的形式使用,而我們可以利用委派呼叫(Invoke)去執行方法裡的內容,這樣就變得方便許多。

*注意: Invoke在此可加可不加,意思都一樣

委派的好處: 多重委派

假設我們要使用100次同一方法,那總不可能一個個去呼叫了吧,此時委派的好處就出現了。就以剛才的DoConsole方法為例,我要顯示100個I am LOSo,則委派的寫法就會如下:

這是我們要委派的方法:

public static void DoConsole1() { Console.WriteLine("I am LOSo"); }

然後,下面是我們多重委派的寫法:

static void Main(string[] args) { MyDelegate md = null; for(int x = 0; x < 100; x++) { md += DoConsole1; } md.Invoke(); }

我們可以將100次的多重委派寫成for迴圈,把方法堆疊100次,最後只要的呼叫一次,就可以完成100次的輸出。這也就驗證我們前面所說的,委派就如同一個把方法裝起來的容器,而當我們把100個一樣的方法存放在這個容器之後,一次呼叫這容器裡的所有方法,就會得到100組一樣的結果。

放入、移除委派方法,實例教學

觀念

我們可以很清楚委派(Delegate)既然像是個容器,可以放進去,自然就可以拿出來。而委派這個存放方法的容器,是非常錦然有序的,先進去的先執行,後進的後執行。

實作

以下我用Form去呈現這個現象,首先我們配置一個像這樣的UI介面。

按照慣例,先宣告一個委派(Delegate),並定義一個此委派型別的變數wld:

public delegate void WriteLineDelegate(); WriteLineDelegate wld = null;

接著,我們定義3個方法,在之後會按照我們給的順序加入或移除於wld:

private void WriteLine1() { rtbShow.AppendText("您好,LOSoMan\n"); } private void WriteLine2() { rtbShow.AppendText("您好,老闆\n"); } private void WriteLine3() { rtbShow.AppendText("您好,客戶\n"); }

接著我們實作Button Click所該做的事。

在"+"的Button做方法加進去的動作,像是:

wld += WriteLine1;

在"-"的Button做方法移出來的動作,像是:

wld -= WriteLine1;

接著,就是我們的Start Button啦,很簡單的做Invoke就行了:

if(wld != null) { wld.Invoke(); rtbShow.AppendText("---------------------\n"); }

大功告成,接著就可以測試了!!

測試

我們點2次LOSo的"+";然後點一次老闆的"+";然後再點2次LOSo的"+"。按Start後結果如下:


很明顯跟我們點的順序一樣,也就證明多重委派是有順序的。

那麼,我們再點一次老闆的"-",然後Start會如何呢?


當然就會連續輸出4次的"您好,LOSoMan"了。

這是一個有趣的測試方法,可以讓人對委派的運作更加深刻理解。

結論

當我們在呼叫使用方法時,可以使用委派的方式進行較為安全。委派的好處有許多,多重委派也只是其中一個比較重要的例子。所以說,委派可以視為一個容器對它進行操作,當然得你要使用就必須先宣告一個這樣的容器(委派)。