# LOSoMan的委派(Delegate)講解(C#為主)
###### tags: `LOSo講解` `C#`
## 委派的定義
[Microsoft手冊](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/delegates/using-delegates)上所說,委派(Delegate)為一種類型,只要有相同簽章和傳回型別的方法,都可與定義好的委派產生關聯。之後要使用時,先定義一個委派呼叫方法,然後帶入參數即可。
[Microsoft手冊](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/delegates/using-delegates)也說委派是可以安全封裝方法的類型。這句話對我而言,就好比委派一個可以裝載方法的容器,你可以把方法放入委派這個容器之中,自然也可以將方法從容器中卸載。而當我們要使用這個容器裡所有存放的方法,就只需要呼叫(Invoke)這個容器就行了。

## 宣告委派
話不多說,先來看看如何宣告一個委派:
```csharp=1
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。
## 不使用委派的程式
在不使用委派的情況下,我們要使用兩個靜態方法,則程式可能會是這樣:
```csharp=1
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;
}
}
```
之所以看起來簡潔,是因為我們只執行了兩種方法,而且是靜態的方法。但是試著想想,如果我們要執行多個方法,如果要一個一個使用,那就會非常麻煩。
## 委派的使用與時機
當我們將方法以參數的形式在丟時,為了安全就會使用委派的方式去使用。以下以簡短例子及說明示範。首先,宣告一個委派:
```csharp=1
public delegate void MyDelegate(int A);
```
這個委派跟我們剛才第2行的程式一模一樣,意思也就當然一樣。
再來,我們宣告委派要呼叫的方法;
```csharp=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;
}
}
```
以及
```csharp=1
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。
在委派宣告好且要呼叫的方法也宣告了以後,我們可以在主程式寫入:
```csharp=1
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,則委派的寫法就會如下:
這是我們要委派的方法:
```csharp=1
public static void DoConsole1()
{
Console.WriteLine("I am LOSo");
}
```
然後,下面是我們多重委派的寫法:
```csharp=1
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:
```csharp=1
public delegate void WriteLineDelegate();
WriteLineDelegate wld = null;
```
接著,我們定義3個方法,在之後會按照我們給的順序加入或移除於wld:
```csharp=1
private void WriteLine1()
{
rtbShow.AppendText("您好,LOSoMan\n");
}
private void WriteLine2()
{
rtbShow.AppendText("您好,老闆\n");
}
private void WriteLine3()
{
rtbShow.AppendText("您好,客戶\n");
}
```
接著我們實作Button Click所該做的事。
在"+"的Button做方法加進去的動作,像是:
```csharp=1
wld += WriteLine1;
```
在"-"的Button做方法移出來的動作,像是:
```csharp=1
wld -= WriteLine1;
```
接著,就是我們的Start Button啦,很簡單的做Invoke就行了:
```csharp=1
if(wld != null)
{
wld.Invoke();
rtbShow.AppendText("---------------------\n");
}
```
大功告成,接著就可以測試了!!
### 測試
我們點2次LOSo的"+";然後點一次老闆的"+";然後再點2次LOSo的"+"。按Start後結果如下:

很明顯跟我們點的順序一樣,也就證明多重委派是有順序的。
那麼,我們再點一次老闆的"-",然後Start會如何呢?

當然就會連續輸出4次的"您好,LOSoMan"了。
這是一個有趣的測試方法,可以讓人對委派的運作更加深刻理解。
## 結論
當我們在呼叫使用方法時,可以使用委派的方式進行較為安全。委派的好處有許多,多重委派也只是其中一個比較重要的例子。所以說,委派可以視為一個容器對它進行操作,當然得你要使用就必須先宣告一個這樣的容器(委派)。