C# 模模式
模模式其实就是我们之前的 { Length: _ }
或者 { Count: _ }
。这个其实就是属性模式的一种特例——属性名恰好是 Length
或 Count
而已,只是在 C# 11 里单独为这两个特殊的属性模式匹配的逻辑有所加强:它的代码内容会影响编译器分析代码。
模模式的原名叫长度模式(Length Pattern),不过在模式匹配里,C# 定义了
Length
和Count
可以提供和参与编译器分析非负性的属性名称,length 这个单词可以叫长度,但 count 不能。因为 count 记录的是元素总个数,你只能说集合有多少元素,而不能说集合长度是多少。从定义和名称叫法上 count 和 length 有区别,所以本文改了一个说法。这个模模式的模就是长度的意思,而它恰好就具有两种不同类型 length 和 count 都可涵盖的含义,所以本文介绍的时候将其称为模模式。其中英语术语里的 cardinality 是基数的意思,指的是集合里有多少个元素;不过在计算机科学范畴,cardinality 除了翻译成基数以外,还可以被翻译为“势”。比如说两个集合等势就意味着两个集合的元素是一一对应起来的。这里用不着这么复杂的概念,我们只需要知道它的基本用法表元素总数就可以了。不过,如果你在查资料的时候请仍然按照“长度模式”这个术语去查阅,只是本文为了避免二义性和语义冲突而换了一个说法。
早期的长度模式只是一个简单的属性判断,但在 C# 11 里有了列表模式和分片模式,因此在集合里,长度的分析过程变得非常重要。
1、系统类型的长度属性均假设为非负数
C# 11 开始,系统提供的集合类型里,只要它带有 Length
或 Count
属性的话,那么编译器会自动假设它非负。换句话说,虽然我们大家都知道,Length
和 Count
属性返回值是 int
,它有负整数的范围,但集合的元素总数是不可能为负数的(哪怕是空集合,长度也是 0,也并非负数)。因此,系统会假设这些数据类型(特别是一维数组类型需要注意)一定是非负长度。如果你使用如下代码,编译器会自动报错:
并提示你,数组的长度非负,因此这个属性模式将永远不会匹配成功。
2、自定义类型的长度模式对编译器的表现行为
这里说一下编译器的处理机制。不论是你定义的集合类型,还是系统给定的数据类型里,长度的非负性质一直是一个正常的逻辑实现。虽然你可以随意给 Length
或 Count
属性设置为一个负数值,但它并不是正确的实现,因为长度在使用的时候一定是非负的,虽然它的返回值是 int
类型包含负数的数值范围。
使用 int
类型但不允许非负的原因是因为,uint
类型虽然能够保证非负,但它不符合 CLS(公共语言运行时)的标准,也就是说,这样的代码可能只能在 C# 上跑,而同一个 DLL 文件编译出来之后,VB 对这个 uint
不支持,因此无法使用这样的代码。因此为了兼容性,我们设置了 int
作为理想的返回类型。
而编译器会先查看这个数据类型是否同时包含一个索引器和一个 Length
或 Count
属性。如果同时存在,那么既然你都能索引了,这个类型的长度就一定不可为负数。但是,如果你没有索引器,但只包含一个单纯的 Length
或 Count
属性的话,编译器会认为它是普通的属性,因此不会验证负数情况。
所以总的来说,必须同时包含索引器和长度属性(Length
或 Count
其一)的时候,编译器才会假设长度属性的结果一定非负。
3、同时包含 Length
和 Count
属性的时候
如果一个集合同时包含 Length
和 Count
属性的话,C# 团队的解决办法是,只验证其中的 Length
属性一定非负,而 Count
属性就不再会假设为非负的情况。举个例子。
Length
属性会得到非负数的假设。而 Count
属性不论什么时候也不会被假设为非负的,因此这段代码里,如果 list