一、簡單的委托 1.1 委托的聲明: C#當中,委托(delegate)是一種方法封裝,也即委托對象可以作為一種傳遞方法的變數來使用。 委托也算是一種類,與類是平級的存在。在類中寫delegate對象當然是允許的,畢竟C#也允許類中類。但是一般不這樣做,委托的聲明最好與類聲明平級。 聲明: 方法執行 ...
一、簡單的委托
1.1 委托的聲明:
C#當中,委托(delegate)是一種方法封裝,也即委托對象可以作為一種傳遞方法的變數來使用。
委托也算是一種類,與類是平級的存在。在類中寫delegate對象當然是允許的,畢竟C#也允許類中類。但是一般不這樣做,委托的聲明最好與類聲明平級。
聲明:
public delegate void ActionName(); 關鍵字 返回類型 委托變數名(函數參數)
方法執行,有兩種方式,效果相同:
TestHandler.Invoke(實參);
TestHandler(實參);
示例:
public delegate void TestHandler(string str); class Program { static void Main(string[] args) { TestHandler t = delegate (string str) { Console.WriteLine($"There will show : {str}"); }; // TestHandler t = str => Console.WriteLine($"There will show : {str}"); lambda表達式,上行的語法糖。 t.Invoke("Hello world !"); } }
在說到具體如何實現委托之前,先簡單說明一下Action、Func是什麼。
C#規則中,Action和Func都是方法對象(委托類型),兩者的區別僅在於有無返回類型。
public delegate void Action(); public delegate TResult Func<out TResult>();
形參是允許添加多個的:
Action<int, string, double> action /*============== F12 ================*/ public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
Func<string, string, string> func
/*============== F12 ================*/
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); //(其中,TResult參數總寫在形參說明的最後)
// 註:尖括弧表示裡面是泛型,在聲明中,需要在<>內寫入具體的類型。
實例化這個對象,這樣寫:
Action<T1, T2> action = delegate(T1 arg1, T2 arg2) { 具體實現 }; 類型 變數名稱 關鍵字 (方法入參) {..}
例如可以這樣聲明:
Action<int, string, double> action = delegate(int a, string b, double c) { };
Func<string, string, string> func = delegate { return "test.."; };
註:入參可以省略不寫,但是註意可能會有一些小陷阱,見下例[1]:
// 啟動新線程,.NET2.0有4個線程構造函數: public Thread(ParameterizedThreadStart start) public Thread(ThreadStart start) public Thread(ParameterizedThreadStart start, int maxStackSize) public Thread(ThreadStart start, int maxStackSize) // 涉及的兩個委托類型是: public delegate void ThreadStart() public delegate void ParameterizedThreadStart(object obj) // 創建一個新線程的嘗試: new Thread(delegate () { //..}); new Thread(delegate (object o) { //..}); new Thread(delegate { //..});
// 編譯器將不知道第三行delegate省卻的形參到底是哪個委托,因為它既可能是ThreadStart,又可以是ParameterizedThreadStart。
1.2 訂閱的容器 event
首先,在此需要認同一個觀點:
屬性不是欄位——很多時候,屬性是欄位的包裝器,保護欄位不被濫用。而包裝器永遠不可能是包裝的東西。
知道如何寫封裝的方法後,接下來便是——如何去打包這些方法。即:如何把這些方法添加(或者說是整合)到某個對象上。這種“添加”,一般需要一個“盒子”,它的關鍵字名稱叫作事件(event)。
把方法裝進事件當中,C#規定了這兩個符號,
◆ += 表示“訂閱”,-= 表示“退訂”;
◆ 實例化後,該實例的事件成員只能出現在 += 或 -= 符號的左邊。
◆ 只要訂閱了,事件一旦執行,所有訂閱的方法都會執行。
姑且可以把事件當成一個方法集合(+= 看做Add, -= 看做Remove)。
聲明格式見下(關鍵字 delegate類型 事件變數名稱 = delegate {..}):
public event EventHandler click = delegate {};
( * 上行省卻的方法參數,編譯器將參考”方法規則” )
以上聲明方法是賦值式的,即會替換掉該事件當前訂閱的所有內容,此處要註意。
示例:
class Program { static void Main(string[] args) { Test a = new Test(); a.TestEvent += delegate() { Console.WriteLine("First Action."); }; a.TestEvent += SecondAction; a.TestEvent += () => { Console.WriteLine("Third Action."); }; a.TestAction();
Console.WriteLine("================"); a.TestEvent -= SecondAction; a.TestAction(); } public static void SecondAction() { Console.WriteLine("Second Action."); } } class Test { public event Action TestEvent; public void TestAction() { if (TestEvent != null) TestEvent(); } }
這個例子是不是非常眼熟,和控制項的事件訂閱是一樣的。
比如CheckBox的勾選事件:
_checkBox.Checked += _checkBox_Checked; // TIPS:輸入+=後長按Tab鍵,IDE將自動生成方法;+=後按一次Tab,自動生成lambda表達式方法。 … private void _checkBox_Checked(object sender, RoutedEventArgs e) { }
同理滑鼠點擊事件、雙擊等等,只不過像是監聽滑鼠操作(點下左鍵、鬆開左鍵等等)的這一項交由C#來處理了。
註意:如果多次初始化界面,同一變數再次訂閱,將訂閱多次哦。
==================================================================================
下節預告:
屬性不是欄位——很多時候,屬性是欄位的包裝器,保護欄位不被濫用。包裝器永遠不可能是包裝的東西。
註釋:
[1] 自《深入理解C#》(第3版)Jon Skeet 著 姚琪琳 譯
[2] 自 劉猛鐵的C#學習視頻