最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

第 50 講:事件:委托字段的封裝

2021-07-23 08:29 作者:SunnieShine  | 我要投稿

還記得屬性嗎?屬性是字段的封裝,它的目的就是為了包裝一些賦值邏輯,防止外來(lái)隨意對(duì)底層字段亂賦值的情況,起到了數(shù)據(jù)校驗(yàn)和數(shù)據(jù)防修改的工作。

本講內(nèi)容將給大家介紹一個(gè)新的面向?qū)ο蟮某蓡T類(lèi)型:事件(Event)。事件是對(duì)委托字段的封裝。下面來(lái)說(shuō)一下引例,來(lái)給大家介紹一下它的使用場(chǎng)合。

Part 1 委托字段的引入

假設(shè)我們現(xiàn)在有一個(gè)列表數(shù)據(jù)類(lèi)型,專(zhuān)門(mén)用來(lái)存儲(chǔ)一系列的數(shù)據(jù)。假設(shè)我現(xiàn)在大體實(shí)現(xiàn)了基本的處理代碼,但這個(gè)列表我想在每次增加一個(gè)元素的時(shí)候都執(zhí)行一個(gè)固定的行為,比如輸出“增加一個(gè)元素,現(xiàn)在有多少元素”的信息;或者是驗(yàn)證增加的元素到底是不是超過(guò)了總?cè)萘?。如果超過(guò)容量范疇,我們就拋出異常來(lái)提示用戶(hù)無(wú)法繼續(xù)添加元素。

先給出數(shù)據(jù)結(jié)構(gòu)的基本實(shí)現(xiàn)邏輯。

我想,應(yīng)該不是特別難理解。主要是部分方法的使用可能之前沒(méi)有介紹過(guò),比如這里的 string.Format 靜態(tài)方法。

string.Format 方法和 Console.WriteLine 的傳參過(guò)程差不多,第一個(gè)字符串給出模式字符串,后面的參數(shù)都直接把各種替代結(jié)果傳入進(jìn)去。只是之前稍微沒(méi)有講到這一點(diǎn):如果模式字符串必須要使用大括號(hào),而又不是 {0}、{1} 這樣的占位符的大括號(hào)的話(huà),就需要雙寫(xiě)大括號(hào),比如 {{ 或者 }}。

然后 GetHashCode 方法是我隨便寫(xiě)的公式,它并不一定必須要這么寫(xiě),這里只是給你看一下,做個(gè)參考;再加上這個(gè)也不是本講的主要內(nèi)容,所以可以先忽略掉。

那么,使用這個(gè)數(shù)據(jù)類(lèi)型也很容易:

就像這樣。

代碼我們看明白之后,接下來(lái)我們來(lái)擴(kuò)展一下代碼。假設(shè),我們每次增加一個(gè)元素,我們就判斷一下總?cè)萘渴遣皇浅^(guò) 10 了。因?yàn)槲覀冞@個(gè)基本數(shù)據(jù)類(lèi)型的設(shè)計(jì)里,我們是規(guī)定了 _elements 是最多只能放 10 個(gè)元素的。那么如果超出元素容量,我們就不讓用戶(hù)繼續(xù)添加元素了。于是,我們就需要改變 AddElement 的邏輯,改成這樣:

大概這種感覺(jué)??蓡?wèn)題是,這樣的代碼不夠靈活,因?yàn)槲覀儾灰欢ǚ且惨褣伄惓5倪@個(gè)邏輯直接寫(xiě)到代碼里當(dāng)成代碼的一部分,而是我們完全可以由調(diào)用方來(lái)指定,即用戶(hù)來(lái)指定處理邏輯,比如說(shuō)更改這里拋異常的邏輯。那么,我們?nèi)绾尾拍茏層脩?hù)自定義一個(gè)行為,然后嵌入到已經(jīng)實(shí)現(xiàn)好的數(shù)據(jù)類(lèi)型里呢?

沒(méi)錯(cuò),委托。委托可以用來(lái)自定義一個(gè)執(zhí)行的行為,這剛好可以派上用場(chǎng)。我們改變一下這里的 List 數(shù)據(jù)類(lèi)型,給它加一個(gè)委托類(lèi)型,并附上一個(gè)此委托類(lèi)型的字段:

是的,這個(gè) Checker 是無(wú)參無(wú)返回值類(lèi)型的委托類(lèi)型。

因?yàn)槲蓄?lèi)型底層會(huì)被翻譯成一個(gè)類(lèi),所以我們盡量建議你把委托類(lèi)型放在類(lèi)的外面來(lái)書(shū)寫(xiě),而不是里面(當(dāng)然,寫(xiě)里面也可以,只是嵌套起來(lái)有點(diǎn)不太好看)。

然后,我們繼續(xù)更改 AddElement 方法:

請(qǐng)看第 4 行到第 7 行代碼。代碼從拋異常換成了 _checker.Invoke();。因?yàn)?_checker.Invoke(); 好比是一個(gè)方法的調(diào)用,所以最終也不一定能夠直接退出 AddElement 方法。如果我們不寫(xiě)這個(gè) return; 的話(huà),有可能在執(zhí)行完 _checker 的回調(diào)函數(shù)后,就會(huì)繼續(xù)往下走到第 9 行代碼里,仍然要添加元素。此時(shí)整個(gè)數(shù)組 _elements 元素已滿(mǎn),再添加元素肯定是會(huì)觸發(fā)索引越界的錯(cuò)誤的。所以我們這里要加一個(gè) return; 強(qiáng)制退出方法。

稍微注意下的是,這里的 _checker 字段可能為 null。如果你不給它賦值,那么我們說(shuō)過(guò),類(lèi)的數(shù)據(jù)成員在初始化的時(shí)候,默認(rèn)是它這個(gè)類(lèi)型的默認(rèn)數(shù)值作為初始化值的,引用類(lèi)型的默認(rèn)數(shù)值是 null,因此在使用之前一定要先判斷是不是 null。

而這里的 _checker.Invoke(); 起到了一個(gè)回調(diào)的作用。雖然我們目前還不知道到底要做什么東西,但是我們可以先寫(xiě)在這里,然后我們更改實(shí)例化 List 的過(guò)程:

比如這樣的感覺(jué)。我們?cè)诘?2 行追加了一個(gè)語(yǔ)句,對(duì)這個(gè)委托類(lèi)型的字段賦了值。這里賦值給的是 new Checker(PrintErrorMessage),這也是對(duì)得上的:因?yàn)?_checker 字段是 Checker 這個(gè)類(lèi)型的,而你右側(cè)賦值也應(yīng)該是這個(gè)類(lèi)型,對(duì)吧。然后,Checker 是委托類(lèi)型,因此實(shí)例化的時(shí)候,構(gòu)造器的參數(shù)里寫(xiě)的是方法名,且要和 Checker 委托類(lèi)型給的簽名(參數(shù)類(lèi)型和返回值類(lèi)型)匹配。

現(xiàn)在,我們?nèi)绻磸?fù)追加元素的話(huà):

你猜,會(huì)出現(xiàn)什么執(zhí)行結(jié)果?

你會(huì)看到,一共我們追加了 12 個(gè)元素,但因?yàn)樽詈髢蓚€(gè)元素超出輸出追加的范圍(只能追加 10 個(gè)元素進(jìn)去),于是產(chǎn)生了錯(cuò)誤信息的輸出。最后的 "Elements: ..." 這個(gè)字符串是 Console.WriteLine 的功勞。

順帶一提。Console.WriteLine 方法的參數(shù)可以不自己追加 .ToString 也會(huì)自動(dòng)調(diào)用 .ToString 的;但是這是因?yàn)閰?shù)是 object 類(lèi)型的關(guān)系,任何數(shù)據(jù)類(lèi)型都可以接收。但值類(lèi)型傳進(jìn)去會(huì)導(dǎo)致裝箱行為,因此我們建議是對(duì)引用類(lèi)型省去 .ToString() 部分,但值類(lèi)型的對(duì)象就不要省略 .ToString() 部分了。

那么,至此我們就把委托字段的概念介紹了。委托字段是委托類(lèi)型的字段,它代表一個(gè)回調(diào)函數(shù)列表,用于一些固定的時(shí)候執(zhí)行一些“外部不方便更改內(nèi)部代碼”的操作。

Part 2 事件成員的引入

為了介紹事件,我們不得不單獨(dú)開(kāi)一個(gè) Part 1 的內(nèi)容介紹委托字段,因?yàn)槲凶侄问怯斜锥说模旅嫖覀冃枰脑焖?/span>

我們?cè)俅位氐皆瓉?lái)的代碼上:

請(qǐng)注意這里的 _checker 委托字段。這個(gè)委托字段有一個(gè)致命問(wèn)題是,它是 public 的。這就意味著,我們可以任意注入回調(diào)函數(shù),甚至是執(zhí)行一些 IO 操作(文件操作),刪除文件、刪庫(kù)跑路就可以通過(guò)這樣的、不安全的委托來(lái)完成。所以,很危險(xiǎn),對(duì)吧。

那么,我們就需要有一個(gè)機(jī)制來(lái)避免任何人隨便寫(xiě)入委托字段的內(nèi)容。這個(gè)時(shí)候,事件就誕生了。

現(xiàn)在,我們將 _checkerpublic 改成 private

然后,在代碼里追加一個(gè)事件的成員,寫(xiě)法是這樣的:

請(qǐng)注意第 5 到第 9 行的代碼。是的,事件成員的書(shū)寫(xiě)代碼跟屬性長(zhǎng)相特別像,只是把這里面的 getset 改成了 addremove。我們說(shuō)一下這個(gè)事件的書(shū)寫(xiě),以及它到底是拿來(lái)干嘛用的。

首先,我們剛才寫(xiě)了委托字段,是吧。那么我們基于這個(gè)委托字段,作為封裝,寫(xiě)成事件的格式,就需要使用同一個(gè)委托類(lèi)型,作為這個(gè)事件成員的類(lèi)型,寫(xiě)在 event 關(guān)鍵字之后。接著,在 Checker 這個(gè)委托類(lèi)型名后,加上事件成員的名字。前面我們說(shuō)過(guò)屬性的名字是自己隨便取的,但建議取名和底層封裝的字段要配套,這樣才表示是一個(gè)東西。那么這里的事件成員也是一樣:我們給底層的 _checker 字段封裝了一下,給事件取了個(gè)“現(xiàn)在進(jìn)行時(shí)”的名字:Checking

有人會(huì)說(shuō),這取名也不配套啊,畢竟你也沒(méi)叫它 Checker,對(duì)吧。實(shí)際上原因是這樣的。屬性是用來(lái)封裝字段的。字段一般存儲(chǔ)的都是一些數(shù)據(jù)信息,這樣屬性才可以取值存值和字段進(jìn)行交互,對(duì)吧。那么既然是這樣的話(huà),字段的取名往往都是一個(gè)名詞,比如年齡(age)、生日(birthday)之類(lèi)的??蓡?wèn)題就在于,事件是委托字段的封裝,委托是干嘛的?委托是用來(lái)間接調(diào)用和處理一個(gè)行為用的。那么既然是一個(gè)行為,那么就是一個(gè)動(dòng)作,所以自然就應(yīng)該用動(dòng)詞表示。

不過(guò)還有一點(diǎn)需要注意的是,既然是動(dòng)作,那肯定動(dòng)詞是有變化形式的。你看英語(yǔ)它有各種各樣的變形;而 C# 使用事件的時(shí)候也會(huì)采用這樣的動(dòng)詞變形來(lái)表達(dá)事件。比如前文的追加元素的過(guò)程。顯然追加元素提示已滿(mǎn),剛好是放在 AddElement 這個(gè)方法里的,說(shuō)明這個(gè)動(dòng)作是正在做的,因此這里的 check 用的是進(jìn)行時(shí) Checking。

往往事件成員使用的動(dòng)詞要么是動(dòng)詞的進(jìn)行時(shí)(現(xiàn)在分詞),要么就是完成時(shí)(過(guò)去分詞),這一點(diǎn)一定要記住。

事件成員的聲明我們就說(shuō)完了,主要就是 event 關(guān)鍵字,搭配 addremove 來(lái)完成。接著來(lái)說(shuō)一下里面的 addremove 代替的內(nèi)容到底是什么。

事件是一種委托字段的封裝,對(duì)吧。那么封裝委托字段是為了干嘛呢?目的是什么呢?是不是不讓外界隨便使用委托?。渴堑?,就是為了避免直接操作委托字段。那么操作委托字段,有兩種行為:

  • 賦值一個(gè)新的委托字段過(guò)去;

  • 為委托字段增加或刪除一個(gè)新的回調(diào)函數(shù)。

事件已經(jīng)避免了你直接使用委托字段,所以這就意味著第一點(diǎn)已經(jīng)完成了。那么第二點(diǎn):增刪回調(diào)函數(shù)是不是就應(yīng)該派上用場(chǎng)了?委托字段既然不能直接操作,那么我們就只能對(duì)底層的回調(diào)函數(shù)列表進(jìn)行增加或刪除操作了。

委托字段的增刪是不是采用了 + 運(yùn)算符和 - 運(yùn)算符?是的,所以為了配合這個(gè)語(yǔ)法,C# 的事件也使用的是加減運(yùn)算符。比如,我們對(duì)現(xiàn)在已經(jīng)封裝完成的數(shù)據(jù)類(lèi)型進(jìn)行實(shí)例化,并追加 Checking 事件的回調(diào)函數(shù):

請(qǐng)注意第 2 行代碼。因?yàn)槲覀兏牧藬?shù)據(jù)類(lèi)型,所以現(xiàn)在我們只能通過(guò)事件來(lái)使用這個(gè)數(shù)據(jù)類(lèi)型了。于是乎,這里的 _checker 就必須得換成 Checking。然后,事件采用了和委托相同的語(yǔ)法,所以我們可以使用 + 來(lái)給事件進(jìn)行賦值和內(nèi)容追加。后面的代碼也不用變動(dòng),整體就是這樣使用的,大概就這種感覺(jué)。

你可能會(huì)這么想:我從外面使用這個(gè)類(lèi)型的話(huà),畢竟我自己也不知道回調(diào)函數(shù)列表里有沒(méi)有這個(gè)方法,那么刪除操作(減法運(yùn)算)應(yīng)該多半都不好用,對(duì)吧?是的,正是因?yàn)槿绱耍詼p法運(yùn)算很少被用到。但是為了提供配套的邏輯,總不可能只有加法沒(méi)有減法吧?而且我們也不一定非得只用加法。在極少數(shù)時(shí)候,我們還是可能使用減法運(yùn)算,所以有這個(gè)機(jī)制還是好的。

在事件內(nèi)部,委托 + 運(yùn)算對(duì)應(yīng)了事件的 add 這一塊代碼;而 - 運(yùn)算符則對(duì)應(yīng)了事件的 remove 這一塊代碼??梢钥吹剑?/span>add 里寫(xiě)的代碼其實(shí)很簡(jiǎn)單:_checker += value;,左側(cè)的 _checker 實(shí)際上就是現(xiàn)在封裝起來(lái)的底層的字段,而右側(cè)的 value 其實(shí)是和屬性里的 value 完全一樣的存在:它是從外界傳入的東西,指代的就是這個(gè)東西。而這里,上方的第 2 行代碼,+= 右側(cè)寫(xiě)的是 new Checker(PrintErrorMessage),這在底層執(zhí)行事件的 add 塊的時(shí)候,value 參數(shù)就對(duì)應(yīng)了 new Checker(PrintErrorMessage) 這個(gè)東西。同理,remove 里的 _checker -= value; 我就不用介紹了吧,和前面 add 塊的內(nèi)容是一樣的處理機(jī)制。

那么整體我就把事件成員的聲明,以及使用就給大家介紹了一下。

Part 3 事件和委托字段初始情況的空合賦值

這里說(shuō)一個(gè)補(bǔ)充的內(nèi)容。委托是類(lèi)型,委托字段是委托類(lèi)型的字段成員,而事件則是委托字段的封裝機(jī)制。那么,如果委托字段最初為空的話(huà),代碼不會(huì)有 bug 嗎?我說(shuō)明白一點(diǎn),讓你明白我的意思。請(qǐng)看這段代碼。

代碼的 _checker 最開(kāi)始初始化是為 null 的。本來(lái)我們就沒(méi)有打算給它賦值的時(shí)候,它默認(rèn)就會(huì)初始化為 null;而現(xiàn)在被封裝成事件機(jī)制了之后,這個(gè)委托字段就更不可能被初始化為別的東西了。那么實(shí)例化的時(shí)候,字段為 null;但這里的 addremove 塊卻直接對(duì)這個(gè) _checker 這個(gè)本就為 null 的字段在追加或移除回調(diào)函數(shù)。

問(wèn)題來(lái)了。它是 null,直接操作不會(huì)產(chǎn)生 NullReferenceException 異常嗎?實(shí)際上,不論是直接操作委托字段本身,還是操作事件,你都不會(huì)出現(xiàn)這個(gè)異常,即使它本來(lái)為 null。這是因?yàn)榈讓?+、- 運(yùn)算符被翻譯成了 Delegate.CombineDelegate.Remove 方法了,而這個(gè)方法本身就是靜態(tài)方法,因此不會(huì)產(chǎn)生 null.方法名() 之類(lèi)的調(diào)用。大家都知道 null.成員 是會(huì)產(chǎn)生異常的,但靜態(tài)類(lèi)型的成員是不會(huì)直接產(chǎn)生異常的,因?yàn)殪o態(tài)成員是不存在實(shí)例的概念的。

換句話(huà)說(shuō),即使我們傳入了 null,也只是 Delegate.Combine(null, 委托) 或者 Delegate.Remove(委托, null) 的調(diào)用的翻譯。由于這個(gè)方法在底層是會(huì)處理 null 的特殊情況的緣故,這樣的運(yùn)算符永遠(yuǎn)不會(huì)產(chǎn)生 NullReferenceException 異常,所以,即使在初始情況下,它也不會(huì)有 bug。

那么至此,我們就把事件、委托的內(nèi)容給大家介紹了一遍了。那么整個(gè)委托和事件的內(nèi)容就全部介紹完了。下面我們會(huì)進(jìn)入新的板塊:集合的基本概念,以及實(shí)現(xiàn)、實(shí)現(xiàn)集合的基本接口等內(nèi)容。

第 50 講:事件:委托字段的封裝的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
霍城县| 台江县| 沙洋县| 江津市| 金平| 论坛| 山阴县| 渝中区| 黔南| 定远县| 中山市| 织金县| 台北县| 乌兰浩特市| 斗六市| 宁强县| 本溪市| 阿拉善盟| 黎城县| 平凉市| 海门市| 长汀县| 维西| 彭阳县| 莱芜市| 雅安市| 潮安县| 台北市| 镇康县| 潢川县| 庆阳市| 南溪县| 铅山县| 堆龙德庆县| 天门市| 东城区| 库车县| 河池市| 沁阳市| 土默特左旗| 延庆县|