官术网_书友最值得收藏!

2.5 委托、事件、裝箱、拆箱

2.5.1 委托與事件

使用過C或C++的讀者對指針是什么應該很清楚,指針是一個需要謹慎對待的東西,它不僅可以指向變量的地址,還可以指向函數的地址,本質上,它是指向內存的地址。

在C#中萬物皆是類,我們使用C#的大部分時間里都沒有指針的身影,最多也只是引用,因為指針被封裝在內部函數中了。不過回調函數卻依然存在,于是C#多了一個委托(delegate)的概念,所有函數指針功能都以委托的方式來完成。委托可以視為一個更高級的函數指針,它不僅能把地址指向另一個函數,而且還能傳遞參數、獲得返回值等多個信息。系統還會為委托對象自動生成同步、異步的調用方式,開發人員使用BeginInvoke()、EndInvoke()方法就可以避開Thread類,從而直接使用多線程調用。

那么委托(delegate)在C#中是如何實現的呢?我們來一探究竟。

首先不要錯誤地認為委托是一個語言的基本類型,我們在創建委托時,其實就是創建一個delegate類實例,這個delegate委托類繼承了System.MulticastDelegate類,類實例里有BeginInvoke()、EndInvoke()、Invoke()這三個函數,分別表示異步開始調用、結束異步調用及直接調用。

但我們不能直接寫一個類來繼承System.MulticastDelegate類,因為它不允許被繼承,它的父類Delegate也同樣有這個規則,官方文檔中就是這么定的一個規則,相關表述翻譯后如下:

MulticastDelegate類是一個特殊的類,編譯器或其他工具可以從它這里繼承,但你不能直接繼承它。Delegate類也有同樣的規則。

Delegate類中有一個變量是用來存儲函數地址的,當變量操作=(等號)時,把函數地址賦值給變量保存起來。不過這個存儲函數地址的變量是一個可變數組,你可以認為它是一個鏈表,每次直接賦值時會換一個鏈表。

Delegate委托類還重寫了+=、-=這兩個操作符,其實就是對應MulticastDelegate類的Combine()和Remove()方法,當對函數進行+=和-=操作時,相當于把函數地址推入鏈表尾部,或者移出鏈表。

當委托被調用時,委托實例會把所有鏈表里的函數依次用傳進來的參數調用一遍。官方文檔中的表述翻譯后如下:

MulticastDelegate類中有一個已經連接好的delegate列表,被稱為調用列表,它由一個或者更多個元素組成。當一個multicast delegate被啟動調用時,所有在調用列表里的delegate都會按照它們出現的順序被調用。如果在執行列表期間遇到一個錯誤,就會立即拋出異常并停止調用。

看到這里我們徹底明白了,原來delegate關鍵字其實只是一個修飾用詞,背后是由C#編譯器來重寫的代碼,我們可以認為是編譯時把delegate這一句換成了Delegate,從而變成一個class,它繼承自System.MulticastDelegate類。

那么什么是event(事件),它和delegate又有什么關系?

event很簡單,它在委托(delegate)上又做了一次封裝,這次封裝的意義是,限制用戶直接操作delegate實例中變量的權限。

封裝后,用戶不能再直接用賦值(即使用=(等號)操作符)操作來改變委托變量,只能通過注冊或者注銷委托的方法來增減委托函數的數量。也就是說,被event聲明的委托不再提供“=”操作符,但仍然有“+=”和“-=”操作符可供操作。

為什么要限制呢?因為在平時的編程中,項目太過龐大,經手的人員數量太多,導致我們無法得知其他人編寫的代碼是什么,以及有什么意圖,這樣公開的delegate會直接暴露在外,隨時會被“=”賦值而清空前面累積起來的委托鏈表,委托的操作權限范圍太大,導致問題會比較嚴重。聲明event后,編譯器內部重新封裝了委托,讓暴露在外面的委托不再擔心有隨時被清空和重置的危險。因為經過event封裝后,不再提供賦值操作來清空前面的累加,只能一個個注冊或者一個個注銷委托(或者說函數地址),這樣就保證了“誰注冊就必須誰負責銷毀”的目的,更好地維護了delegate的秩序。

主站蜘蛛池模板: 黄石市| 苏州市| 上饶县| 安化县| 营口市| 石首市| 昌黎县| 孝感市| 昭觉县| 峨边| 望都县| 弋阳县| 三原县| 和顺县| 虎林市| 永修县| 防城港市| 靖边县| 金塔县| 台北市| 平顺县| 山西省| 丹阳市| 金坛市| 巴里| 应城市| 托里县| 红原县| 高安市| 牙克石市| 长武县| 安图县| 织金县| 庐江县| 固安县| 光山县| 温州市| 中江县| 盐山县| 姜堰市| 高青县|