C# 模式匹配的综合应用
下面我们来讨论一些关于前面讲解的模式匹配的综合内容。
1、如何判断集合至少有一个元素
如下的这些模式都可以。
:先看内层
not { } or [],它表示要么not { },要么[]。not { }等于is null,而[]等于Length == 0属性判断,所以对整个模式取反not (not { } or [])就是not (null or { Length: 0 }),即不空且至少包含一个元素;{ Length: not 0 }:最基础的属性模式判断。不过这个用法稍微注意一下,如果集合不含Length属性而是Count属性,你可能需要改掉这里的Length名称;not (null or []):not { }就是null,所以和第一个写法等价;{ Length: > 0 }:和第二个一致,只不过这里是要求Length必须非负。但是 C# 11 开始模模式必须非负,因此语义上> 0和not 0是一致的了;{ } and not []:{ }是not null,not []则是Length == 0的属性判断,因此结合起来就是序列不空,且至少有一个元素;[.., _]和[_, ..]:这两个写法是等价的,不必多说——列表模式包含一个弃元符号和范围记号,因此序列至少包含一个元素才满足该要求,不过先写..还是先写_都无所谓,因为编译器都能识别。
整体来说,看你个人喜好来书写代码。它们最终是一样的代码,都是序列不空且至少有一个元素。
2、三种括号一起用
假设我们有一个类型,它使用了这样的模式匹配:
根据前面的知识,你可以猜测或者推断出,该类型的最小实现逻辑吗?换言之,你知道它允许这些模式匹配同时使用的时候,至少有多少个必需的成员存在呢?我们来想一想。
首先是 ()。() 代表的是它是一个元组,但不包含任何数值。如果要找到“最优解”,只需要表示出 inst 变量是不是 ITuple 的实现类型就可以了。只要它实现自 ITuple 接口,那么对象就可以支持和兼容 () 模式,不需要定义任何新成员。哪怕它不走 ITuple 派生,只要 inst 是 object 类型,那么这种模式匹配就是成功兼容的,就不会出现语法错误。
其次是 []。列表模式要求对象至少是集合类型,那么对象至少有一个带 int 单参数的索引器,以及一个 Length 或 Count 属性的其中一个即可。那么,这至少就需要对象有两个成员的实现。
最后是 {}。属性模式的唯一要求是,该类型不能是指针。因为指针类型永远都不包含任何判断属性。它只能取出其中的数值(*p)然后才可能有对应的属性。因此为了尽量包容和兼容前面的模式,那么我们假设 inst 此时是 object 类型。那么由于它不是指针,因此属性模式就可以使用(即使我们知道 object 里不包含任何可访问的属性信息)。
所以,要想满足三种括号一起使用的模式匹配的话,那么至少需要对象从语法上实现两个成员(Length 或 Count 其一,然后一个 int 类型的单参数的索引器),然后 inst 是 ITuple 的实现类型即可:
3、过于复杂的递归模式匹配
考虑一种极端情况:
这意味着什么?这意味着我在使用解构模式的时候会产生这样的代码:
这个 (({})) 是一个嵌套模式,不过没什么特殊意义。我们拆开看看就明白了。首先 (({})) 最外层是一个 () 模式,它表示对象可以解构就行,因此它等价于 s is not null;然后里面一层是 ({}) 的 ()。它代表我在使用 Deconstruct 产生解构对象了之后又一次作判断。但 Deconstruct 是我写的一种极端代码:它解构了一个寂寞——返回了它自己。所以内层的 ({}) 的 () 还是跟原来判断信息完全一样。最后,最内层有一个空大括号,它表示空属性模式匹配,它依然和 o is not null 表达式等价,因此,完整的表达式和你写一个 s is {} 或 s is not null 没有区别。
你觉得好玩的话,我这还有一个例子。
然后模式匹配:
蛇皮怪。
我们强烈不建议你这么写代码,我之所以讲这个是为了告诉你有这么一种特殊的情况。而且,虽然没有这么多层级的嵌套,但经常会有括号嵌套括号的用法,两层还是蛮常见的:
((var a, var b), var c):将对象解构为两个值,用对位模式判断。其中第一个值可继续解构,并仍然使用对位模式继续对位判断;第二个值使用的是var模式;([var a, var b], var c):将对象解构为两个值,用对位模式判断。其中第一个值使用列表模式判断;第二个值使用的是var模式;({ Property: var a }, _):将对象解构为两个值,用对位模式判断。其中第一个值使用属性模式判断;第二个值使用的是弃元模式;[(var a, var b), var c]:使用列表模式判断对象是否只有两个值。其中列表模式里的第一个值可解构为两个值,并都使用var模式;第二个值使用的是var模式;[[var a, ..], [.., var b]]:使用列表模式判断对象是否只有两个值。其中第一个值可继续使用列表模式判断,并只判断该列表里的其中第一个值,使用var模式;第二个值也使用列表模式判断,且只判断该列表里的最后一个值,使用var模式;[{ P1: 42 }, [var p2], (p3: 0)]:使用列表模式判断对象是否只有三个值。其中第一个值使用属性模式判断P1属性;第二个值使用列表模式判断该列表是否只包含一个值,并使用var模式将该值取出;第三个值使用对位模式判断p3;{ Property: (var a, var b, _) }:使用属性模式判断属性Property。该属性的结果可继续使用对位模式判断三个值,前两个值都用var模式,最后一个是弃元模式;{ Property: [var a, .., var b, _] }:使用属性判断属性Property。该属性的结果可继续使用列表模式,并判断第一个元素和倒数第二个元素,都用var模式;{ Property: { Nested1: 42, Nested2: 0 } }:使用属性模式判断属性Property。该属性的返回值还可继续使用属性模式判断其中的Nested1和Nested2模式。

