C# 列表模式
1、语法
为了将集合的元素提取出来判断,C# 拥有了列表模式。
列表模式是将一个不知道是不是集合的对象,用列表的格式列举出来,对其中的元素挨个进行判断的模式。
我们使用一对大括号进行判断。使用范围记号 ..
来表达“这是一个范围”。举个例子:[1, .., 3]
表示判断一个序列的第一个元素是不是 1,而最后一个元素是不是 3。所以,自然这个写法就等价于下面这个格式了:
它等价于
这里的 ^1
是 C# 8 里的表达式,表示倒数第一个元素。^n
就是倒数第 n 个元素。可以从这个写法里看出,..
是灵活的:它不是固定长度,是随着整个模式匹配的序列来确定 ..
的长度的。这么写是为了简化代码的书写格式。
当然,假设我们判断倒数第二个元素而不是倒数第一个的话,那么我们可以尝试在倒数第一个元素的判断信息上添加弃元记号 _
来表达占位:
弃元记号在这里起到了很重要的作用。一个弃元记号占一个位置,这恰好表达和判断了 arr[^2]
的数据,而不是 arr[^1]
。
2、预防性长度判断
和前文一致,要用这个模式的话,这个数据类型除了拥有 Length
或 Count
属性外,索引器成员是必不可少的。另外,如果你不写上范围记号 ..
的话,就成了判断恰好这些数据了。
这三个写法的区别是,第一个和第二个是一样的判断:因为 [1, 2, 4]
按照顺序,判断的都是前三个数据的数值,因此长度给出后,判断的自然是前三个数据了,而后续的数据不用管,写上 ..
和不写 ..
都是没有关系的。但是第三个则不一样了。第三个因为长度模式不存在的关系,模式匹配的长度模式会依赖于 [1, 2, 4]
这个列表模式。这个模式只给出了三个元素,因此不写长度模式的话,编译器会认为这个写法下,长度模式是 { Length: 3 }
;相反,如果你加上了 ..
的话,编译器就不再去确认后面的元素信息了。
但是,为了避免抛出异常,C# 会贴心地做一个“预防性判断”。如果 arr
没有这么长呢?假设 arr
就俩元素,那么判断 [1, 2, 4, .. ]
就可能产生一个异常。因此,C# 会自动生成一条预判长度语句:arr.Length >= 3
。
因此,如下四种写法的等价格式是这样的:

“默认生成预防性长度判断”这一点比较隐晦,因此你一定要记住。
3、注意使用条件
在列表模式里,我们约定数据类型必须包含索引器和 Count
或 Length
属性才可以使用列表模式,下面有两个最容易忽略也会被当成可以用列表模式,实际上不然的数据类型。
Dictionary<TKey, TValue>
类型;ICollection<T>
类型。
首先,字典数据类型的索引器并不是 int
数据类型,而是 TKey
这个泛型数据类型的。由于 C# 对列表模式的语法设计规则,所以字典无法表达出合适的写法,所以字典类型不支持列表模式;同时,ICollection<T>
类型也不支持。原因可能让人大跌眼镜:这个接口压根就没有索引器的成员。可能这一点非常容易忽略掉,但是也很好想到为什么:实现了 ICollection<T>
接口的数据类型我们都可以叫它们“集合”。但集合只表示和表达一种合适的存储序列。但序列不一定是连续的,也就是说,你不一定可以使用这个集合类型从前一个元素来找后一个元素。最常见的情况就是“不重复集合”。如果你写了一个不重复集合的话,它可能会使用哈希码来建立列表。那么元素之间就不一定是连续的了,因此你无法定义一种良好的索引机制获取每一个元素。这样的数据类型就不存在索引器一说。正是因为存在这样的集合数据类型不含有索引器,因此你无法对一个这样的数据类型使用列表模式,因为列表模式依赖索引器机制。
4、无条件成立的列表模式
当然,既然可以允许判断列表模式,那么自然就有 [..]
这种写法。这个写法的意思是,集合的元素无条件成立。但是,这个写法还不如不写,对吧。
5、请一定注意,列表模式不是递归模式的一部分
如题,列表模式是一个单独的模式,你必须声明得和别的模式串联起来用 and
或 or
连接,所以如下的语法是错的:
int[]
和 [1, _, .., 3]
之间插入一个 and
这一点一定要记住,因为它是对集合作判断,但大多数类型也都不是集合,因此不要想着把它和别的模式放在一起;但是,列表模式允许定义内联变量。
假设此时的 expr
是一个表达式,我们可以使用此语法定义表达结果,并视 result
为表达式的运算结果。
还有一个原因是,假设我们想要确定是否一个数据类型是 int[]
并且没有元素的话,它的语法是 is int[] and []
。如果我们去掉 and
使之允许,这个语法就成了 is int[] []
,这会被编译器视为 is int[][]
,即判断对象是不是一个元素是 int
的锯齿数组。因此,不加 and
还真不行。