C# 模式匹配的綜合應(yīng)用
下面我們來(lái)討論一些關(guān)于前面講解的模式匹配的綜合內(nèi)容。
1、如何判斷集合至少有一個(gè)元素
如下的這些模式都可以。
:先看內(nèi)層
not { } or []
,它表示要么not { }
,要么[]
。not { }
等于is null
,而[]
等于Length == 0
屬性判斷,所以對(duì)整個(gè)模式取反not (not { } or [])
就是not (null or { Length: 0 })
,即不空且至少包含一個(gè)元素;{ Length: not 0 }
:最基礎(chǔ)的屬性模式判斷。不過(guò)這個(gè)用法稍微注意一下,如果集合不含Length
屬性而是Count
屬性,你可能需要改掉這里的Length
名稱;not (null or [])
:not { }
就是null
,所以和第一個(gè)寫法等價(jià);{ Length: > 0 }
:和第二個(gè)一致,只不過(guò)這里是要求Length
必須非負(fù)。但是 C# 11 開(kāi)始模模式必須非負(fù),因此語(yǔ)義上> 0
和not 0
是一致的了;{ } and not []
:{ }
是not null
,not []
則是Length == 0
的屬性判斷,因此結(jié)合起來(lái)就是序列不空,且至少有一個(gè)元素;[.., _]
和[_, ..]
:這兩個(gè)寫法是等價(jià)的,不必多說(shuō)——列表模式包含一個(gè)棄元符號(hào)和范圍記號(hào),因此序列至少包含一個(gè)元素才滿足該要求,不過(guò)先寫..
還是先寫_
都無(wú)所謂,因?yàn)榫幾g器都能識(shí)別。
整體來(lái)說(shuō),看你個(gè)人喜好來(lái)書寫代碼。它們最終是一樣的代碼,都是序列不空且至少有一個(gè)元素。
2、三種括號(hào)一起用
假設(shè)我們有一個(gè)類型,它使用了這樣的模式匹配:
根據(jù)前面的知識(shí),你可以猜測(cè)或者推斷出,該類型的最小實(shí)現(xiàn)邏輯嗎?換言之,你知道它允許這些模式匹配同時(shí)使用的時(shí)候,至少有多少個(gè)必需的成員存在呢?我們來(lái)想一想。
首先是 ()
。()
代表的是它是一個(gè)元組,但不包含任何數(shù)值。如果要找到“最優(yōu)解”,只需要表示出 inst
變量是不是 ITuple
的實(shí)現(xiàn)類型就可以了。只要它實(shí)現(xiàn)自 ITuple
接口,那么對(duì)象就可以支持和兼容 ()
模式,不需要定義任何新成員。哪怕它不走 ITuple
派生,只要 inst
是 object
類型,那么這種模式匹配就是成功兼容的,就不會(huì)出現(xiàn)語(yǔ)法錯(cuò)誤。
其次是 []
。列表模式要求對(duì)象至少是集合類型,那么對(duì)象至少有一個(gè)帶 int
單參數(shù)的索引器,以及一個(gè) Length
或 Count
屬性的其中一個(gè)即可。那么,這至少就需要對(duì)象有兩個(gè)成員的實(shí)現(xiàn)。
最后是 {}
。屬性模式的唯一要求是,該類型不能是指針。因?yàn)橹羔橆愋陀肋h(yuǎn)都不包含任何判斷屬性。它只能取出其中的數(shù)值(*p
)然后才可能有對(duì)應(yīng)的屬性。因此為了盡量包容和兼容前面的模式,那么我們假設(shè) inst
此時(shí)是 object
類型。那么由于它不是指針,因此屬性模式就可以使用(即使我們知道 object
里不包含任何可訪問(wèn)的屬性信息)。
所以,要想滿足三種括號(hào)一起使用的模式匹配的話,那么至少需要對(duì)象從語(yǔ)法上實(shí)現(xiàn)兩個(gè)成員(Length
或 Count
其一,然后一個(gè) int
類型的單參數(shù)的索引器),然后 inst
是 ITuple
的實(shí)現(xiàn)類型即可:
3、過(guò)于復(fù)雜的遞歸模式匹配
考慮一種極端情況:
這意味著什么?這意味著我在使用解構(gòu)模式的時(shí)候會(huì)產(chǎn)生這樣的代碼:
這個(gè) (({}))
是一個(gè)嵌套模式,不過(guò)沒(méi)什么特殊意義。我們拆開(kāi)看看就明白了。首先 (({}))
最外層是一個(gè) ()
模式,它表示對(duì)象可以解構(gòu)就行,因此它等價(jià)于 s is not null
;然后里面一層是 ({})
的 ()
。它代表我在使用 Deconstruct
產(chǎn)生解構(gòu)對(duì)象了之后又一次作判斷。但 Deconstruct
是我寫的一種極端代碼:它解構(gòu)了一個(gè)寂寞——返回了它自己。所以內(nèi)層的 ({})
的 ()
還是跟原來(lái)判斷信息完全一樣。最后,最內(nèi)層有一個(gè)空大括號(hào),它表示空屬性模式匹配,它依然和 o is not null
表達(dá)式等價(jià),因此,完整的表達(dá)式和你寫一個(gè) s is {}
或 s is not null
沒(méi)有區(qū)別。
你覺(jué)得好玩的話,我這還有一個(gè)例子。
然后模式匹配:
蛇皮怪。
我們強(qiáng)烈不建議你這么寫代碼,我之所以講這個(gè)是為了告訴你有這么一種特殊的情況。而且,雖然沒(méi)有這么多層級(jí)的嵌套,但經(jīng)常會(huì)有括號(hào)嵌套括號(hào)的用法,兩層還是蠻常見(jiàn)的:
((var a, var b), var c)
:將對(duì)象解構(gòu)為兩個(gè)值,用對(duì)位模式判斷。其中第一個(gè)值可繼續(xù)解構(gòu),并仍然使用對(duì)位模式繼續(xù)對(duì)位判斷;第二個(gè)值使用的是var
模式;([var a, var b], var c)
:將對(duì)象解構(gòu)為兩個(gè)值,用對(duì)位模式判斷。其中第一個(gè)值使用列表模式判斷;第二個(gè)值使用的是var
模式;({ Property: var a }, _)
:將對(duì)象解構(gòu)為兩個(gè)值,用對(duì)位模式判斷。其中第一個(gè)值使用屬性模式判斷;第二個(gè)值使用的是棄元模式;[(var a, var b), var c]
:使用列表模式判斷對(duì)象是否只有兩個(gè)值。其中列表模式里的第一個(gè)值可解構(gòu)為兩個(gè)值,并都使用var
模式;第二個(gè)值使用的是var
模式;[[var a, ..], [.., var b]]
:使用列表模式判斷對(duì)象是否只有兩個(gè)值。其中第一個(gè)值可繼續(xù)使用列表模式判斷,并只判斷該列表里的其中第一個(gè)值,使用var
模式;第二個(gè)值也使用列表模式判斷,且只判斷該列表里的最后一個(gè)值,使用var
模式;[{ P1: 42 }, [var p2], (p3: 0)]
:使用列表模式判斷對(duì)象是否只有三個(gè)值。其中第一個(gè)值使用屬性模式判斷P1
屬性;第二個(gè)值使用列表模式判斷該列表是否只包含一個(gè)值,并使用var
模式將該值取出;第三個(gè)值使用對(duì)位模式判斷p3
;{ Property: (var a, var b, _) }
:使用屬性模式判斷屬性Property
。該屬性的結(jié)果可繼續(xù)使用對(duì)位模式判斷三個(gè)值,前兩個(gè)值都用var
模式,最后一個(gè)是棄元模式;{ Property: [var a, .., var b, _] }
:使用屬性判斷屬性Property
。該屬性的結(jié)果可繼續(xù)使用列表模式,并判斷第一個(gè)元素和倒數(shù)第二個(gè)元素,都用var
模式;{ Property: { Nested1: 42, Nested2: 0 } }
:使用屬性模式判斷屬性Property
。該屬性的返回值還可繼續(xù)使用屬性模式判斷其中的Nested1
和Nested2
模式。