委托
delegate 是表示對具有特定參數列表和返回類型的方法的引用的類型。在實例化委托時,你可以將其實例與任何具有兼容簽名和返回類型的方法相關聯。你可以通過委托實例調用方法。
委托用於將方法作為參數傳遞給其他方法。事件處理程序就是通過委托調用的方法。你可以創建一個自定義方法,當發生特定事件時,某個類(如 Windows 控件)就可以調用你的方法。下面的示例演示了一個委托聲明:
public
delegate
int
PerformCalculation(
int
x,
int
y);
可將任何可訪問類或結構中與委托類型匹配的任何方法分配給委托。該方法可以是靜態方法,也可以是實例方法。這樣便能通過編程方式來更改方法調用,還可以向現有類中插入新代碼。
注意:在方法重載的上下文中,方法的簽名不包括返回值。但在委托的上下文中,簽名包括返回值。換句話說,方法和委托必須具有相同的返回類型。
將方法作為參數進行引用的能力使委托成為定義回調方法的理想選擇。例如,對比較兩個對象的方法的引用可以作為參數傳遞到排序算法中。由於比較代碼在一個單獨的過程中,因此可通過更常見的方式編寫排序算法。
委托概述
委托具有以下屬性:
使用委托
委托是安全封裝方法的類型,類似於 C 和 C++ 中的函數指針。與 C 函數指針不同的是,委托是面向對象的、類型安全的和可靠的。委托的類型由委托的名稱確定。以下示例聲明名為 Del 的委托,該委托可以封裝采用字符串作為參數並返回 void 的方法:
? 1public
delegate
void
Del(
string
message);
委托對象通常通過提供委托將封裝的方法的名稱或使用匿名方法構造。對委托進行實例化後,委托會將對其進行的方法調用傳遞到該方法。調用方傳遞到委托的參數將傳遞到該方法,並且委托會將方法的返回值(如果有)返回到調用方。這被稱為調用委托。實例化的委托可以按封裝的方法本身進行調用。例如:
? 1 2 3 4 5 6 7 8 9 10 11 12// 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());
}
然後,你可以將上面創建的委托傳遞到該方法:
? 1MethodWithCallback(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 時,將按順序調用所有三個方法。如果委托使用引用參數,引用將按相反的順序傳遞到所有這三個方法,並且一種方法進行的任何更改都將在另一種方法上見到。當方法引發未在方法內捕獲到的異常時,該異常將傳遞到委托的調用方,並且不會調用調用列表中的後續方法。如果委托具有返回值和/或輸出參數,它將返回上次調用方法的返回值和參數。若要刪除調用列表中的方法,請使用減法運算符或減法賦值運算符(“+”或“+=”)。例如:
? 1 2 3 4 5//remove Method1
allMethodsDelegate -= d1;
// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;
由於委托類型派生自 System.Delegate,因此可以在委托上調用該類定義的方法和屬性。例如,若要查詢委托調用列表中方法的數量,你可以編寫:
? 1int
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);
}
帶有命名方法的委托與帶有匿名方法的委托
委托可以與命名方法關聯。使用命名方法對委托進行實例化時,該方法將作為參數傳遞,例如:
? 1 2 3 4 5 6 7 8// Declare a delegate:
delegate
void
Del(
int
x);
// Define a named method:
void
DoWork(
int
k) {
/* ... */
}
// Instantiate the delegate using the method as a parameter:
Del d = obj.DoWork;
這被稱為使用命名的方法。使用命名方法構造的委托可以封裝靜態方法或實例方法。在早期版本的 C# 中,命名方法是對委托進行實例化的唯一方式。但是,在不希望付出創建新方法的系統開銷時,C# 使您可以對委托進行實例化,並立即指定委托在被調用時將處理的代碼塊。代碼塊可以包含 lambda 表達式或匿名方法。
備注:作為委托參數傳遞的方法必須與委托聲明具有相同的簽名。
委托實例可以封裝靜態或實例方法。
盡管委托可以使用 out 參數,但建議您不要將其用於多路廣播事件委托,因為您無法知道哪個委托將被調用。
示例 1
以下是聲明及使用委托的一個簡單示例。注意,委托 Del 和關聯的方法 MultiplyNumbers 具有相同的簽名
// Declare a delegate
delegate
void
Del(
int
i,
double
j);
class
MathClass
{
static
void
Main()
{
MathClass m =
new
MathClass();
// Delegate instantiation using "MultiplyNumbers"
Del d = m.MultiplyNumbers;
// Invoke the delegate object.
System.Console.WriteLine(
"Invoking the delegate using 'MultiplyNumbers':"
);
for
(
int
i = 1; i <= 5; i++)
{
d(i, 2);
}
// Keep the console window open in debug mode.
System.Console.WriteLine(
"Press any key to exit."
);
System.Console.ReadKey();
}
// Declare the associated method.
void
MultiplyNumbers(
int
m,
double
n)
{
System.Console.Write(m * n +
" "
);
}
}
輸出:
Invoking the delegate using 'MultiplyNumbers':
2 4 6 8 10
示例 2
在下面的示例中,一個委托被同時映射到靜態方法和實例方法,並分別返回特定的信息。
// Declare a delegate
delegate
void
Del();
class
SampleClass
{
public
void
InstanceMethod()
{
System.Console.WriteLine(
"A message from the instance method."
);
}
static
public
void
StaticMethod()
{
System.Console.WriteLine(
"A message from the static method."
);
}
}
class
TestSampleClass
{
static
void
Main()
{
SampleClass sc =
new
SampleClass();
// Map the delegate to the instance method:
Del d = sc.InstanceMethod;
d();
// Map to the static method:
d = SampleClass.StaticMethod;
d();
}
}
輸出:
A message from the instance method.
A message from the static method.