C# 程式設計/委託和事件
委託和事件是任何 Windows 或 Web 應用程式的基礎,允許開發人員“訂閱”使用者執行的特定操作。因此,您不必預期所有情況並過濾掉您想要的內容,而是選擇您想要接收通知的內容並對該操作做出反應。
委託是告訴 C# 在事件觸發時呼叫哪個方法的一種方式。例如,如果您單擊表單上的Button,程式將呼叫特定方法。這個指標就是一個委託。委託很好,因為您可以根據需要通知多個方法發生了事件。
事件是 .NET 框架發出的關於已發生操作的通知。每個事件都包含有關特定事件的資訊,例如,滑鼠單擊將說明在表單上的哪個位置單擊了哪個滑鼠按鈕。
假設您編寫了一個程式,該程式僅對Button單擊做出反應。以下是發生的事件順序
- 使用者在按鈕上按下滑鼠按鈕
- .NET 框架引發
MouseDown事件
- .NET 框架引發
- 使用者釋放滑鼠按鈕
- .NET 框架引發
MouseUp事件 - .NET 框架引發
MouseClick事件 - .NET 框架在
Button上引發Clicked事件
- .NET 框架引發
由於已訂閱了按鈕的單擊事件,程式會忽略其餘事件,而您的委託會告訴 .NET 框架在引發事件後呼叫哪個方法。
委託是 C# 中事件處理的基礎。它們是抽象和建立引用方法的物件的結構,可用於呼叫這些方法。委託宣告指定特定的方法簽名。可以將對一個或多個方法的引用新增到委託例項。然後可以“呼叫”委託例項,這實際上會呼叫已新增到委託例項的所有方法。一個簡單的例子
using System;
delegate void Procedure();
class DelegateDemo
{
public static void Method1()
{
Console.WriteLine("Method 1");
}
public static void Method2()
{
Console.WriteLine("Method 2");
}
public void Method3()
{
Console.WriteLine("Method 3");
}
static void Main()
{
Procedure someProcs = null;
someProcs += new Procedure(DelegateDemo.Method1);
someProcs += new Procedure(Method2); // Example with omitted class name
DelegateDemo demo = new DelegateDemo();
someProcs += new Procedure(demo.Method3);
someProcs();
}
}
在此示例中,委託由行delegate voidProcedure()宣告。此語句是一個完整的抽象。它不會生成執行任何工作的可執行程式碼,而只是宣告一個名為Procedure的委託型別,該型別不接受任何引數也不返回任何內容。接下來,在Main()方法中,語句Procedure someProcs = null; 例項化一個委託。賦值表示委託最初未引用任何方法。語句someProcs += newProcedure(DelegateDemo.Method1)和someProcs += newProcedure(Method2)將兩個靜態方法新增到委託例項。請注意,也可以省略類名,因為該語句位於DelegateDemo內部。語句someProcs += newProcedure(demo.Method3)將一個非靜態方法新增到委託例項。對於非靜態方法,方法名前面是物件引用。當呼叫委託例項時,將在新增方法到委託例項時提供的物件上呼叫Method3()。最後,語句someProcs()呼叫委託例項。現在將按新增順序呼叫已新增到委託例項的所有方法。
可以使用-=運算子刪除已新增到委託例項的方法。
someProcs -= new Procedure(DelegateDemo.Method1);
在 C# 2.0 中,新增或刪除委託例項中的方法引用可以縮短如下
someProcs += DelegateDemo.Method1;
someProcs -= DelegateDemo.Method1;
呼叫當前不包含任何方法引用的委託例項會導致NullReferenceException。
請注意,如果委託宣告指定返回值型別,並且將多個方法新增到委託例項,則呼叫委託例項將返回最後一個引用方法的返回值。其他方法的返回值無法檢索(除非在返回值之外還顯式儲存在某處)。
匿名委託是編寫委託程式碼的簡短方法,使用delegate關鍵字指定。委託程式碼還可以引用宣告它們的函式的區域性變數。匿名委託由編譯器自動轉換為方法。例如
using System;
delegate void Procedure();
class DelegateDemo2
{
static Procedure someProcs = null;
private static void AddProc()
{
int variable = 100;
someProcs += new Procedure(delegate
{
Console.WriteLine(variable);
});
}
static void Main()
{
someProcs += new Procedure(delegate { Console.WriteLine("test"); });
AddProc();
someProcs();
Console.ReadKey();
}
}
它們可以像普通方法一樣接受引數
using System;
delegate void Procedure(string text);
class DelegateDemo3
{
static Procedure someProcs = null;
private static void AddProc()
{
int variable = 100;
someProcs += new Procedure(delegate(string text)
{
Console.WriteLine(text + ", " + variable.ToString());
});
}
static void Main()
{
someProcs += new Procedure(delegate(string text) { Console.WriteLine(text); });
AddProc();
someProcs("testing");
Console.ReadKey();
}
}
輸出為
testing testing, 100
Lambda 表示式是一種更清晰的方式來實現與匿名委託相同的效果。其形式為
(type1 arg1, type2 arg2, ...) => expression
這等效於
delegate(type1 arg1, type2 arg2, ...)
{
return expression;
}
如果只有一個引數,則可以省略圓括號。也可以省略型別名稱,讓編譯器根據上下文推斷型別。在下面的示例中,str 是一個 字串,返回值型別是一個 整數
Func<string, int> myFunc = str => int.Parse(str);
這等效於
Func<string, int> myFunc = delegate(string str)
{
return int.Parse(str);
};
事件是一種特殊的委託,它可以促進事件驅動的程式設計。事件是類成員,無論其訪問說明符如何,都無法在類外部呼叫。因此,例如,宣告為公共的事件將允許其他類使用+=和-=操作事件,但觸發事件(即呼叫委託)只允許在包含事件的類中進行。一個簡單的示例
public delegate void ButtonClickedHandler();
class Button
{
public event ButtonClickedHandler ButtonClicked;
ButtonClicked += ()=>{Console.WriteLine("click simulation !");};
public void SimulateClick()
{
if (ButtonClicked != null)
{
ButtonClicked();
}
}
...
}
然後,另一個類中的方法可以透過將其中一個方法新增到事件委託來訂閱事件
Button b = new Button();
b.ButtonClicked += ButtonClickHandler;
private void ButtonClickHandler()
{
//Handle the event
}
即使事件被宣告為公共的,它也不能直接在包含它的類之外的任何地方觸發。