5.8 可选参数
C# 4.0新增了对可选参数的支持。声明方法时将常量值赋给参数,以后调用方法时就不必为每个参数提供实参,如代码清单5.20所示。
代码清单5.20 使用可选参数的方法
在代码清单5.20中,DirectoryCountLines()方法的单参数版本已被移除(注释掉),但Main()方法似乎仍在调用该方法(指定一个参数)。如调用时不指定extension(扩展名)参数,就使用声明时赋给extension的值(本例中是*.cs)。这样在调用代码时就可以不为该参数传递值。而在C# 3.0和更早的版本中,将不得不声明一个额外的重载版本。注意可选参数一定要放在所有必需参数(无默认值的参数)后面。另外,默认值必须是常量或其他能在编译时确定的值,这一点极大地限制了“可选参数”的应用。例如,不能像下面这样声明方法:
这是由于Environment.CurrentDirectory不是常量。而因为"*.cs"是常量,所以C#允许它作为可选参数的默认值。
设计规范
·要尽量为所有参数提供好的默认值。
·要提供简单的方法重载,必需参数的数量要少。
·考虑从最简单到最复杂的组织重载。
C# 4.0新增的另一个方法调用功能是具名参数。调用者可利用具名参数为一个参数显式赋值,而不是像以前那样只能依据参数顺序来决定哪个值赋给哪个参数,如代码清单5.21所示。
代码清单5.21 调用方法时指定参数名
代码清单5.21从Main()中调用DisplayGreeting()时将值赋给一个具名参数。调用时,两个可选参数(middleName和lastName)只指定了lastName。如一个方法有大量参数,其中许多都可选(访问Microsoft COM库时很常见),那么具名参数语法肯定能带来不少便利。但注意代价是牺牲了方法接口的灵活性。过去(至少就C#来说)参数名可自由更改,不会造成调用代码无法编译的情况。但在添加了具名参数后,参数名就成为方法接口的一部分。更改名称会导致使用具名参数的代码无法编译。
设计规范
·要将参数名视为API的一部分;如果要强调API之间的版本兼容性,就避免改变名称。
对于有经验的C#开发者,这是一个令人吃惊的限制。但该限制自.NET 1.0开始就作为CLS的一部分存在了。另外,Visual Basic一直都支持用具名参数调用方法。因此,库开发者应该早已养成了不更改参数名的习惯,这样才能成功地与其他.NET语言进行互操作,不会因为版本的变化而造成自己开发的库失效。C# 4.0只是像其他许多.NET语言早就要求的那样,对参数名的更改进行了相同的限制。
方法重载、可选参数和具名参数这几种技术一起使用,会导致难以一眼看出最终调用的是哪个方法。只有在所有参数(可选参数除外)都恰好有一个对应的实参(不管是根据名称还是位置),而且该实参具有兼容类型的情况下,才说一个调用适用于(兼容于)一个方法。虽然这限制了可调用方法的数量,但不足以唯一地标识方法。为进一步区分方法,编译器只使用调用者显式标识的参数,忽略调用者没有指定的所有可选参数。因此,假如由于其中一个方法有可选参数使得两个方法都适用,编译器最终将选择无可选参数的方法。
高级主题:方法解析
编译器从一系列“适用”的方法中选择最终调用的方法时,依据的是哪个方法最具体。假定有两个适用的方法,每个都要求将实参隐式转换成形参的类型,最终选择的是形参类型更具体(派生程度更大)的方法。
例如,假定调用者传递一个int,那么接受double的方法将优先于接受object的方法。这是由于double比object更具体。有不是double的object,但没有不是object的double,所以double更具体。
如果有多个适用的方法,但无法从中挑选出最具唯一性的,编译器就会报错,指明调用存在歧义。
例如,给定以下方法:
调用Method(42)会解析成Method(int thing),因为存在从实参类型到形参类型的完全匹配。如删除该版本,重载解析会选择long版本,因为long比double和object更具体。
C#规范包含额外的规则来决定byte、ushort、uint、ulong和其他数值类型之间的隐式转换。但在写程序时最好是使用显式转型,方便别人理解你想调用哪个目标方法。