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

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

第 68 講:C# 2 之泛型(二):可空值類型

2021-11-29 21:39 作者:SunnieShine  | 我要投稿

今天我們來銜接前一節(jié)內(nèi)容說到的 Nullable<T> 類型。這個(gè)數(shù)據(jù)類型在 C# 里是提供了這個(gè) API 的,名字也一樣,用法是類似的,因此我們需要單獨(dú)提出來給大家解釋和說明。

Part 1 簡(jiǎn)化類型聲明語法:可空值類型的概念

可空值類型(Nullable Value Type,簡(jiǎn)稱 NVT)是什么呢?它表示一個(gè)值類型,但它可以表示“為空”的情況。就像是上一節(jié)內(nèi)容里說到的表格里取出來的單元格為空一樣,最終獲取到的結(jié)果數(shù)值為“空”,我們使用的是 null 這個(gè)常用的字面量來表達(dá)這個(gè)數(shù)據(jù)類型為空的情況。

1-1 可空類型記號(hào) ?

C# 2 里除了泛型的概念外,還為這個(gè)數(shù)據(jù)類型單獨(dú)設(shè)計(jì)了一個(gè)語法。如果我們要聲明一個(gè)可空值類型,都不得不寫全名:Nullable<T>,然后把 T 替換為我們的實(shí)際值類型,比如 int。

可問題在于這么寫太麻煩了,因?yàn)槊看味家蜻@么多字母。因此 C# 提供了一個(gè)新的類型記號(hào):?。之前我們學(xué)習(xí)過的類型記號(hào)一共有三個(gè):

  • T[]:表示元素為 T 類型的數(shù)組;

  • T<>:表示 T 是泛型數(shù)據(jù)類型;

  • T*:表示指向一個(gè) T 類型的指針。

現(xiàn)在我們可以使用新的記號(hào) ? 來聲明一個(gè)數(shù)據(jù)類型:T?,它表示一個(gè)值類型是可空的。請(qǐng)注意,這個(gè) T?T 此時(shí)只能是一個(gè)值類型。引用類型由于它自身的特性,它自己默認(rèn)數(shù)值就是 null,因此完全不需要 ? 記號(hào)自身就可以表達(dá)出 null 的數(shù)值,因此,這個(gè)記號(hào)只對(duì)值類型有效:

這樣的語法。這個(gè) int? 將直接被翻譯成 Nullable<int>,也就是說,它倆是等價(jià)的寫法,只不過 int? 更簡(jiǎn)單一些。沒人會(huì)因?yàn)閮蓚€(gè)完全一樣的語法,卻選擇了更難書寫的 Nullable<int> 吧?

由于它完全等價(jià)于 Nullable<int>,所以:

是原本 int? val = new int?(30); 的等價(jià)完整版寫法。

1-2 null 字面量在可空值類型里的應(yīng)用

接著。如果我要賦 Null 類似的數(shù)值,那么怎么做呢?C# 的行為是這樣的:直接用關(guān)鍵字 null 來臨時(shí)代替這種情況。也就是說,null 直接表示一個(gè) T? 里的特殊值。

比如這樣就行了。

1-3 Nullable<T> 類型自身不允許嵌套使用

另外說一點(diǎn)要注意的地方。按照定義,T?T 可以是任何值類型。但 T? 自己會(huì)被翻譯成 Nullable<T> 類型,而 Nullable<T> 也是一個(gè)值類型。那么是不是就意味著它自己也可以作為 T 而替換過去,使之成為一個(gè)嵌套泛型數(shù)據(jù)類型呢?答案是否定的。因?yàn)?Nullable<T> 是特殊的數(shù)據(jù)類型,它自身代表有 null 數(shù)值的值類型。但它自己自身包含 null 數(shù)值,所以如果它自己再嵌套進(jìn)去作為 T?T 出現(xiàn)的話,就顯得沒有任何意義了。因此,C# 不允許 Nullable<Nullable<T>> 的語法存在,當(dāng)然,T?? 這樣的語法也就不存在了。

Part 2 Nullable<T> 類型的常用成員介紹

使用的時(shí)候,我們要介紹一下這個(gè)類型的真正的 API 有哪些。

2-1 T?(T) 構(gòu)造器

Nullable<T> 類型(或者以后也可以直接簡(jiǎn)寫成 T? 了)只有一個(gè)構(gòu)造器,傳入一個(gè) T 類型的實(shí)例作為參數(shù)。也就是像是剛才那樣,我使用 new int?(30) 來實(shí)例化一個(gè) 30 這個(gè) int 類型的數(shù)據(jù)作為 int? 類型的底層數(shù)值。

2-2 HasValueValue 屬性

貼心的 T? 類型提供了 HasValueValue 屬性用于獲取里面的數(shù)值。和之前我們自己設(shè)計(jì)的 _realValue 的邏輯類似,HasValue 屬性表示獲取這個(gè) T? 類型的實(shí)例到底是不是包含數(shù)值。我們之前是認(rèn)為,_isNulltrue 就表示 _realValue 是“沒有值”的狀態(tài),而這里 T? 類型封裝好了一系列的東西,所以 HasValue 可以立刻獲取實(shí)例是不是包含數(shù)值。而 Value 數(shù)值的效果和我們?cè)O(shè)計(jì)的 RealValue 屬性效果完全一致:如果有數(shù)值,就返回結(jié)果;否則直接拋出 InvalidOperationException 類型的異常告知用戶,因?yàn)?T? 不包含值,而你嘗試在取它的值。

是這么使用的。

2-3 GetValueOrDefault 實(shí)例方法

API 還提供了 GetValueOrDefault 方法,這個(gè)方法具有兩個(gè)重載版本,一個(gè)是無參的,一個(gè)是帶一個(gè) T 類型的參數(shù)的。這個(gè)方法和我們之前設(shè)計(jì)的屬性 ValueOrDefault 是一致的效果,不過由于有兩個(gè)重載版本,所以它只和無參這個(gè)重載版本是一致的執(zhí)行效果,而帶 T 類型參數(shù)的這個(gè)重載版本則稍微不一樣。

如果我們認(rèn)為 GetValueOrDefault() 的執(zhí)行表達(dá)式是 _isNull ? default(T) : _realValue 的話,那么帶 T 參數(shù)的重載版本的執(zhí)行表達(dá)式是 _isNull ? parameter : _realValue。

2-4 ToString 方法

這個(gè) T? 類型在底層也重寫了 object 派生下來的那些方法,不過 EqualsGetHashCode 就不多說了,因?yàn)槲覀兓旧弦灿貌簧纤鼈?;?ToString 稍微可以提一下。

ToString 方法會(huì)輸出一個(gè) T? 的實(shí)際數(shù)值。如果 T? 實(shí)例里包含的是 null 數(shù)值,那么 ToString 也不會(huì)因?yàn)榘?null 而拋異常,但輸出的結(jié)果是一個(gè)空字符串,因此你可能看不到有任何東西顯示出來。

2-5 類型轉(zhuǎn)換器

T? 類型還提供了兩個(gè)轉(zhuǎn)換器,一個(gè)是從 T 轉(zhuǎn) T? 的,另外一個(gè)則是從 T? 轉(zhuǎn) T 的。在 API 里,從 TT? 轉(zhuǎn)換的是隱式轉(zhuǎn)換,而 T?T 的轉(zhuǎn)換是顯式轉(zhuǎn)換。這也就是說,我們?nèi)绻o一個(gè) int? 類型賦值 int 數(shù)值的時(shí)候,是可以直接書寫的:

不過,反過來的話,因?yàn)槟悴恢?val 是不是真的包含值而非 null,那么你必須使用強(qiáng)制轉(zhuǎn)換:

是的,T?T 上強(qiáng)制轉(zhuǎn)換等價(jià)于直接調(diào)用 Value 屬性。因此這里需要你注意,它不會(huì)調(diào)用 GetValueOrDefault 方法,而是調(diào)用 Value 屬性。如果 int? 實(shí)例不包含任何數(shù)值(即 null)的話,強(qiáng)制轉(zhuǎn)換將會(huì)產(chǎn)生 InvalidCastException 異常,表示你的強(qiáng)制轉(zhuǎn)換是失敗的。

Part 3 對(duì)自帶運(yùn)算符的數(shù)據(jù)類型,T? 的處理過程

我們常見的 T 的替換數(shù)據(jù)類型一般就是比如 int 啊、float 啊、bool 這些數(shù)據(jù)類型。雖然 T 可以被任何值類型所替換,但實(shí)際上基本上用不上自定義值類型作為 T 替代的情景。不是說語法不允許,只是很少用。

而正是因?yàn)?T 經(jīng)常被內(nèi)置值類型所替代,所以 T 類型的運(yùn)算符處理過程,T? 也具備。換句話說,比如我 int 類型有加法運(yùn)算,那么 int? 的實(shí)例其實(shí)也具備加法運(yùn)算操作,你甚至可以混合加法運(yùn)算,一個(gè) int 一個(gè) int? 都行。不過,這種運(yùn)算過程是如何的呢?

C# 是這么設(shè)計(jì)計(jì)算規(guī)則的。在操作過程之中,但凡有一個(gè)實(shí)例是 null 的話,操作就會(huì)立刻得到 null 作為結(jié)果,否則,將操作的實(shí)例的真正數(shù)值取出作為處理,并得到結(jié)果。

請(qǐng)問,這個(gè)例子輸出結(jié)果是多少?是的,40。

這個(gè)例子呢?請(qǐng)注意 (a + b).ToString() 這個(gè)表達(dá)式。因?yàn)?(a + b) 是一個(gè)部分,而后面的 .ToString() 是一部分,按照運(yùn)算符優(yōu)先級(jí),我們應(yīng)當(dāng)先計(jì)算 (a + b) 這部分。而按照 C# 語法設(shè)計(jì),ab 里有一個(gè)是 null,因此結(jié)果為 null

問題來了。null 是結(jié)果,但它作為表達(dá)式結(jié)果的實(shí)例去調(diào)用 ToString 方法,不會(huì)拋異常嗎?真的不會(huì)出現(xiàn) NullReferenceException 異常嗎?是的??煽罩殿愋筒粫?huì)出現(xiàn)這個(gè)異常。但是請(qǐng)注意,它作為結(jié)果來看的話,因?yàn)榻Y(jié)果是 null,所以按照可空值類型的調(diào)用 ToString 方法的規(guī)則來看,最終輸出的結(jié)果是一個(gè)空字符串??梢?yàn)榭兆址鞘裁炊紱]有的字符串,所以輸出內(nèi)容里,你也看不到任何可見字符。

所以,所有運(yùn)算符的處理規(guī)則和運(yùn)算規(guī)則均和這里的操作是一致的,除了……

Part 4 bool? 類型和三值布爾的概念

有一個(gè)可空值類型,它可能有些特殊,因?yàn)樗奶幚硪?guī)則不完全符合上面的所說的那些東西,這個(gè)數(shù)據(jù)類型叫 bool?。

bool? 類型是 bool 類型的可空版本,也就是說,bool? 包含三個(gè)可能取值:true、falsenull,除此之外,別無其它。正是因?yàn)樗娜≈捣秶挥腥齻€(gè)情況,所以它的處理機(jī)制有些特殊,也被編譯器自身處理和優(yōu)化掉了。另外,由于它有三個(gè)情況可取,所以 bool? 有一個(gè)單獨(dú)的名稱叫三值布爾類型(Tri-valued Boolean)。

三值布爾擁有三種情況,而布爾運(yùn)算有 &| 兩種最為常見,在 C# 里,三值布爾運(yùn)算就顯得特別特殊了。在三值布爾運(yùn)算里,不是一方為 null 結(jié)果就一定是 null。我們來看表格:

該表記錄了 xy 兩個(gè)三值布爾對(duì)象的 &| 的結(jié)果??梢宰⒁獾剑?/span>true | null 是為 true 而不是為 null 的。

有人問為什么沒有異或運(yùn)算 ^。異或運(yùn)算的處理機(jī)制和 C# 原生的 bool 是一致的,而如果其中一方為 null,那么異或運(yùn)算結(jié)果則為 null,它是滿足前述內(nèi)容的運(yùn)算,因此這里沒有單獨(dú)列出。

這個(gè)表格怎么記呢?很簡(jiǎn)單,不要死記硬背。

首先我們知道基本的不空運(yùn)算結(jié)果,這個(gè)不必多說,需要說的也就只有兩種情況:null 和正常數(shù)值計(jì)算,以及 nullnull 的計(jì)算。首先明確一點(diǎn)是 nullnull 不論是 & 還是 |,結(jié)果都一定是 null。這個(gè)也是符合正常邏輯的:兩個(gè)對(duì)象都表示“沒有數(shù)值”,那么結(jié)果怎么可能會(huì)變成有值呢?而一邊 null 一邊不是 null 的情況只有兩種:truenull 的運(yùn)算,以及 falsenull 的運(yùn)算,于是表格就只剩下這么一點(diǎn)了:

& 運(yùn)算符要求嚴(yán)苛一點(diǎn),因?yàn)樗枰獌蓚€(gè)都 true 才能返回 true,因此有一個(gè) null 我們肯定不會(huì)把 null 視為 true 來看,因此 true & nullnull。而 | 運(yùn)算符較為松散,有一個(gè) true 就行。因此,既然我有 true 了,那么我管你剩下那個(gè)是不是 null,我有 true 不就可以了?所以 true | nulltrue

接著。falsenull 的計(jì)算行為稍顯奇特,這是為了保證數(shù)學(xué)推導(dǎo)過程的嚴(yán)謹(jǐn)性。我們來使用邏輯運(yùn)算來看這個(gè)處理規(guī)則:

%5Cbegin%7Balign%7D%0Ax%20%5Cland%20y%20%26%3D%20z%5C%5C%0A%5Ctext%7BNegation%7D%5C%20%5Cdownarrow%5C%5C%0A!(x%20%5Cland%20y)%20%26%3D%20!z%5C%5C%0A%5Ctext%7BDe%20Morgan's%20laws%7D%5C%20%5Cdownarrow%5C%5C%0A!x%20%5Clor%20!y%20%26%3D%20!z%5C%5C%0A%5Ctext%7BNegation%20again%7D%5C%20%5Cdownarrow%5C%5C%0A!(!x%20%5Clor%20!y)%20%26%3D%20z%0A%5Cend%7Balign%7D

其中 %5Cland?是且的意思,而 %5Clor?是或的意思。比如 a%20%5Cland%20b?就是 ab 的意思,而 a%20%5Clor%20b?就是 ab 的意思。

按照這個(gè)處理規(guī)則進(jìn)行,我們可以看到我們使用了一次等價(jià)變換:德?摩根律。我們參照這個(gè)結(jié)論表達(dá)式 !(!x%20%5Clor%20!y)%20%3D%20z?以及對(duì)偶的另外一個(gè)表達(dá)式 !(!x%20%5Cland%20!y)%20%3D%20z?來計(jì)算 false & null 以及 false | null。

%5Cbegin%7Balign%7D%0A%5Ctext%7Bfalse%7D%5C%20%5C%26%5C%20%5Ctext%7Bnull%7D%20%26%3D%20!(!(%5Ctext%7Bfalse%7D%5C%20%5C%26%5C%20%5Ctext%7Bnull%7D))%5C%5C%0A%26%3D%20!(!%5Ctext%7Bfalse%7D%5C%20%7C%5C%20!%20%5Ctext%7Bnull%7D)%5C%5C%0A%26%3D%20!(%5Ctext%7Btrue%7D%5C%20%7C%5C%20%5Ctext%7Bnull%7D)%5C%5C%0A%26%3D%20!%5Ctext%7Btrue%7D%5C%5C%0A%26%3D%20%5Ctext%7Bfalse%7D%5C%5C%0A%5C%5C%0A%5Ctext%7Bfalse%7D%5C%20%7C%5C%20%5Ctext%7Bnull%7D%20%26%3D%20!(!(%5Ctext%7Bfalse%7D%5C%20%7C%5C%20%5Ctext%7Bnull%7D))%5C%5C%0A%26%3D%20!(!%5Ctext%7Bfalse%7D%5C%20%5C%26%5C%20!%5Ctext%7Bnull%7D)%5C%5C%0A%26%3D%20!(%5Ctext%7Btrue%7D%5C%20%5C%26%5C%20%5Ctext%7Bnull%7D)%5C%5C%0A%26%3D%20!%5Ctext%7Bnull%7D%5C%5C%0A%26%3D%20%5Ctext%7Bnull%7D%0A%5Cend%7Balign%7D

可以從推導(dǎo)計(jì)算里看到,false & null 通過德摩根律迂回了一下之后得到的結(jié)果是 false,而 false | null 也是如此運(yùn)算,得到的結(jié)果是 null。這就是為什么這兩個(gè)計(jì)算表達(dá)式結(jié)果會(huì)這么奇怪的原因。

除了計(jì)算公式別扭以外,使用上和正常的可空值類型是一樣的。不過這里我們就不再贅述了,因?yàn)椴僮魇且粯拥模瑳]必要說兩遍。

Part 5 判斷一個(gè)泛型參數(shù)的實(shí)際類型是否包含 null

這是一個(gè)好問題。既然說到了可空值類型了,那么我們就得給大家掰扯掰扯如何判斷泛型參數(shù)的實(shí)例是不是 null,以及泛型參數(shù)的實(shí)際類型自身是否包含 null 值。

我們都知道,只有引用類型和可空值類型包含一個(gè) null 數(shù)值,但在普通的值類型里是不可能有 null 的。但是對(duì)于一個(gè)泛型參數(shù)來說,我們壓根不知道它具體是值類型還是引用類型,因此我們無從下手判斷是否一個(gè)泛型參數(shù)作為類型的實(shí)例是否為 null。

實(shí)際上,我們可以直接用 ReferenceEquals== 來和 null 進(jìn)行比較。由于泛型參數(shù)在正常情況下是無從知道它是什么數(shù)據(jù)類型的,C# 會(huì)直接假設(shè)為 object 或者它的子類型。注意,這個(gè)假設(shè)排除掉了指針類型。但這也是前面說過的。正是因?yàn)檫@種假設(shè)的存在,所以它必然是一個(gè)派生體系上的一環(huán)。而 object 這個(gè)最終基類型里包含一個(gè) ReferenceEquals 可以判斷是否和某個(gè)實(shí)例引用的地址是相同的,因此我們可以拿這個(gè)直接去參與比較。不論是值類型還是引用類型,這個(gè)判斷 null 都是正確的??赡芤恍┵Y料或書籍上會(huì)直接告訴你,值類型不要使用 ReferenceEquals 方法來比較引用,因?yàn)樗鼈円b箱。但我們讓一個(gè)即使可能是值類型的類型實(shí)例判斷是否為 null 是可以使用它的,這是因?yàn)槟呐滤侵殿愋?,裝箱之后地址也不可能為 null。換句話說,只要一個(gè)實(shí)例是有數(shù)值的,那么它不管裝箱與否,最終都必然有一個(gè)地址數(shù)值(值類型會(huì)因?yàn)檠b箱而得到一個(gè)地址數(shù)值,而引用類型自己則就是地址數(shù)值),但它一定不可能是 null,畢竟它是有值的。所以,我們可以利用這一點(diǎn)判 null。

那么,如何確認(rèn)一個(gè)泛型參數(shù) T 是可空的呢?可空類型包含兩個(gè):引用類型和可空值類型。

如代碼所示。要想判斷實(shí)例 obj 是否為 null,我們的判斷次序是先和 null 比較,如果是,那么很顯然這個(gè)數(shù)據(jù)類型就是包含 null 的,直接返回 true 即可。

不過,如果它不是 null,那么它就具有數(shù)值,因此我們無從知道它是不是可空的,所以需要繼續(xù)判斷。此時(shí)我們會(huì)使用反射機(jī)制獲取類型的信息。這里我們用到一個(gè) typeof(T) 語法來獲取一個(gè)泛型參數(shù) T 的類型信息。注意這里雖然是泛型參數(shù),但仍然可以使用此語法來判別,因?yàn)樗罱K會(huì)被替代為一個(gè)實(shí)際類型,那么就相當(dāng)于是把一個(gè)實(shí)際類型替代到這里。接著,我們使用其中的 IsValueType 屬性就可以知道它是否是值類型了。如果它不是值類型,就一定是引用類型或者指針類型,因此這里我們需要判斷它是不是指針類型或引用類型。使用 type.IsPointer 可以確認(rèn)它是不是指針類型。如果是則一定包含 null(因?yàn)橹羔橆愋吞焐蜁?huì)用到 null),因此直接返回 true;否則它不是指針類型后,我們繼續(xù)使用 !type.IsValueType 來判斷它是不是引用類型。因?yàn)閷?duì) IsValueType 屬性的結(jié)果取反就意味著它不是值類型。不是值類型的情況只有指針類型或引用類型兩種,而指針類型前面已經(jīng)判斷過了,所以這里只剩下一種情況:它是引用類型。而引用類型也自帶 null 的情況,因此引用類型也是包含 null 的,因此也返回 true。

最后,我們使用 Nullable 這個(gè)類型里自帶的 GetUnderlyingType 來判斷一個(gè) Nullable<T> 類型的 T 是什么。如果它是可空值類型的話,這個(gè)方法將會(huì)返回 T 類型的類型信息實(shí)例(即 Type 類型的實(shí)例)作為結(jié)果,反之會(huì)返回 null(比如它完全就不是 T? 類型,根本無法獲取里面 T 的信息)。如果這里我們比較結(jié)果 != null,那么很顯然的就是它一定是 T? 類型了,因此我們返回 true 即可。

最后,如果以上條件沒有一個(gè)滿足,那么就說明它是普通的值類型,因此返回 false,因?yàn)槠胀ㄖ殿愋蜎]有 null 一說。


第 68 講:C# 2 之泛型(二):可空值類型的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
丹江口市| 灌南县| 葵青区| 林西县| 海兴县| 娱乐| 栖霞市| 常山县| 长子县| 平陆县| 绥阳县| 平武县| 六盘水市| 永登县| 漠河县| 河曲县| 宜兰县| 大名县| 彝良县| 吴桥县| 宜君县| 甘泉县| 喀什市| 定安县| 阿巴嘎旗| 临沧市| 剑川县| 安多县| 平塘县| 天水市| 冕宁县| 惠来县| 新平| 巧家县| 和田县| 武鸣县| 枣强县| 崇义县| 东源县| 洛川县| 仁怀市|