C# 邏輯與括號(hào)模式
因?yàn)槟J狡ヅ淅锏拿總€(gè)模式并不是一個(gè)“數(shù)據(jù)信息”,因此我們無(wú)法直接對(duì)模式用 &&
、||
等符號(hào)來(lái)進(jìn)行拼接組合。C# 為了解決這個(gè)問(wèn)題,多了三個(gè)關(guān)鍵字:and
、or
和 not
來(lái)拼接模式。
1、合取模式
合取模式用 and
拼接模式,來(lái)表達(dá)這些模式都必須成立。
比如這里,>= 'a' and <= 'z'
整個(gè)表達(dá)式用來(lái)表達(dá),>= 'a'
和 <= 'z'
兩個(gè)條件必須都滿足。如果要寫分開(kāi),就必須寫成 c is >= 'a' && c is <= 'z'
。
2、析取模式
析取模式用 or
拼接。
注意,or
拼接了前面 >= 'a' and <= 'z'
和后面 >= 'A' and <= 'Z'
兩個(gè)模式。or
表示兩個(gè)模式有一個(gè)模式能夠匹配成功就可以。
這里我們介紹了一種新的語(yǔ)法:C# 允許模式匹配的內(nèi)部使用小括號(hào),來(lái)斷開(kāi)和分隔一個(gè)模式。and
和 or
的模式名稱不變,但這個(gè)小括號(hào)套起來(lái)的模式,C# 稱之為括號(hào)模式。
3、取反模式
取反模式用 not
。
最常見(jiàn)的就是這里。我們?nèi)绻袛鄬?duì)象是不是不為 null
,那么我們最常用的就是寫成 is not null
。is null
屬于前面的常量模式,判斷對(duì)象是不是 null
。它和 ==
運(yùn)算符的區(qū)別是,==
運(yùn)算符可重載,重載會(huì)影響 ==
的判斷和使用邏輯;而 is
是永遠(yuǎn)不變的判斷模式。
4、混用三種模式
當(dāng)然,你也可以混用到 and
和 or
關(guān)鍵字拼接起來(lái)的模式里。
ch
not
這 4 行內(nèi)容可以幫助你理解和拼接模式的具體內(nèi)容。
5、三種模式的優(yōu)先級(jí)和結(jié)合性
稍微注意一下。合取式 and
和數(shù)學(xué)上是一樣的,比 or
更優(yōu)先推理,因此無(wú)需對(duì) and
和 or
模式一起的復(fù)雜模式匹配添加括號(hào):
比如這樣,(>= 'a' and <= 'z')
和 (>= 'A' and <= 'Z')
的小括號(hào)可以不要。
取反式的話,因?yàn)樗缓鸵粋€(gè)模式結(jié)合使用,不像是 and
和 or
需要兩個(gè)模式結(jié)合,因此 not
的優(yōu)先級(jí)比 and
和 not
都要高。所以,上面的例子里,這個(gè)寫法你應(yīng)該是知道哪些地方省略了小括號(hào)。
6、對(duì)括號(hào)模式和對(duì)位模式下小括號(hào)的辨識(shí)
不知道你注意到了沒(méi)有。由于我們這里介紹了三種邏輯模式的類型,因此我們也不得不會(huì)產(chǎn)生和使用一種新的模式來(lái)自定義模式的結(jié)合??墒牵±ㄌ?hào)在模式匹配里本身就有別的用途:它表示一個(gè)對(duì)位模式。
那么,如果我們出現(xiàn)了像是使用對(duì)位模式的語(yǔ)法,但它又可以被當(dāng)成帶括號(hào)的模式的話,這不就出現(xiàn)二義性了嗎?
假設(shè)我們這里的 val
是一個(gè)對(duì)象。這里的模式 (3)
到底是什么呢?是括號(hào)模式里套了常量模式嗎?還是說(shuō),它是一個(gè)一元組解構(gòu)之后的對(duì)位模式呢?這我們無(wú)法確定,尤其是這個(gè) val
剛好可以解構(gòu)成這個(gè)樣子的時(shí)候。
所以,考慮到語(yǔ)法的嚴(yán)謹(jǐn)性和兼容性,小括號(hào)一直以來(lái)都是被視為“冗余后可去掉”的存在,因此,(3)
模式會(huì)被認(rèn)為是一個(gè)括號(hào)模式。而此時(shí)的 (3)
是一個(gè)很普通的常量模式,因此編譯器會(huì)提示你可以直接去掉這個(gè)小括號(hào)。
不信你可以打開(kāi) Visual Studio 試一試。如果你在寫一個(gè)對(duì)位模式匹配的時(shí)候,如果你寫了一段代碼:
當(dāng)此時(shí)光標(biāo)在 case ()
的 (
和 )
之間時(shí),你肯定會(huì)往里面輸入模式:先判斷對(duì)象 a
,然后逗號(hào),然后判斷對(duì)象 b
??墒牵阕屑?xì)看看,如果你在輸入任何一個(gè)字符的時(shí)候,Intellisense 實(shí)際上并不是在給你提供 a
對(duì)象的屬性成員讓你對(duì)位匹配,而是比如 a:
啊、b:
啊之類的玩意兒。這是因?yàn)?,編譯器認(rèn)為你這里的小括號(hào)是括號(hào)模式,因此括號(hào)可以不要。于是,它視為你正在對(duì) (a, b)
這個(gè)元組類型 ValueTuple<T1, T2>
在做模式匹配。因此,它會(huì)提示給你看的是 a
字段和 b
字段(這兩個(gè)字段就是 ValueTuple<,>
類型下自帶的 Item1
屬性和 Item2
屬性在編譯期間的臨時(shí)引用名稱。所以,Intellisense 給出的比如 a:
的玩意兒,實(shí)際上是讓你填充進(jìn)去 a:
的字段名,用于屬性模式等模式的判斷。
這就解釋了前文“解構(gòu)模式”里的一節(jié)內(nèi)容:解構(gòu)和對(duì)位模式不要求判斷元素?cái)?shù)量至少兩個(gè),以及單元素的解構(gòu)模式要手動(dòng)消除二義性,這兩點(diǎn)的真實(shí)原因。
7、字面量在 and
模式下的類型可調(diào)整性
之所以放在這里說(shuō),是因?yàn)樽置媪渴强梢赃M(jìn)行類型匹配的,這也就是前文提到的常量模式。不過(guò),字面量有時(shí)候表現(xiàn)得并不一定非得是字面量本身的數(shù)據(jù)類型。
舉個(gè)例子,1 是 int
類型的字面量,但我們可以使用 and
連接常量模式和類型模式,使得這個(gè) 1 的類型發(fā)生變化:
請(qǐng)注意這里的 uint and 1
模式。uint
是表示類型必須是 uint
類型,而 1 卻又是 int
類型的字面量,這不會(huì)沖突嗎?答案是并不會(huì),字面量在模式匹配里會(huì)按照 and
里聯(lián)立給出的類型進(jìn)行隱式轉(zhuǎn)換。如果能夠轉(zhuǎn)換過(guò)去,那么就是允許的。
舉個(gè)例子,o is decimal and 1
和 o is decimal and 1M
是一個(gè)意思,這個(gè)字面量 1
可以寫成 1M
但也可以直接寫成 1
,因?yàn)樽置媪繒?huì)按照類型匹配的關(guān)系自動(dòng)轉(zhuǎn)換其表達(dá)的類型。不過(guò),這僅限于上下文可以暗示類型的情況。如果前面用的是 or
的話,就不行了。
8、is not var
組合模式到底是否為永假式?
所謂的永真和永假式,就是說(shuō)這個(gè)式子的判斷結(jié)果永遠(yuǎn)都是 true
或 false
。比如 if (true)
條件我們直接寫的是 true
字面量,這就是一種典型的永真式;當(dāng)然你不嫌復(fù)雜也可以寫 if (!false && true || false || true)
這種超級(jí)復(fù)雜的寫法。
那么,expr is var variable
的話,假設(shè) expr
是一個(gè)表達(dá)式的話,那么它是否是永真式呢?
這個(gè)問(wèn)題問(wèn)得好。expr is var variable
的模式匹配規(guī)則允許我們將表達(dá)式的結(jié)果在內(nèi)聯(lián)為布爾運(yùn)算邏輯的其中當(dāng)成一個(gè)永真的表達(dá)式在使用,目的是為了合并多個(gè)布爾表達(dá)式,使之直接稱為一個(gè)表達(dá)式還能跑起來(lái),這樣就可以不用使用一大堆的 if
判斷語(yǔ)句來(lái)影響可讀性和代碼量了。
不過(guò),is var
真的是永真的嗎?如果是的話,那么 is not var
不就是永假式么?永假的邏輯在 if
里還不如就寫成 if (false)
么?那這種東西還有何意義呢?
實(shí)際上,并非如此。is var
也不一定隨時(shí)都永真。考慮一種情況:解構(gòu)模式。解構(gòu)模式需要我們按照一定的情況對(duì)一個(gè)對(duì)象進(jìn)行解構(gòu)。它需要對(duì)象有一個(gè)解構(gòu)函數(shù),或者是包含一個(gè)可訪問(wèn)到的擴(kuò)展解構(gòu)函數(shù)。
考慮一種情況,假設(shè)這個(gè)對(duì)象是引用類型呢?那么解構(gòu)會(huì)隨時(shí)都成功嗎?不見(jiàn)得,對(duì)吧,因?yàn)樗约嚎赡苁?null
。因此,在引用類型使用該模式組合的時(shí)候是有別的含義的,比如:
對(duì)于這種情況下,nullableExpression
在判斷的意思就是,如果它不可解構(gòu)為后面的三個(gè)變量的時(shí)候,則退出方法。在這個(gè)時(shí)候,“不可解構(gòu)”就對(duì)應(yīng)了它為 null
的時(shí)候。
不過(guò),對(duì)于純定義變量的 is var
的話,它肯定是直接賦值過(guò)去的,它根本不判空。因此,is not var
不可用于這種情況:因?yàn)樗兰?,?dǎo)致的情況就是無(wú)法完成匹配,因此 var
后面的變量你寫啥都沒(méi)有用:反正也不會(huì)成功賦值過(guò)去。因此后續(xù)的變量自然就不可能用得上它,因此編譯器也不允許你這么寫代碼。
9、聲明和 var
模式不能寫進(jìn) or
模式的兩側(cè)
這里就需要稍微提及一下注意事項(xiàng)了。實(shí)際上,雖然模式匹配很好用,但有些時(shí)候我們難免會(huì)因?yàn)橐恍┧季S不嚴(yán)謹(jǐn)導(dǎo)致錯(cuò)誤使用——本來(lái)我們覺(jué)得可以用,但實(shí)際上編譯器不認(rèn)為你這么寫是嚴(yán)謹(jǐn)?shù)?,于是不允許你這么用。你還納悶,為什么呢。
舉個(gè)例子。倘若我們要定義一個(gè)變量,并且判斷數(shù)值是否不為 0,那么我們可以這么做:
我們學(xué)了模式匹配后,就可以簡(jiǎn)化一下:
于是,我們使用 var val and not 0
來(lái)判斷了數(shù)值結(jié)果。這是合理的。
但是,我們總有時(shí)候,會(huì)因?yàn)榇a的書寫邏輯冗長(zhǎng)而不得不使用過(guò)濾思想:倒裝判斷邏輯,減少縮進(jìn),將所有的不滿足條件的情況直接給 return
或 continue
提前退出,這樣可以減少縮進(jìn)。于是,我們就需要對(duì)上述表達(dá)式進(jìn)行取反:
按照基本的取反規(guī)則,and
要轉(zhuǎn)為 or
,not
會(huì)變?yōu)?not not
,也就是雙重否定,即可以直接去掉;反之沒(méi)有 not
的地方補(bǔ)一個(gè) not
即可:
似乎可以這么寫。但是,這樣的轉(zhuǎn)換真的合理嗎?實(shí)際上是否定的。我們來(lái)詳細(xì)說(shuō)明一下,為什么 not var val or 0
是不正確的模式判斷。
首先,not var val
模式指的是對(duì) var
模式取反。var
模式唯一可以取反的情況只有元組判斷的情況。如果一個(gè)引用類型用于解構(gòu),那么此時(shí)的 var
會(huì)優(yōu)先判斷一次 null
,畢竟對(duì)象不為 null
才可以進(jìn)行對(duì)象解構(gòu)。因此,這是唯一一種可以用 not var
的情況。但是,很顯然這里的 is not var val
并非解構(gòu),它的 val
是一個(gè)臨時(shí)變量,因此,not var val or 0
這個(gè)模式就永遠(yuǎn)不可能成立:因?yàn)?var val
是必然成立的賦值過(guò)程。
其次,就算我們退一萬(wàn)步講,not var val
我們暫且不考慮賦值成功與否,or
代表的是兩者至少有一個(gè)成立就可以。那么,如果我們假設(shè) or
右側(cè)的常量模式 0
成立的話,那么左邊的 not var val
可能就不一定成立了。注意,這里我們要說(shuō)“不一定成立”,是因?yàn)槲覀兺肆艘蝗f(wàn)步在說(shuō)明這個(gè)道理,這里是一個(gè)假設(shè)說(shuō)明。那么,一旦這樣的 var
模式不成立,我們就無(wú)法讓編譯器斷定,這個(gè) val
是否能在代碼后面繼續(xù)使用了。那么這里的 val
的聲明就無(wú)法確??捎眯?。且不說(shuō)編譯器無(wú)法確定,就是你,在寫代碼的時(shí)候也不能確保能安全使用該變量。因此,對(duì)于聲明模式和 var
模式下,這兩種模式由于都會(huì)產(chǎn)生新的臨時(shí)變量,因此為確保編譯器可以正確知道變量的使用范圍,我們不能將這兩種模式放在 or
的兩側(cè)的其中一邊。
那么,上面的代碼也不是沒(méi)有辦法去做。你只需要將原始的模式加上小括號(hào),然后在小括號(hào)左邊加 not
表示對(duì)整個(gè)模式取反即可。
這就可以了。這樣編譯器就知道,你在匹配 var val and not 0
模式。當(dāng)不滿足的時(shí)候,if
條件成立,直接 return
;反之,var val and not 0
模式成立,于是 val
變量可以安全正確使用。
當(dāng)然,這里需要你打上小括號(hào)。因?yàn)?not
優(yōu)先級(jí)最前,它是單模式的運(yùn)算,不打括號(hào)會(huì)被視為 not var val
和 not 0
兩個(gè)模式的 and
連接,導(dǎo)致錯(cuò)誤:not var val
是不可能成立的。
10、純棄元不可單獨(dú)使用
前文簡(jiǎn)要介紹過(guò)棄元模式的用法和范疇,不過(guò)棄元模式是可以直接運(yùn)用到邏輯模式里的:
var _
或者別的什么模式,就可以直接使用純棄元 _