委托
委托是一個非常不錯的設計,允許我們把方法做為參數傳遞,實現了開放閉放原則。在方法中我們只要有一個委托占位,調用者就可以傳入符合簽名的方法來做不同的操作,這也面向對象開發中多態的魅力。
但是在C#1.0的時候,委托寫起來實際上是非常復雜的,首先我們要聲明一個委托,然后再寫一個符合委托簽名的方法。再創建委托實例。然后才是調用,比如下面一個簡單的例子,我要在winform程序中用戶按了按鍵后我需要彈出一個消息
因為委托的聲明已經內置了,也就是KeyPressEventHandler所以我不用去寫。簡單一看似乎挺簡潔,但是在winForm程序中會有著大量的事件,有些事件可能處理的方式非常簡單,但是我們需要去相應的寫一個方法。這也是比較頭疼的問題。下面我們來看如何優化這個代碼
? ?
方法組
在這面我們創建了一個委托實例,在C#1.0中,要同時指定委托類型和方法(操作)也就是如下圖
而在C#2.0中支持從方法組到一個兼容委托的隱式轉換,也就是如果方法簽名和委托聲明完全相同,那么就不必再去new一個委托。這時代碼就變成了
? ?
但是有些方法并不是可以進行隱式轉換的,如果方法需要一個Delegate類型的參數,那么我們的方法就不適用了,比如Invoke方法。這些我們就要顯示轉換
? ?
? ?
協變與逆變
很多人都以為這是在4.0中才支持的,因為那時有了泛型的可變性。不過這個委托的可變性完全不同。
在winform中給我們內置很多的委托類型,比如上面用到的KeyPressEventHandler,還有MouseEventHandler,他們實際上區別不大,只是參數類型不同。第一個參數類型是KeyPressEventArgs,第二個是MouseEventArgs。
不同的事件對應不同的處理,這是沒有問題的。但是我們可能會有不同的事件同樣的處理這種需求。在C#1.0中很遺憾是沒有辦法的。而在C#2.0中我們可以使用逆變來解決這個問題
KeyPressEventArgs與MouseEventArgs都派生與EventArgs類型。實際上EventArgs的派生類有多達數百個。
我們只需要有一個具有EventAgrs類型的方法,就可以這么去做
? ?
一個返回類型為基類的委托,我們想要用子類去實例化這個委托,這在之前是不可能的。而在C#2.0中,這已經沒有任何問題
? ?
? ?
? ?
匿名方法
在C#2.0中設計者也意識到了創建一個委托的步驟過于繁瑣,我們要有一個完整的方法,然后再創建委托實例進行調用,在C#2.0中則出現了匿名方法來幫助我們簡化這一流程(3.0中的拉姆達則更加的方便)
下面就是一個簡單的匿名方法創建委托實例的例子,拿到一個字符串然后去除兩邊空格打印出來
? ?
雖然我們創建的是一個委托方法,但是編譯成IL后每個匿名方法都會創建一個方法,會在匿名方法所在的類生成一個方法不過方法名則是亂七八糟的,不過也不是給程序員去看的。
? ?
閉包
使用方法就會使用到變量,對于匿名方法來說,分為外部變量與局部變量。很容易理解,外部變量就是匿名方法外聲明的變量,而局部變量就是匿名方法內聲明的變量。
如果匿名方法沒有使用任何外部變量,那么則相安無事。如果使用了外部變量,那么它就是被捕獲的外部變量。在匿名方法內對該變量的操作是有效的!
為什么要說閉包,是因為匿名方法會在特成的情況下延長變量的生命周期,為什么這么說呢,大家都知道委托是方法的類型,可以把方法作為委托進行返回,如果一個方法里有一個委托實例,使用的是匿名方法并且捕獲了外部變量,然后把這個委托實例進行返回。這時就形成了一個閉包
? ?
這時如果去看IL會發現創建了一個新的類去容納i變量,這也是為什么方法結束后i變量仍然存在的原因,還有一點需要注意的,外部變量只有一個,如果多個匿名方法捕獲了它,那么這些匿名方法使用的都是一個變量,局部變量則沒有這個問題
循環中創建的變量,每個委托捕獲到的都是不同的變量
? ?
我們需要牢記的是
- 補獲的是變量,百不是創建委托初值時它的值
- 捕獲的變量生命周期被延長,至少和捕捉它的委托一樣長
- 多個委托可以捕獲同一個變量
- 必要時創建額外的類型來保存捕獲變量