使用委派 (C# 程式設計手冊)
更新:2007 年 11 月
委派是可以安全封裝方法的型別,類似 C 和 C++ 中的函式指標。與 C 函式指標不同的是,委派是物件導向、型別安全,而且本身也十分安全。委派的型別是由委派的名稱所定義。下列範例宣告名為 Del 的委派,此委派可封裝使用字串做為引數的方法,並傳回 void:
public delegate void Del(string message);
委派物件一般是藉由提供委派將包裝的方法名稱來建構,或使用匿名方法建構。一旦委派經過執行個體化,該委派便會將對其方法的呼叫傳遞至該方法。呼叫端傳遞至委派的參數會傳遞至方法,而如果方法有傳回值的話,委派即會將方法的傳回值傳回至呼叫端。這種情況稱為叫用委派。經過執行個體化的委派可當做被包裝的方法本身叫用。例如:
// Create a method for a delegate.
public static void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
// Instantiate the delegate.
Del handler = DelegateMethod;
// Call the delegate.
handler("Hello World");
委派型別是衍生自 .NET Framework 中的 Delegate 類別。委派型別為密封,不但無法從其衍生任何物件,也不能從 Delegate 衍生自訂類別。由於執行個體化的委派為物件,因此可當做參數傳遞,或指派給屬性。如此一來,方法便可接受委派做為參數,並於稍後呼叫委派。這稱為非同步回呼,通常是用於在較長的處理序完成時通知呼叫端。以這種方式使用委派時,使用委派的程式碼並不需要知道使用方法的實作。這個功能與介面提供的封裝類似。如需詳細資訊,請參閱使用委派取代介面的時機。
另一種常用的回呼方式是定義自訂比較方法,然後將該委派傳遞至排序方法。如此可讓呼叫端的程式碼變成排序演算法的一部分。下列範例方法即使用 Del 型別做為參數:
public void MethodWithCallback(int param1, int param2, Del callback)
{
callback("The number is: " + (param1 + param2).ToString());
}
您接著可以將上面建立的委派傳遞至該方法:
MethodWithCallback(1, 2, handler);
主控台便會看到如下的輸出結果:
The number is: 3
若將委派當做抽象使用,MethodWithCallback 就不需要直接呼叫主控台,設計時完全無須將主控台納入考量。MethodWithCallback 就只是用來準備字串,然後將字串傳遞至另一個方法。由於委派的方法可使用任意數目的參數,因此這項功能十分強大。
將委派建構來包裝執行個體方法時,該委派會同時參考執行個體和方法。委派除了本身包裝的方法之外,並不會知道執行個體的型別,因此委派可參考任何型別的物件,只要物件上有符合委派簽章的方法即可。將委派建構來包裝靜態方法時,委派就只會參考該方法。以下面的宣告為例:
public class MethodClass
{
public void Method1(string message) { }
public void Method2(string message) { }
}
除了有之前說明的靜態 DelegateMethod 之外,現在還有 Del 執行個體可包裝的三個方法。
委派一經叫用時,可以呼叫多個方法。這稱為多點傳送。若要將一個額外的方法加入至委派的方法清單 (即叫用清單) 中,只要使用加法或加法指派運算子 ('+' 或 '+=') 加入兩個委派即可。例如:
MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;
//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;
這時 allMethodsDelegate 的叫用清單中包含三個方法,分別為 Method1、Method2 和 DelegateMethod。原有的三個委派 d1、d2 和 d3 則維持不變。當叫用 allMethodsDelegate 時,會按順序呼叫這三個方法。如果委派使用參考參數,則會循序將參考分別傳遞至這三個方法,而且只要有方法做了變更,都會反映在下一個方法中。當任何一個方法擲回未能在方法內攔截的例外狀況時,該例外狀況會傳遞至委派的呼叫端,而且將不會呼叫叫用清單中的任何後續方法。如果委派有傳回值和 (或) out 參數,則會傳回最後所叫用方法的傳回值和參數。若要從叫用清單中移除方法,請使用遞減或遞減指派運算子 ('-' 或 '-=')。例如:
//remove Method1
allMethodsDelegate -= d1;
// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;
由於委派型別是衍生自 System.Delegate,因此可在委派上呼叫該類別定義的方法和屬性。例如,若要在委派的叫用清單中找出方法的數目,您會寫成:
int invocationCount = d1.GetInvocationList().GetLength(0);
叫用清單中有多個方法的委派是衍生自 MulticastDelegate,其為 System.Delegate 的子類別。上面的程式碼可以使用任一類別,因為這兩個類別都支援 GetInvocationList。
多點傳送委派常用於事件處理。事件來源物件會傳送事件通知給已註冊可以接收該事件的接收者物件。若要註冊接收事件通知,接收者可建立處理該事件的方法,然後為該方法建立委派,再將委派傳遞至事件的來源。來源便會在事件發生時呼叫委派。委派接著會呼叫接收者的事件處理方法,並傳送事件資料。特定事件的委派型別是由事件來源所定義。如需詳細資訊,請參閱事件 (C# 程式設計手冊)。
比較兩種在編譯時期指派不同型別的委派,將會導致編譯錯誤。如果委派執行個體為靜態 System.Delegate 型別,則允許比較,但會在執行階段傳回 false。例如:
delegate void Delegate1();
delegate void Delegate2();
static void method(Delegate1 d, Delegate2 e, System.Delegate f)
{
// Compile-time error.
//Console.WriteLine(d == e);
// OK at compile-time. False if the run-time type of f
// is not the same as that of d.
System.Console.WriteLine(d == f);
}