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

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

第 114 講:C# 4 之動(dòng)態(tài)綁定(一):dynamic 關(guān)鍵字

2022-06-01 22:52 作者:SunnieShine  | 我要投稿

歡迎來到 C# 4!從這里開始,我們將帶領(lǐng)各位學(xué)習(xí) C# 4 的相關(guān)語法。C# 4 一共擁有的新語法特性只有三個(gè):

  • 動(dòng)態(tài)綁定(Dynamic Binding);

  • 泛型委托和接口的泛型參數(shù)協(xié)變性和你變性(Co-variance & Contra-variance on Generic Delegate & Interface Types);

  • 可選參數(shù)(Optional Parameter)和命名參數(shù)(Naming Parameter)。

我們將挨個(gè)給大家介紹。

今天我們要給大家介紹的是第一個(gè)特性:動(dòng)態(tài)綁定。

Part 1 引例:簡(jiǎn)單的反序列化

有些時(shí)候,我們可能對(duì)于一些處理過程(特別是反射機(jī)制)尤其痛苦。

咱們來舉一個(gè)例子??赡軐?duì)于你初步學(xué)習(xí)來說有些難以理解,沒事。

還記得序列化嗎?序列化就是把一個(gè) C# 里的對(duì)象給轉(zhuǎn)換為字符串(或者別的什么表達(dá)形式)的等價(jià)表達(dá)形式,以便我們可以反向操作,把字符串解析為對(duì)象,并且能夠保持?jǐn)?shù)據(jù)一致。序列化的好處在于,比如你在寫一個(gè)軟件,軟件需要保存一些配置項(xiàng)。配置項(xiàng)可能需要你在第二次打開程序的時(shí)候不會(huì)重新被默認(rèn)設(shè)置覆蓋掉,于是你可能會(huì)做這樣的功能。

那么,C# 提供了這樣的序列化的 API 允許我們這樣處理和操作它們。假如你接觸過它們的話,下面這樣的代碼肯定不難理解:

我們使用 XElement 類型對(duì)一個(gè)字符串進(jìn)行解析,解析后的對(duì)象就是這里的 person 了。它是 XElement 類型的。

本例里的這個(gè)過程是反序列化,是序列化的逆向操作。序列化是把對(duì)象自身轉(zhuǎn)換為可存儲(chǔ)到本地的信息(最常見的是字符串,比如這個(gè)例子里的字符串);而反序列化就是倒過來,把本地的這個(gè)信息給轉(zhuǎn)換回對(duì)象。

接著,我們對(duì)這個(gè)對(duì)象進(jìn)行取值。我們需要獲取的是里面 FirstNameLastName 標(biāo)簽里存儲(chǔ)的值。這個(gè)操作比較麻煩的地方在,personXElement 類型的,它包含的值可能有很多,需要我們逐步去迭代。于是,我們?cè)诤竺嬲{(diào)用自帶的 Descendants 方法。這個(gè)方法取的是解析出來的 person 信息里的指定標(biāo)簽的對(duì)象列表。

然后,得到了這個(gè)結(jié)果后,我們調(diào)用 First 方法。還記得前面說的 LINQ 嗎?這個(gè)方法可以取出一個(gè)集合里的第一個(gè)元素,不論它是不是有索引器(有索引器相當(dāng)于 [0],沒有就相當(dāng)于取出第一個(gè)迭代的元素)。

最后,得到這個(gè)對(duì)象后,我們需要取出存儲(chǔ)的值,因此還需要對(duì)這個(gè)表達(dá)式結(jié)果調(diào)用 Value 才可取值。這里的 Value 屬性的返回值是 string 類型,即存儲(chǔ)本身的數(shù)值信息,因此直接賦值給左側(cè)的 firstNamelastName 就可以了。

那么,這樣就是完整的取值操作;同理下面的 lastName 也是如此。

這個(gè)代碼沒有問題,我們可以得到正確結(jié)果;但是,好像很復(fù)雜的樣子。對(duì)于 API 不熟悉的用戶來說,并不友好;而且我們可以發(fā)現(xiàn)的是,我們待解析的字符串里的標(biāo)簽不一定非得在任何時(shí)候都叫 FirstNameLastName,也可以叫別的,比如 AB 之類的。那么,靈活性就更強(qiáng)了。上面的代碼在 Descendants 方法里傳入的字符串就是對(duì)應(yīng)了這些標(biāo)簽名,顯然就很麻煩,也不統(tǒng)一,使得代碼靈活性不夠高,維護(hù)起來就比較麻煩一些。

C# 4 提供了一種機(jī)制,使得代碼量大大降低,而且還很好看:

我們注意到,變動(dòng)的地方有三處:

  • 第一行變量返回值類型從 var 改成了 dynamic;

  • 第一行的 XElement.Parse 改成了我們自定義創(chuàng)建的類型 DynamicXml

  • 第 8、9 行的右值表達(dá)式(賦值運(yùn)算符右側(cè)的表達(dá)式)從原本的 LINQ 查詢過程改成了簡(jiǎn)單的屬性引用(雖然我們沒有這么去定義過)。

別的地方都不用改,程序我們照樣是可以跑起來的,而且也可以得到正確的結(jié)果。顯然,后者就很友好。下面我們就來說說這個(gè)用法。

Part 2 dynamic 關(guān)鍵字

2-1 動(dòng)態(tài)類型的概念

C# 4 起,引入了一種新的數(shù)據(jù)類型:動(dòng)態(tài)類型(Dynamic Type)。動(dòng)態(tài)類型是一種弱類型(Weak-typed)的語法機(jī)制。所謂的弱類型,指的是它的語法跟數(shù)據(jù)類型的概念并沒有綁定起來。這樣的編程語言有很多,例如 Visual Basic、Python 等等。拿 Python 為例,這個(gè)編程語言是具有類型體系的概念的,但你并不需要在使用和編程書寫代碼的時(shí)候去刻意去體現(xiàn)它們,而是運(yùn)行程序的時(shí)候才會(huì)去檢查類型到底綁定得合不合理、正不正確,比如 Python 定義一個(gè)變量:

它就比 C# 簡(jiǎn)單不少:

因?yàn)?C# 必須帶有一個(gè)類型的匹配項(xiàng)在左側(cè),而等號(hào)的右邊必須給出的是它的數(shù)值信息;但 Python 在編程期間不會(huì)校驗(yàn)檢查表達(dá)式的類型,因此隨便你怎么寫都 OK。

對(duì)比兩個(gè)編程語言可以發(fā)現(xiàn),顯然 C# 就嚴(yán)謹(jǐn)復(fù)雜了不少,但 C# 正因?yàn)檫@樣的類型匹配規(guī)則,才構(gòu)建出了值類型引用類型的復(fù)雜體系,編碼的時(shí)候才會(huì)更加嚴(yán)謹(jǐn)。不過,有些時(shí)候,強(qiáng)類型(Strongly-typed,指類型體系必須在編程期間就必須嚴(yán)格表明和約定)也不見得隨時(shí)都是好事情。所以,C# 4 開始引入一種新的類型規(guī)則,使得我們可以做到和 Python 基本一模一樣的類型使用規(guī)范。不過,因?yàn)?C# 的語法規(guī)則約定了我們必須寫一個(gè)類型在左邊,所以 C# 4 為了去規(guī)避這樣語法設(shè)計(jì)的規(guī)范,就引入了 dynamic 關(guān)鍵字。

我們只需要對(duì)變量左邊寫 dynamic 而不是實(shí)際的類型,就可以將其表達(dá)為一個(gè)“不確定啥類型”的變量。這就很 Python 了對(duì)不對(duì):

雖然不如 Python 簡(jiǎn)潔,但起碼你可以一勞永逸了對(duì)不對(duì)。我們把此時(shí) i、jk 變量稱為動(dòng)態(tài)類型變量(Dynamic-typed Variable),和平時(shí) C# 需要匹配嚴(yán)謹(jǐn)類型的靜態(tài)類型變量(Static-typed Variable)名稱相對(duì)應(yīng)。

這里需要你引起注意。dynamicvar 是兩回事。dynamic 是類型未知,因此你不管什么類型的表達(dá)式,全都可以賦值給 dynamic 類型的變量來接收;但 var 只是一種長(zhǎng)類型名稱的簡(jiǎn)寫,它仍然是原來的類型,只是寫起來有些麻煩,就干脆用 var 代替一波。因此,它們有這樣的區(qū)別。

可能,這些內(nèi)容還不夠你區(qū)分開它們。我們?cè)賮砜匆粋€(gè)例子。

在這個(gè)例子里,我們定義了 d 為動(dòng)態(tài)類型變量,由于它的類型實(shí)際是未知的,所以你可以隨意為其重新賦值為其它類型的變量。但是這一點(diǎn) var 就做不到:var 只是單純代替長(zhǎng)類型名稱,因此它該啥類型就啥類型,這樣的語法就做不到了。

可能聰明的你會(huì)想到一種極端情況:varobject 的時(shí)候。大家都知道,object 類型可以接收任何非指針類型的數(shù)據(jù),所以,它應(yīng)當(dāng)是可以兼容我們?cè)诳捎梅秶畠?nèi)所有類型的變量的賦值,因此假設(shè) dynamic 替代為 object 的話,這樣的操作(上面的例子這些賦值過程)應(yīng)該是可以成立的。實(shí)際上,你可以測(cè)試一下它們,你會(huì)發(fā)現(xiàn)確實(shí)如此,并且 object 執(zhí)行程序得到的結(jié)果,和 dynamic 是完全相同的結(jié)果。

2-2 那么,dynamic 類型能接收指針變量嗎?

我們來試試:

你猜,這樣的代碼可以跑起來嗎?答案是否定的,你可以得到一個(gè)錯(cuò)誤信息提示:

這句話的意思就是在告訴你,你無法把一個(gè) int* 類型的指針變量給賦值為 dynamic 的變量。那么,dynamic 更像是 object 了。

2-3 dynamic 關(guān)鍵字的感染現(xiàn)象

我們?cè)囍^察一下引例里給的原版代碼,并且稍加改動(dòng):

假如我們?cè)囍o person 改成 dynamic 的話,是否可行呢?當(dāng)然,因?yàn)槲覀儾耪f過,任何數(shù)據(jù)類型(當(dāng)然,指針除外)都可以賦值給 dynamic 類型的變量來接收,所以這樣的賦值肯定是成功的。

不過,原來寫 var 的時(shí)候,我們鼠標(biāo)放在 Descendants 方法上、First 方法上以及 Value 屬性上,我們都可以合理地查看它們對(duì)應(yīng)的調(diào)用信息:

可我們改成 dynamic 之后,事情就變得奇怪起來了:


好家伙,全成 dynamic 了。這就是我們標(biāo)題給出的 dynamic 感染現(xiàn)象dynamic Infection):如果你實(shí)例是 dynamic 類型之后,由于它自身就已經(jīng)無法斷定類型是什么了,因此在它基礎(chǔ)之上得到的調(diào)用(方法調(diào)用、屬性甚至是別的什么)得到的結(jié)果也全都是 dynamic 類型的結(jié)果;而既然結(jié)果也是 dynamic 了,那么持續(xù)往下調(diào)用的過程也就是 dynamic 的相同行為了,因此可以看到圖上給出的結(jié)果,其實(shí)是合理的,但也是一個(gè)比較有趣且奇特的行為。

2-4 如果隨便亂寫,會(huì)怎么樣呢?

比如說,我對(duì)一個(gè)本來是 int 類型的變量賦值給 dynamic 類型的變量,這樣是可以的,對(duì)吧。但是如果我們將其隨便使用,比如使用一些本來 int 里就不帶的方法,比如 Hello,會(huì)如何呢?

你覺得會(huì)如何呢?答案顯而易見:由于運(yùn)行期間 d 不包含 Hello 方法(嚴(yán)謹(jǐn)一點(diǎn),甚至無參方法),因此會(huì)產(chǎn)生一個(gè)異常,叫 RuntimeBinderException。這個(gè)異常如果一旦拋出,就意味著你有一些地方?jīng)]有寫對(duì),比如這種錯(cuò)誤調(diào)用。

我們把 dynamic 類型的變量后調(diào)用一些成員的語句稱為動(dòng)態(tài)綁定(Dynamic Binding)。如果出現(xiàn)錯(cuò)誤調(diào)用的過程,就說明我們出現(xiàn)了錯(cuò)誤的動(dòng)態(tài)綁定,或者叫動(dòng)態(tài)綁定錯(cuò)誤,就會(huì)出現(xiàn)運(yùn)行時(shí)異常,因此這個(gè)運(yùn)行時(shí)異常類型就顯得非常好理解了,runtime 是運(yùn)行時(shí),binder 是綁定對(duì)象,exception 是異常的意思,所以這個(gè)類型名稱的字面意思就是“綁定實(shí)例運(yùn)行時(shí)異常”。

2-5 很遺憾,dynamic 類型不支持調(diào)用擴(kuò)展方法

什么意思呢?還記得我們前面給的 First 方法嗎?這個(gè) First 是 LINQ 里的一個(gè)方法,它是一個(gè)擴(kuò)展方法,將一個(gè)可迭代的對(duì)象里取出第一個(gè)元素??蓡栴}是,它是擴(kuò)展方法,也就意味著它實(shí)際上并不是這個(gè)類型自身實(shí)例里就包含的方法,這就導(dǎo)致了一個(gè)什么呢?這就導(dǎo)致了,dynamic 類型經(jīng)過轉(zhuǎn)換之后,就無從知曉這里的 First 到底從何而來。前面的 Descendants 方法好歹也是這個(gè) XElement 類型里的一個(gè)成員,因此調(diào)用肯定也能找到合適的情況;但 First 就不一定了,它是擴(kuò)展方法。

所以,dynamic 類型里是不支持?jǐn)U展方法的調(diào)用的,因此你需要將其改成原本靜態(tài)類型里調(diào)用的那樣去調(diào)用。順帶一說,LINQ 里的這些擴(kuò)展方法都被一個(gè)叫 Enumerable 的靜態(tài)類所包裹,因此寫法必須是這樣的:

這樣就可以了。那么思考一下,如果我們把鼠標(biāo)放在 Value 屬性上,你看到的結(jié)果應(yīng)該是

呢,還是

這個(gè)呢?

這樣的問題其實(shí)可以自己思考一陣。

答案是后者,你猜對(duì)了嗎?什么,你不是猜的?牛逼。

原因很簡(jiǎn)單,因?yàn)槟憬o這個(gè)方法傳入了一個(gè) dynamic 類型的參數(shù)進(jìn)去,使得 Enumerable.First 方法無法知曉真正的返回值類型,所以這樣的執(zhí)行后,返回值也是 dynamic 類型的。因此,Enumerable.First 方法的結(jié)果是 dynamic 類型的,那么也因此,再次對(duì)這個(gè)結(jié)果調(diào)用 Value 屬性,那么肯定也是 dynamic 的,因此你鼠標(biāo)放在這里的 Value 上,也是 dynamic 的顯示文字信息:Represents an object whose operations will be resolved at runtime.。這句話啥意思呢?represents 是“提供”的意思,an object 是“一個(gè)對(duì)象”,whose 引出從句,表示“所對(duì)應(yīng)的、所有的”,operation 是“操作”的意思,will be resolved 意思是“被解析”,at runtime 則是“在運(yùn)行時(shí)”的意思。所以這句話的意思是:提供一個(gè)對(duì)象,該對(duì)象的所有操作全部在運(yùn)行時(shí)才會(huì)被解析。

Part 3 dynamic 關(guān)鍵字的實(shí)現(xiàn)原理

那么,這樣神奇的關(guān)鍵字,它底層是怎么去實(shí)現(xiàn)的呢?而且,我們剛才發(fā)現(xiàn)到它和 object 確實(shí)很相似,那么這倆到底有什么關(guān)系呢?還是說,僅僅是巧合呢?下面我們就來說說。

實(shí)際上,dynamic 類型其實(shí)就是 object 類型。是的,我們的假設(shè)是正確的。在底層來看,dynamicobject 實(shí)際上就是一個(gè)東西。正是因?yàn)檫@個(gè)限制,所以 dynamic 不可接收指針類型的表達(dá)式結(jié)果,而又確實(shí)是因?yàn)檫@個(gè)原因,所以變量可以重新賦值為別的數(shù)據(jù)類型也不會(huì)有語法錯(cuò)誤。從編程的角度出發(fā),如果值類型(比如前面的那些 int 賦值給 dynamic 這種),就會(huì)造成裝箱操作,因?yàn)楫吘箯?int 轉(zhuǎn) object 了。

那么,我們調(diào)用的這些雜七雜八的方法屬性啥的,這些又怎么解釋呢?我們舉個(gè)簡(jiǎn)單的例子:

夠簡(jiǎn)單了吧。我們把一個(gè)字符串賦值給 d 變量。因?yàn)樗莿?dòng)態(tài)類型的變量,因此在后續(xù)所有跟 d 變量操作有關(guān)的處理過程,結(jié)果都也全是 dynamic 類型的。于是,目光轉(zhuǎn)向第 2 行代碼可以看到,d.Length 結(jié)果就會(huì)變?yōu)?dynamic 類型的變量,而由于 dynamic 類型是未知的數(shù)據(jù)類型,因此賦值給 length 變量的話,也不會(huì)被編譯器發(fā)現(xiàn)錯(cuò)誤,因此,編譯器從語法角度來說是沒問題的,因此予以通過,因此 int length = d.Length 是正確的寫法。

接著,我們顯示 length 變量作為結(jié)果輸出。我們可以得到正確的結(jié)果嗎?可以。下面我們就來分析一下這個(gè)代碼到底是如何執(zhí)行的。

嚴(yán)謹(jǐn)一點(diǎn)。C# 是強(qiáng)類型的編程語言,因此所有的操作和轉(zhuǎn)換雖然可能從語法上不體現(xiàn),但你也需要補(bǔ)回去。那么它等于啥呢?

等于這樣一段代碼。其中 DynamicTypeGenerated 類型是自動(dòng)生成的類型,而前面的代碼則是等價(jià)轉(zhuǎn)換過去的代碼。我們挨個(gè)理解一下。

3-1 第 9 行代碼:dynamic 賦值語句

第 9 行代碼直接對(duì)應(yīng)上了這個(gè)賦值語句,因?yàn)槲覀冋f過,dynamic 實(shí)際上就是 object,所以底層翻譯的時(shí)候直接就是賦值給了 object,這沒有問題對(duì)吧。

3-2 第 10 到第 15 行代碼:綁定隱式轉(zhuǎn)換

你很好奇,dynamic d = "hello" 哪里來的轉(zhuǎn)換。實(shí)際上,轉(zhuǎn)換不在這里,而是 int length = d.Length 這里。這里一共發(fā)生了兩次動(dòng)態(tài)調(diào)用的過程,一次是 d.Length,對(duì) d 變量取 Length 屬性;而另外一次,其實(shí)是這個(gè)由于該結(jié)果的類型是 dynamic 類型,因此賦值給 int 類型是類型不匹配的。只是因?yàn)榫幾g器并未對(duì)類型進(jìn)行檢查,所以放任了這種過程。實(shí)際上,dynamicobject,所以此處有一次拆箱的轉(zhuǎn)換:object 轉(zhuǎn) int。

我們仔細(xì)觀察類型就會(huì)發(fā)現(xiàn),左值表達(dá)式(即賦值運(yùn)算符左側(cè)的表達(dá)式)DynamicTypeGenerated.Conversion 對(duì)應(yīng)了第 41 行的 Conversion 靜態(tài)字段。它并未標(biāo)記為 readonly,因?yàn)樗诖藭r(shí)是修改了數(shù)值的。

仔細(xì)觀察賦值過程(雖然有點(diǎn)復(fù)雜),但是很好看到的是,它是一個(gè)由 CallSite<> 的泛型類型包裹起來了的一個(gè)表達(dá)式處理過程。我們教過大家如何觀察和理解復(fù)雜泛型類型,那么,這里的 CallSite<Func<CallSite, object, int>> 就是一個(gè)典型的嵌套的類型。這里的 CallSite<> 包裹了一個(gè)委托類型,表達(dá)的是這個(gè)動(dòng)態(tài)處理過程到底是如何的進(jìn)行規(guī)則。比如這里,我們是一個(gè)拆箱轉(zhuǎn)換,因此我們?nèi)绻麑懗?lambda 表達(dá)式的話,肯定是 (object o) => (int)o,即 object 類型的參數(shù) o 執(zhí)行表達(dá)式 (int)o,得到的結(jié)果反饋回去。

那么這里的委托類型 Func<CallSite, object, int> 里,第一個(gè)類型參數(shù) CallSite 是一個(gè)無泛型參數(shù)的類型。它包裹的是我們進(jìn)行處理轉(zhuǎn)換動(dòng)態(tài)操作的一個(gè)上下文(Context)信息。好吧,你肯定一頭霧水咋一口氣出現(xiàn)了倆 CallSite 名稱的類型。

CallSite 類型,不管包不包含泛型參數(shù),它都表示一個(gè)上下文信息。啥叫上下文?考慮一下你在和另外一個(gè)人下棋,下棋用到的棋盤,棋盤落子的位置,如果要寫成代碼的話,你在處理和玩棋期間,它們都是會(huì)變動(dòng)的數(shù)據(jù),也是重要的數(shù)據(jù),因此你可能會(huì)存儲(chǔ)起來。不管你存什么,怎么存儲(chǔ)吧,但它們都是重要的信息,作為游玩期間的必須信息。在游玩結(jié)束之前,它們都會(huì)存在。這種只存在一段時(shí)間的數(shù)據(jù),我們就稱為上下文數(shù)據(jù)。如果粗糙理解的話,你就可以理解成這樣即可。對(duì)于這個(gè)例子里的話,我們處理 dynamic 的一條龍服務(wù)的操作期間,就相當(dāng)于是下棋的過程。在處理運(yùn)算期間,這些數(shù)據(jù)要保存起來,就是通過的這個(gè) CallSite 在存儲(chǔ)。帶泛型參數(shù)的類型,往往包裹的就是具體的處理行為,所以也是因?yàn)檫@個(gè)原因,它的泛型參數(shù)往往是一個(gè)委托類型;而不包含泛型參數(shù)的版本,則是這個(gè)帶有泛型參數(shù)的 CallSite<> 類型的基類型。是的,它倆是派生關(guān)系。

你不用掌握得很深入,因?yàn)檫@些內(nèi)容說細(xì)了的話,展開可以跟表達(dá)式樹一樣復(fù)雜。所以咱挑選簡(jiǎn)單一些的理解來說,畢竟我們寫代碼完全用不上它們,只是底層會(huì)遇到它們。你去面試也可能會(huì)遇到這種題。

再回頭看看 Create 方法。該方法是一個(gè)靜態(tài)方法,它左邊傳入的就是這個(gè) CallSite<> 上下文類型。調(diào)用這個(gè)方法的目的是為了創(chuàng)建出一個(gè)可提供后期執(zhí)行的委托實(shí)例。這里的委托實(shí)例包裝起來了你可能看不到,但實(shí)際上它確實(shí)被包裹起來了。

這里 Create 傳入了三個(gè)參數(shù)你也不必理解得很深入,大概就是你需要給運(yùn)行時(shí)提供基本的調(diào)用信息。調(diào)用信息有三個(gè):是否帶有額外信息、返回值類型,以及該上下文在哪里執(zhí)行的。

我們傳入的三個(gè)參數(shù)分別是 CSharpBinderFlags.None、typeof(int)typeof(Program),都很好理解,對(duì)吧。

3-3 第 17 行代碼:拆除上下文的包裹,得到委托實(shí)例

上下文類型里包裹了委托,我剛才說過這個(gè)話,對(duì)吧。現(xiàn)在我們?nèi)〕鏊?,目的是稍后調(diào)用,所以這里有一個(gè)變量專門取它。這里用到的是這個(gè)上下文類型里包含的 Target 屬性。

3-4 第 18 到第 26 行代碼:綁定 Length 屬性取值

我們剛才處理用到了 dynamic 的處理過程,除了一個(gè)轉(zhuǎn)換,還有一個(gè)取值 Length 屬性。這個(gè)比較復(fù)雜,因此這里我們要看一下這里的 if 條件以及里面的代碼。

和前文一樣,先判空。如果為空則賦值,有效避免重復(fù)賦值。

接著,這里我們用到了的是調(diào)用語句,因此比起原始的處理過程要復(fù)雜許多。首先是 Length 屬性調(diào)用。

這一截代碼我們也用的是 Create 方法,不過仔細(xì)注意 CallSite<> 的泛型參數(shù)就可以發(fā)現(xiàn),這次后面的倆參數(shù)都是 object 了。這是為啥呢?不是 Lengthint 結(jié)果嗎?還記得我前面說的什么嗎?dynamic 感染。dynamic 實(shí)例調(diào)用的所有東西也都是 dynamic 的結(jié)果,因此這里的 dynamic 對(duì)應(yīng)了 object 的話,因此編譯器無從知曉具體的返回值類型,所以倆參數(shù)都是 object 的。

接著,這次我們傳入的不再是前文用到的 Binder.Convert 了,因?yàn)?Convert 的意思是轉(zhuǎn)換,意味著用法是轉(zhuǎn)換機(jī)制;而這里則是屬性的調(diào)用,所以這里用 GetMember 另外一個(gè)方法。參數(shù)這次就成四個(gè)了。第一個(gè)仍然是 CSharpBinderFlags.None,因?yàn)槟壳皝碚f這個(gè)地方是固定的;而第二個(gè)參數(shù)則是調(diào)用的成員的字符串寫法。因?yàn)?d.Length 的成員就是 Length,所以這里就傳入一個(gè) Length 的字符串就可以;第三個(gè)參數(shù)也是暗示的上下文在哪個(gè)類型里執(zhí)行,這里也是一樣;最后一個(gè)參數(shù)傳入的是參數(shù)。由于 C# 4 的動(dòng)態(tài)綁定的設(shè)計(jì)規(guī)則,GetMember 可以獲取屬性也可以獲取索引器和別的成員。如果我們把索引器和方法看成帶參成員的話,那么這里的屬性就是無參成員。這里我們需要傳入的是無參的狀態(tài)信息,這里給出的第一行代碼(上面這個(gè)代碼里的第一行)就是默認(rèn)帶有一個(gè)基本信息的實(shí)例。這是固定的,也就是說你可以照抄在你的代碼里去(當(dāng)然一般也不會(huì)這么去寫底層代碼)。

最后,我們把這個(gè)數(shù)組傳入到第四個(gè)參數(shù)里,大功告成。

3-5 第 28 到第 33 行代碼:輸出 length 變量的結(jié)果

最后我們要輸出結(jié)果了。這里稍微麻煩一些,因?yàn)槲覀兦拔挠玫降木褪沁@種動(dòng)態(tài)處理特別復(fù)雜,所以這里也不簡(jiǎn)單。最后這里,我們需要把前面得到的兩個(gè)靜態(tài)屬性的數(shù)值給一并傳入,并且得到合適的結(jié)果。

先來說說 target 這個(gè)變量(前文第 17 行代碼里得到)。在這個(gè)變量里,我們要求傳入兩個(gè)參數(shù),一個(gè) CallSite 上下文信息,一個(gè) object 類型實(shí)例,且返回 int 結(jié)果。于是,我們第一個(gè)參數(shù)傳什么呢?可以看到例子里給的是把 Length 取值操作給傳過去了。

這是因?yàn)?,我們先有?Length 取值,才會(huì)有轉(zhuǎn)換。因此,我們優(yōu)先給出前一步的處理規(guī)則作為上下文信息,然后才能有下一步的處理信息。這是合理的,雖然有點(diǎn)奇怪。

接著,第二個(gè)參數(shù)傳入的則是 object 實(shí)例,即 d.Length 里的這個(gè) d 的轉(zhuǎn)換。這個(gè) d 在底層是 object 類型的,前文已經(jīng)說過了。不過這里不能直接傳實(shí)例進(jìn)去,因?yàn)檫@里,我們要先做 Length 取值,這我們還沒開始呢。取值過程很簡(jiǎn)單,模擬“實(shí)例.Length”的調(diào)用行為即可。怎么模擬呢?請(qǐng)看代碼:

這表達(dá)式是怎么理解的呢?先是 DynamicTypeGenerated.LengthProperty,這是包裹了 Length 屬性取值的上下文實(shí)例,包含取值的委托。接著我們前文說到,我們要用 Target 屬性來取里面的委托實(shí)例。不過這次,因?yàn)槲覀兪钦{(diào)用 Length 屬性,因此需要我們額外傳入實(shí)例進(jìn)去,這里的 Target 就從實(shí)例改成了方法。方法要兩個(gè)參數(shù),一個(gè)是到底取什么玩意兒的上下文(那么這里還是傳入 DynamicTypeGenerated.LengthProperty 它自己),然后第二個(gè)參數(shù)給的則是實(shí)例本身了。那么,我們拿出前文的 args 變量(定義在第 9 行),賦值過來即可。

這樣的表達(dá)式得到的就是那個(gè)委托。然后我們把此委托傳入到外層的 target 委托里當(dāng)?shù)诙€(gè)參數(shù)。這便是整個(gè)處理過程。

3-6 總結(jié)

下面我們來做一個(gè)處理過程的總結(jié)吧。dynamic 的調(diào)用會(huì)被翻譯成一系列的 CallSite 以及 CallSite<委托> 的上下文對(duì)象。這些對(duì)象包裹了一系列轉(zhuǎn)換、調(diào)用的處理信息留著我們之后一并調(diào)用起來。處理過程期間,我們會(huì)按照整個(gè)調(diào)用 dynamic 處理操作的順序,逐個(gè)得到變量的順序去帶入 CallSiteCallSite<委托>Create 方法里去作為上下文創(chuàng)建出來。

創(chuàng)建完成后,我們就有了處理的上下文了。下面我們就需要去調(diào)用拆解上下文實(shí)例,去執(zhí)行里面的委托。執(zhí)行委托的辦法是使用 Target 屬性或 Target 方法。使用 Target 屬性是因?yàn)樗恍枰~外的信息;而 Target 方法則帶有處理信息,比如 Length 屬性前面用到的這個(gè)實(shí)例對(duì)象。

最后,我們輸出調(diào)用結(jié)果,也就是在執(zhí)行這些個(gè)委托。

來看一下結(jié)果吧:

答案是對(duì)的:"hello".Length 可不就是 5 嘛。

那么今天我們就先說到這里。下一節(jié)內(nèi)容我們將繼續(xù)討論 dynamic 關(guān)鍵字,因?yàn)椋覀兦拔牡囊倪@個(gè)用法,我們并未在這里涉及和講到,下一節(jié)內(nèi)容我們將來探討一下,如何自定義一個(gè)可用來動(dòng)態(tài)取成員的語法規(guī)則。


第 114 講:C# 4 之動(dòng)態(tài)綁定(一):dynamic 關(guān)鍵字的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
九江县| 资阳市| 德令哈市| 西乌珠穆沁旗| 三穗县| 安宁市| 金川县| 江津市| 普宁市| 景泰县| 中宁县| 刚察县| 布拖县| 泸溪县| 绍兴县| 孟州市| 穆棱市| 垫江县| 方城县| 杭锦后旗| 甘德县| 百色市| 黄大仙区| 眉山市| 淮南市| 宜春市| 阜新市| 德格县| 黄浦区| 寿宁县| 衡阳县| 芮城县| 红原县| 日土县| 邳州市| 北京市| 潜山县| 故城县| 龙海市| 来凤县| 韩城市|