欢迎光临散文网 会员登陆 & 注册

第 117 讲:C# 4 之命名和可选参数

2022-06-14 21:24 作者:SunnieShine  | 我要投稿

今天我们要说的是命名参数和可选参数。这个特性是为了更加轻便、灵活使用参数而制定的语法。先来说可选参数。

Part 1 可选参数

可选参数(Optional Parameter)是一种将两个重载版本代码基本一致的时候,抽取成一个方法的操作。

1-1 基本语法

比如说,我现在要使用冒泡排序法来排序,不过我们为了让用户可以灵活使用,我们再提供一个重载版本,让用户自定义排序的委托。

可以发现,两个代码唯一的区别是参数不同,以及 if 判断不同。显然,既然大部分代码相同,就小部分不一样的话,我们这么重载虽然可以运行,但不便于维护。于是,C# 4 允许我们往参数上赋初始常量数值。

我们注意到第 7 到第 9 行代码,我们使用了一个三目运算符来判断这个参数。如果这个参数是 null,就说明它是初始值,于是就使用 int 的默认比较规则来进行比较;否则,如果定义了的话,就使用 comparison 参数来进行比较。

注意,由于只有常量是安全使用,所以只能往参数上赋常量数值。如果你要赋变量或者是别的成员引用的话,是不允许的,毕竟你没办法确定这样做是否会变更对象的信息,导致不安全的事情发生。

我们把 comparison 参数称为可选参数。它上面有一个默认数值。那么这个方法怎么使用和调用呢?

由于该参数具有默认数值,因此如果你不使用参数的话,可以完全不考虑此参数,直接当成“少一个参数”的方法来进行调用。比如说,这个参数有两个参数,第一个参数必须传入,那么我们传入 arr 变量;而第二个参数保持 null 就不管它:

就可以了。它和写成 Sort(arr, null); 是一样的。

1-2 注意事项

除了前面说的只能赋常量作为默认数值这个限制以外,还具有一些注意事项。

第一个,它并不会影响你代码的执行和签名规则。它只是给参数带了一个初始数值,但是参数的个数,参数的类型等等信息,都是不受影响的,所以比如重载两个方法,如果仅仅区别在于可选参数上的话(比如有一个重载有赋默认参数数值,有一个没有),这样并不会构成重载。

第二个,它的有无不仅仅是可以用于实际方法上,也可以用于抽象的成员上。这里的抽象除了指抽象类型(包含 abstract 修饰符的方法),还可以用户接口的方法成员。举个例子:

如代码所示,C 类型同时实现了两个接口 IJ,且这两个接口里的方法 F 唯一区别只有参数是可选和不可选而已。但是,在 C 类型的实现上,是不需要你关心是否也需要带默认参数数值的。在第一点里我没说过,它不影响程序的编译和重载规则,因此 IJ 即使有可选参数的区别,但它们俩不构成逻辑上的重载。换句话说,哪怕这俩方法写在同一个接口里,编译器是会报错的,毕竟会被认为是同一个方法:同一个方法不能重复声明。

但是,在实现上,C 类型不必关心该参数是否是可选的。这是为什么呢?因为该参数是否可选取决于你自己的实现,所以你需要就设定,不需要就不设定,至少编译器和运行时都知道你下一步的调用行为。我们使用多态规则来试试吧:

我们实例化了两个 C 类型的对象,然后分别赋值给 IJ 接口的实例。因为 C 类型实现了这俩接口,所以这种转换是成功的。接着,我们对 ij 变量都调用各自里面包含的 F 方法。由于 I 接口里包含的 F 方法具有可选参数,因此你可以对此使用这样的特性,不给该参数赋值,这样的话,参数默认数值就是 42;而 J 接口的方法参数不是可选参数,因此必须传入数值进去。

这样程序也不会有问题,而且也是预期的。

第三个,不仅参数的默认数值只能设置为常量,而且还不能有隐式转换。举个例子。

这种奇怪的赋值是合理的吗?答案是否定的。这种赋值是错误的。虽然我们知道,字符串可以往 IEnumerable<char> 赋值,毕竟 string 实现了这个接口类型,但在可选参数上,这样的赋值是不被允许的,原因在于它具有一次隐式转换。而隐式转换是运行时行为,换句话说,这种操作是不可在编译时期而推断的行为,毕竟你不知道后果如何。当然了,这里的转换我们可以猜想到可能是没有毛病的,但是对于一些我们自定义的类型来说,假设它们实现了这样的接口,然后你去将字符串赋值过去。假设这个语法允许,那么它就会强制在编译时期得到一次隐式转换的规则,但你可以在这个隐式转换的过程植入很多不是预期的代码,然后逃避编译器的检查,这样是很危险的。所以,这种赋值过程不被允许。

第四个,可选参数只能放在参数表列的末尾。举个例子,比如你这么写代码是不可以的:

假设这样的语法成立,我们调用该方法的时候,如果不对可选参数赋值,那么方法就成了这样:

因为第一个参数我们不需要传入数值,而你又必须标记一个逗号来分隔参数,暗示 42 是第二个参数的位置上的数值。这样肯定是不合语法逻辑和规则的。因此,C# 不允许这样的语法。不过,类似这种缺省的调用语法,在别的编程语言可能是有的,比如 Visual Basic 里是支持这种写法的。

第五个,可选参数不一定只能有一个。可选参数只要放在参数表列的末尾,你想有多少个都行,你甚至全都是可选参数也行。

Part 2 命名参数

命名参数(Named Parameter)的语法是,允许用户将参数名写在调用方。

2-1 基本语法

举个例子,还是使用冒泡排序:

比如在第 4 行,我们传入一个 arr 的时候,还允许我们手动写 array: 来表示这个参数。嗯,说完了。

2-2 这有什么用?

可能有很多小伙伴觉得,这能有什么用?我为啥要花时间去多写一下参数名称?它的存在是有两点意义:

  1. 标注一些常量参数传入的时候的参数名,这样使得代码更具可读性;

  2. 约束参数调用,使得我们的可选参数和必需参数(Required Parameter)使用起来更灵活。

我们来说说这两点。

第一。假如我们传入的参数是一个常量或字面量数值,这个时候如果没有标注的话,就会显得很难去看,特别是参数特别多的时候:

假设我有一个带 6 个参数的方法。调用的时候假设长这样:

对是对,但是我咋知道这些参数是啥。难不成我挨个都要去把鼠标放上去看?这不是遭罪么。那么我们有了命名参数之后,参数名写出来就显得很方便了:

虽然代码更长了,但是起码做到了参数名确定起来,代码可读性提高的作用。它也不影响你程序运行,写不写都对,但写了更好看,所以命名参数有这个用。

第二。我们再来看这个方法。假如我们对最后两个参数搞成可选参数:

假如我指向给 maximumValue 参数赋一个另外的数值,但 minimumValue 我还是想用这个 0,这能做到吗?

能。有了命名参数就能。我们只需要给前四个参数赋值之后,带上参数名即可:

这样写之后,编译器就知道,前面四个参数数值对位赋值到前面四个方法的参数里去,而最后一个参数由于给了参数名,因此会被识别为最后一个参数 maximumValue,于是赋值给这个参数上。如果你不写的话,这个时候按照赋值的匹配规则,顺次赋值到方法参数之中去,就会让 minimumValue 参数赋上 60 的数值,这就不是我们想要的结果了。因此,命名参数还有一个强约束可选参数的赋值的功能。

这就是这两种参数的用法。我们就说完了。C# 4 也就全部结束了。

接下来是 C# 5。C# 5 的特性也不多(数下来一共也就三个特性),但是会有开幕雷击的效果,因为第一个特性“异步方法”是特别难的点。在这个特性里我们会接触到多线程的新架构和模型,这也是我在早期讲解多线程的时候挖的坑。在这里我们就会填坑完善这个多线程的使用体系和规则;另外,它还带有一个全新的语法:asyncawait 关键字。如果用不好的话,会让人完全不知道这个语法设计起来到底应该怎么用。所以这个特性特别有得说。

而我打算停更一段时间,因为确实有点累,每四天更新一次这个语法教程的话,前面的语法还好,特别是这个 C# 5 的这个语法,有点复杂,四天说不定都不一定赶得上,而且感觉腱鞘炎有些严重了,所以我想暂时先休息一阵子。


第 117 讲:C# 4 之命名和可选参数的评论 (共 条)

分享到微博请遵守国家法律