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

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

第 115 講:C# 4 之動態(tài)綁定(二):DynamicObject 類型

2022-06-05 22:29 作者:SunnieShine  | 我要投稿

在上一節(jié)內(nèi)容里我們講到了如何去使用一個(gè) dynamic 關(guān)鍵字定義出來的類型,以及它的底層原理(實(shí)現(xiàn)是用的 object 來接收的,使得過程會產(chǎn)生裝拆箱操作)。

今天我們來看看我們前文沒有解決的另外一個(gè)維度的問題,就是如何將一個(gè)對象動態(tài)化,搞成一個(gè)直接在后面用小數(shù)點(diǎn)跟上一個(gè)可能完全不存在的屬性,比如前文的 .FirstName.LastName 這樣的規(guī)則。

Part 1 引例:數(shù)據(jù)字典包裝類

1-1 使用字典類型

考慮一種用法。假設(shè)我定義了一個(gè)字典,這個(gè)字典包含了眾多我們可能在程序里會用到的文字信息。舉個(gè)例子,我們的程序是走國際化路線的,也就意味著我們可能需要實(shí)現(xiàn)一套代碼,存儲中文和英語兩種不同的語言的用法。比如說,我有一個(gè)求和運(yùn)算的簡易程序,有兩個(gè)輸入框和一個(gè)按鈕,按鈕提示用戶按下去就獲取結(jié)果。按鈕在中文環(huán)境下是顯示為“求和”,而在英語里則顯示為“add”。

咱們簡化一下。我們不在這篇文章里實(shí)現(xiàn)整個(gè)代碼,而只關(guān)心這個(gè)存儲的“求和”和“add”這個(gè)信息到代碼里去,并且怎么調(diào)取出來。顯然,我們可以想到一個(gè)辦法:字典。

字典數(shù)據(jù)類型 Dictionary<,> 可以按照指定類型來獲取數(shù)值。而且它在 C# 里有一個(gè)騷操作,使用索引器的語法,就可以取值。于是,考慮這樣的行為,我們可以這么去做。

這里用到一個(gè) C# 提供的字典的初始化器的語法。字典的元素初始化器語法的最外層和別的數(shù)據(jù)類型也一樣,也是一對大括號。不過和別的類型不一樣的地方是,因?yàn)樽值涞?Add 方法要兩個(gè)元素,因此 C# 對這個(gè)數(shù)據(jù)類型具有特殊的初始化語法規(guī)則:使用的是嵌套級別的一對大括號,大括號只包含兩個(gè)元素,其中第一個(gè)是它的鍵,而第二個(gè)則是它的值。

這個(gè)語法在前文 C# 3 里并未說明,因?yàn)樵谥暗奈恼吕镉貌坏皆摂?shù)據(jù)類型,所以就沒有過多講解。

1-2 封裝一個(gè) ResourceDictionary 類型

為了我們稍后能夠靈活穩(wěn)定處理多個(gè)語言的話,我們可以包裝一個(gè)專門的類型:ResourceDictionary 類型:

比如這樣。很好理解,對吧。

當(dāng)然,你用 sealed class 也可以,這里用 struct 單純是因?yàn)樗鼉?nèi)部只有一個(gè)數(shù)據(jù)成員 _kvp,還是引用類型的。學(xué)過 C# 的小伙伴應(yīng)該都知道,嵌套級別的數(shù)據(jù)成員,如果是引用類型的話,不管是存儲在值類型里,還是在引用類型里,它都是以引用的形式存在。所謂的引用,在底層也就是一個(gè)一個(gè)的地址數(shù)據(jù)。所以,這里的 _kvp 其實(shí)是一個(gè)地址罷了。一個(gè)地址數(shù)值只占據(jù)很小的內(nèi)存空間,因此定義為 struct 的話,在實(shí)例化該類型的對象的時(shí)候,比 class 就要快上不少。

接著,我們有了這個(gè)類型之后,將對象給存儲進(jìn)去。

1-3 CultureInfo.CurrentCulture 屬性獲取語言習(xí)慣

那么,如何去做到按語言去取字典的數(shù)值呢?這里我們用到的是一個(gè)叫國際化的類型:CultureInfo。這個(gè)類型可以取出我們當(dāng)前用戶在運(yùn)行的這個(gè)程序所在操作系統(tǒng),用的是什么語言。

這里稍微說清楚一點(diǎn)。由于地理位置的特殊性,臺灣地區(qū)、香港地區(qū)和澳門地區(qū)作為使用繁體字的地區(qū),它們的習(xí)慣和內(nèi)陸地區(qū)使用的中文并不相同,內(nèi)陸使用的是簡體字,而臺灣、澳門和香港地區(qū)則用的是繁體字。而且就臺灣、澳門和香港這三個(gè)地區(qū)來看,它們自己的繁體字寫法也有時(shí)候不相同,因此如果你在臺灣地區(qū)、香港地區(qū)的話,得到的結(jié)果就不會是 zh-CN 這個(gè)結(jié)果,比如臺灣地區(qū)在這個(gè)規(guī)則上得到的結(jié)果是 zh-TW 而不是 zh-CN。雖然咱們都知道,它們是中國不可分割的一部分,但對于文字習(xí)慣來說,使用中文漢字的習(xí)慣并不相同,因此就導(dǎo)致了同樣使用中文的地方也有不同的使用規(guī)范和標(biāo)準(zhǔn),因此,臺灣、香港和澳門地區(qū)在語言的使用標(biāo)準(zhǔn)上就被分離出來了。所以請不要對于這樣的問題進(jìn)行雞蛋里挑骨頭的無理取鬧。

1-4 封裝一個(gè) Resource 類型作為終極類型

那么,我們現(xiàn)在有了包裝類型,并且也學(xué)會了如何去按語言分配和取字典信息了,下面我們就來完善整個(gè)行為。我們要想讓用戶可以優(yōu)雅地使用這個(gè)多語言的字典,我們光實(shí)例化出來兩個(gè) ResourceDictionary 還不夠。因?yàn)檫@樣并不方便,像是做特別復(fù)雜的國際化處理的時(shí)候,這樣的字典可能包含幾十個(gè)。是的,確實(shí)是幾十個(gè)。所以這樣實(shí)例化字典之后,使用 switch 來判斷使用具體哪個(gè)字典的行為并不優(yōu)雅。那么怎么做呢?

我們再套一層 Dictionary<,> 對象。這次,我們把鍵值對的鍵和值分別對應(yīng)上 stringResourceDictionary。其中的 string 是這里通過 CultureInfo.CurrentCulture 屬性得到的實(shí)例,取 Name 而得到的字符串結(jié)果;而通過字符串相等性的比較,我們就可以達(dá)到找尋對應(yīng)字典的用法。

我們定義出一個(gè) Resource 類型來存儲它們。通過我們添加的 Add 方法來開放給用戶使用,這樣用戶就可以往里面追加字典了。做法很簡單:

1-5 追加 Resource 的字典取值成員

既然有了終極類型了,那么我們自然是需要通過這個(gè)綜合對象來完成不同語言取不同字典的規(guī)則。對于這個(gè)類型來說,我們需要開放一個(gè)用戶可以使用的取值規(guī)則。做法是開一個(gè)和 ResourceDictionary 一樣用途的索引器,傳入 string 類型當(dāng)參數(shù)。由于 ResourceDictionary 的索引器是因?yàn)榈讓拥淖值浔话b起來了所以必須打開索引器來取里面的數(shù)據(jù),而 Resource 類型是高階的用法,所以它的索引器除了取值用,還要額外帶有判斷語言習(xí)慣的操作。所以,我們要設(shè)計(jì)為這樣才比較合理:

1-6 試著使用一下自己寫的數(shù)據(jù)字典包裝類

然后,我們就算完成了整個(gè)封裝包裝過程。下面,我們只需要在主方法里進(jìn)行處即可。

第一行代碼用到 resource 變量的索引器操作。這個(gè) resource 就是前文里定義出來的那個(gè) resource。來看看完整的代碼吧:

來看看效果:

“求和”二字就是我們最開始在主方法里寫的讀入的數(shù)據(jù)信息“求和”。至此,引例就完成了。如果你還需要寫更多的語言的字典的話,只需要往 resource 變量里加就行了。而至于這個(gè) zh-CN 啊,en-US 怎么得來的,請參考網(wǎng)上給出的表格。這個(gè)叫做 i18n 語言代碼表(英語叫 Locale Codes)。

說起來很有意思,i18n 這個(gè)詞語的由來竟然是因?yàn)?internationalization 單詞太長了,一共 20 個(gè)字母,i 和 n 是它的頭尾,剩下一共 18 個(gè)字母,所以就簡寫成 i18n 了。

Part 2 DynamicObject 類型

在我們前文的取值代碼里,我們用到了索引器:

該索引器用法也不復(fù)雜,但也確實(shí)很丑陋。尤其是字典用多的時(shí)候,一大堆的 resource["鍵"] 的用法,搞得代碼非常不好看。于是,我們會去處理和修正語法規(guī)則,使得我們把它視為動態(tài)實(shí)例,寫成這樣:

我們想讓它也可以生效。這怎么做到呢?難道直接把前面的 var resource = ... 給改成 dynamic resource = ... 這么簡單嗎?當(dāng)然不是。因?yàn)檫@里的索引器參數(shù)是一個(gè)字符串啊,我們直接是將這個(gè)字符串同等當(dāng)成屬性方在了實(shí)例的右邊。這種用法實(shí)在是不可能只是簡單改成 dynamic 而已。那么,怎么做呢?

2-1 定義單例

單例模式(Singleton Pattern)不知道你聽說過沒有。這是 C# 編程語言里的一種設(shè)計(jì)模式。可能你對設(shè)計(jì)模式并不熟悉,所以我們就直接在這里說清楚了吧。單例模式就是往這個(gè)類型里創(chuàng)建一個(gè)可以隨時(shí)隨地訪問的靜態(tài)只讀實(shí)例,并防止用戶再次對類型進(jìn)行實(shí)例化的設(shè)計(jì)模式。做法很簡單,改掉兩處代碼:

  1. 定義單例;

  2. 私有化構(gòu)造器。

私有化構(gòu)造器,然后把構(gòu)造器呢,用于這個(gè)靜態(tài)只讀的實(shí)例 Instance,它作為唯一一個(gè)你可以對接使用 Resource 類型對象的方案而存在。這就叫單例模式。

單例模式的其中一個(gè)作用(即我們這里用到的作用)是防止用戶誤用代碼,創(chuàng)建過多沒有意義的實(shí)例出來。

2-2 讓 Resource 類型從 DynamicObject 派生

接下來就是這篇文章的重頭戲了。我們需要讓 Resource 類型從 DynamicObject 類型派生。好在我們剛好給 Resource 用引用類型實(shí)現(xiàn)了。因?yàn)橹殿愋褪菬o法自定義繼承派生關(guān)系的,所以這個(gè)只能引用類型來做。

把頭部改成這樣即可。

這個(gè) DynamicObject 作為關(guān)鍵的一步,我先說明一下它是什么。

它是我們將對象動態(tài)化的一個(gè)重要規(guī)則和約定。如果一個(gè)對象可以使用前文那樣的動態(tài)屬性調(diào)用一個(gè)完全沒有的成員,卻可以定義出一套固定的取值規(guī)則和標(biāo)準(zhǔn)的話,那么必須要從這個(gè) DynamicObject 類型進(jìn)行派生。派生之后,對象就有了 dynamic 后引用一個(gè)完全不存在的成員,但又可以正確執(zhí)行的能力了。

2-3 重寫 TryGetMember 方法

接著,雖然我們并未對代碼進(jìn)行重寫好像也沒問題,但是我們這里需要重寫一個(gè)里面的方法,目的是為了關(guān)聯(lián)上前文的 resource.AddOperationName 的神奇調(diào)用規(guī)則,以及字典。

在從 DynamicObject 類型派生之后,我們可以搞到這樣的重寫:

輸入 override,就可以看到 TryGetMember 方法,然后按 tab 按鍵確認(rèn)你的輸入,就可以得到這樣的代碼。不過,這個(gè) base.TryGetMember 是在調(diào)用基類型的默認(rèn)的代碼,因此沒有意義,我們要將其刪除,改成我們自己用的代碼。

先來簡要介紹一下兩個(gè)參數(shù)。第一個(gè)參數(shù) binder 提供綁定的信息,也就是我們剛才使用的 resource.AddOperationName 語法里,屬性引用的這個(gè) AddOperationName 的基本用法規(guī)則和信息;而第二個(gè)參數(shù),則是返回我們應(yīng)該正常通過 resource.AddOperationName 的用法應(yīng)該正常返回什么結(jié)果出來。返回值 bool 類型是暗示了我們是否可以這么去使用。

考慮到例子里,我們是按字典取值的,所以我們可以把返回值定義理解為“是否可以成功從字典里取到合適的結(jié)果”,而 out object result 這個(gè)參數(shù)則返回出來這個(gè)看起來很像是屬性引用的表達(dá)式的結(jié)果。

我們改寫代碼為這樣:

看起來實(shí)現(xiàn)有些復(fù)雜,但多數(shù)都是注釋文字。我們仔細(xì)理解一下注釋。

首先,我們通過第一個(gè)參數(shù) binderName 屬性,獲取到屬性引用的那個(gè)成員的信息。因?yàn)檫@里是動態(tài)使用的,編譯器并不會發(fā)現(xiàn)錯(cuò)誤,所以編譯通過;而對于底層來說,這個(gè)屬性引用實(shí)際上是不存在的,所以它不外乎就是一個(gè)字符串一樣的存在。因此,binder.Name 得到的結(jié)果就是字符串形式的表達(dá)。這一點(diǎn)很神奇,屬性引用的屬性居然被轉(zhuǎn)為了字符串寫法。

然后,我們使用一個(gè) try-catch 語句來完成取值。注意,這里盡量不要拋異常,是為了保證取值的成功或失敗。失敗的話,C# 會自動產(chǎn)生異常,因此不必我們手寫。懂意思嗎?就是說,這個(gè)方法只是動態(tài)調(diào)用過程的其中核心一步,而其它的步驟都被 C# 包裝好了,行為也都包裝好了,因此不必我們在方法里拋異常來告知用戶出錯(cuò),拋異常留給 C# 就行。這個(gè)方法如果返回 false,就表示我們綁定失敗,于是就會產(chǎn)生 RuntimeBinderException 異常,在前文已經(jīng)說過了。

至此,我們就算把關(guān)鍵的行為和操作給說完了。

2-4 單例模式的類型改成 dynamic

這一步很關(guān)鍵。我們在前文給出的是 Resource Instance = new Resource(),現(xiàn)在我們需要改成 dynamic Instance = new Resource()

這一步很關(guān)鍵。目的是為了讓對象真的可以動態(tài)處理。因?yàn)槲覀冎暗?Resource 類型都是以實(shí)際類型存儲和存在的,這里改成 dynamic 后,對象又從 DynamicObject 類型派生,因此 C# 會識別代碼,并自動知道如何去處理。

2-5 修改 Main 方法的代碼

最后一步是修改 Main 的代碼。我們改下后面的部分,完整的代碼如下:

這里的 Resource.Instance 就是在使用這個(gè)單例,而這里的 Add 方法和 AddOperationName 這個(gè)“假屬性”也都在正常操作和調(diào)用方法的具體內(nèi)容。其中,Add 方法本身就存在,所以不管它是不是 dynamic 類型的,這個(gè)方法也都會被正常處理和執(zhí)行到;而下面的 AddOperationName 并不存在,但屬性的引用的底層實(shí)現(xiàn)被我們重寫了,所以也不會出錯(cuò)。

再來看下這次我們修改后的完整版代碼吧:

來看這次我們得到的結(jié)果:

非常好,我們修改了之后也得到了我們要的結(jié)果,而且代碼雖然更多了,但是主方法里的代碼更優(yōu)雅了。封裝是麻煩的,但是用起來是方便的,這就是編程。

Part 3 DynamicObject 支持的處理行為

實(shí)際上,不僅是前文用到的屬性的綁定,還有很多的成員全都可以在 DynamicObject 的部分成員重寫之后完成正常的執(zhí)行和使用。我們打開 DynamicObject 對象的元數(shù)據(jù):


可以發(fā)現(xiàn)它竟然有這么多方法。其中:

  • TryBinaryOperation:對一個(gè) dynamic 對象參與的二元運(yùn)算符行為進(jìn)行執(zhí)行處理。比如說我對了一個(gè) dynamic 對象和一個(gè) int 使用了 + 運(yùn)算符操作,那么具體在底層綁定和定義處理規(guī)則的時(shí)候,你需要重寫這個(gè)方法;

  • TryConvert:對一個(gè) dynamic 對象上使用到的強(qiáng)制轉(zhuǎn)換或隱式轉(zhuǎn)換(雖然 dynamic 類型自身很少有隱式轉(zhuǎn)換)進(jìn)行處理。比如如果我用到了一個(gè) dynamic 類型的 obj 對象的 (int)obj 操作,你就需要重寫這個(gè)方法;

  • TryCreateInstance:對一個(gè) dynamic 對象進(jìn)行實(shí)例化。常見的情況就是 new 一個(gè)出來;

  • TryDeleteIndex:這個(gè)方法對應(yīng)到 C# 里沒有相同的語法,所以就不介紹了;

  • TryDeleteMember:這個(gè)方法對應(yīng)到 C# 里也沒有相同的語法,所以就不介紹了;

  • TryGetIndex:對一個(gè) dynamic 對象使用索引器取值的時(shí)候。比如 dynamic 的對象 obj 使用了 obj[1, 2, 3] 如此的語法;

  • TryGetMember:對一個(gè) dynamic 對象使用了屬性或字段的時(shí)候。這個(gè)方法就是前文里重寫的那個(gè);

  • TryInvoke:對一個(gè) dynamic 對象,直接當(dāng)委托的形式直接跟小括號調(diào)用的語法。比如 dynamic 對象 obj 直接使用 obj(1, 2, 3) 這樣的語法的時(shí)候;

  • TryInvokeMember:對一個(gè) dynamic 對象調(diào)用類似方法調(diào)用的規(guī)則。比如 dynamic 對象 obj 調(diào)用類似 obj.InnerMethod(1, 3) 這樣的方法的時(shí)候;

  • TrySetIndex:對一個(gè) dynamic 對象使用索引器在賦值的時(shí)候。比如 dynamic 對象 obj 使用了 obj[3] = 0 如此的語法。注意它和前文 TryGetIndex 的區(qū)別,前文是取值,這里是賦值;

  • TrySetMember:對 dynamic 對象使用字段或?qū)傩酝镔x值的時(shí)候;

  • TryUnaryOperation:對 dynamic 對象使用一元運(yùn)算符的時(shí)候。比如對一個(gè) dynamic 對象使用了自增運(yùn)算符 ++ 的時(shí)候。

至此,我們就把 C# 提供的兩種動態(tài)類型的語法給大家介紹完了。下一講的內(nèi)容則繼續(xù)是 C# 4 的語法:用于委托和接口里的泛型參數(shù)的協(xié)變性和逆變性。這個(gè)規(guī)則好像在 C# 2 提過,但它是針對委托的參數(shù)和返回值的,并沒有說泛型參數(shù)。泛型參數(shù)有一個(gè)比較重要的轉(zhuǎn)換優(yōu)化,在 C# 4 里才有,這個(gè)我們在下一講會給大家介紹。


第 115 講:C# 4 之動態(tài)綁定(二):DynamicObject 類型的評論 (共 條)

分享到微博請遵守國家法律
郎溪县| 波密县| 漾濞| 郎溪县| 汝南县| 吉隆县| 内江市| 奈曼旗| 西青区| 夏津县| 香港| 左贡县| 漯河市| 宜昌市| 昭平县| 潞西市| 汝阳县| 唐山市| 栖霞市| 诏安县| 武功县| 沁源县| 宝坻区| 武清区| 治多县| 苍梧县| 台东县| 西吉县| 辉南县| 镇平县| 乌兰县| 平山县| 称多县| 社旗县| 赞皇县| 黄平县| 资中县| 登封市| 兖州市| 察隅县| 碌曲县|