C# 解构模式
1、语法
因为前文我们拥有了解构函数,也拥有了 var
模式,因此 C# 灵活的语法提供了 var
模式的解构版本:
var (x, y)
。当然,你也可以内联 var
(var x, var y)
的语法。
这样是可以的。
不过严谨一点的话,
var (x, y)
是解构模式,而(var x, var y)
是对位模式。因为前者使用var (x, y)
语法,小括号里直接定义了变量名,小括号的外侧则是var
关键字;但(var x, var y)
在小括号里定义了两个变量,都使用了var
关键字,这意味着是对应位置上的数据分别定义变量,类似point.X is var x && point.Y is var y
的效果,因此只能说是对位模式。使用解构模式可以更清楚、更简明地将对象进行解构,直接赋值到变量上;但它存在一定的弊端,例如解构模式下就不能往里判断数值了。也就是说,你在写成
var (a, b)
的类似语法后,就无法往a
、b
上使用任何模式匹配的判别语法了。该嵌套模式匹配之语法将在稍后说明。
2、可空值类型解构模式的别样意义
在 C# 里,可空值类型一直是一种方便也不方便的数据类型。它的声明和使用都比较方便,但问题就出在它可能是 null
数值。假设前文的 Point
我们用的是可空类型的话:
nullable
是否为 null
(除非看取了 nullable
的值才行)。因此,一旦我们对这个类型进行解构:
var
模式一样。它牵扯到数据是不是 null
才可解构的问题。如果数据都是 null
了,我们就无法解构。因此,可空值类型的解构模式会先判断对象是不是不为 null
和
nullable.HasValue
是等效的,所以写nullable.HasValue
也没问题。
3、主构造器的解构模式
是的,主构造器会自动生成对应的解构函数,因此完全可以直接使用解构模式。还是使用之前的 Person
类型:
那么,有这样的语法:
这样是允许的。但你不能写 is Person (name, _, isBoy)
,因为前面的 var
关键字是这个模式匹配的固定格式,改成了 Person
的话,后面就只能看成对位模式了。
4、调用扩展方法的解构模式
解构模式和对位模式类似,编译器也支持嗅探解构模式对应的扩展方法。一般正常的实现我们可能对一些数据类型无法实现解构操作,因此我们需要扩展方法来达到一些行为。比如假设我要去获取数组的前两个元素,我们经常会使用 [0]
和 [1]
来获取,不过现在我们可以使用解构模式来完成:
T[]
请注意解构函数正常使用的时候是尽量不出现 0 或 1 个元素的解构模式,不过在这个时候也可能会遇到,因此语法没有对此进行限制。
5、解构和对位模式不要求判断元素数量至少两个
这里稍微说一个比较不容易了解到的知识点。编译器限制我们定义一个至少两个元素的值元组 ValueTuple
类型,也就是说,一个或零个的值元组类型是不被允许的:
var
而不是 ValueTuple<int>
的话,编译器会自动消去 (1)
两侧的小括号,然后直接认为它是 1;故意显式给出类型名是为了告诉你,这两个情况都是值元组不被允许的。
不过,虽然解构模式和对位模式长得都跟值元组的类型声明模式很像,但对位模式和解构模式允许和支持解构函数可以包含任意多的 out
参数用于解构,这也意味着在解构模式和对位模式里,is ()
或 is (1)
是存在的语法。
6、单元素的解构模式要手动消除二义性
在 C# 里,小括号如果不需要是会被编译器分析出来的。比如说 var a = (1 + 3)
,此时的小括号没有必要需要它。在模式匹配里,单元素的解构模式也是一种特殊的处理:它会被视为常量模式,于是,考虑下面的例子,判断就有些奇怪了:
请看这样的代码。你认为它是对的吗?答案是不对。编译器会首先认为 (42)
是常量模式,而 o
变量是 C
类型而不是一个整数,因此这个模式会导致编译器直接告知“永远都不会匹配成功”的编译器错误。
那么,怎么让它调用该解构函数来完成判别呢?答案其实很简单:消除编译器认为是常量模式的二义性即可。比如给 (42)
模式添加参数名。
7、任意类型的解构模式
对任何数据类型(当然,指针类型除外)而言,我们都是可以使用解构模式的。这一点很神奇。
可,这会被视为什么判断规则呢?不知道你知不知道一个类型叫 ITuple
?这个数据类型限制了类型具有元组的性质。所以,对任何数据类型来说的解构模式,实际上是被编译器特殊处理和优化过,并认为是在匹配该类型的数据规则。
比如 o is ()
会被视为 o is ITuple tuple && tuple.Length == 0
。注意此时 ITuple
里的 Length
属性表示的是元组的元素数。当然了,如果你这个类型具有解构函数,就不会走这个路线去判断。但是,如果一个类型既没有实现这个 ITuple
接口,又没有匹配的解构函数,就会产生编译器错误。