3.4 使用可选参数和命名参数前面讲述了如何定义重载的方法来实现一个方法的不同版本,让它们获取不同的参数。生成一个使用了重载方法的应用程序时,编译器针对每个方法调用,都会判断应该使用方法的哪一个版本。这是面向对象语言的一个很常见的功能,并非仅C#才支持。
然而,开发人员完全可能采用其他语言和技术来生成Windows应用程序和组件,而且那些语言和技术可能并不遵守这些规则。C#和其他面向.NET Framework的语言的一项关键的能力就是能够与使用其他技术开发的应用程序和组件进行互操作。MicrosoftWindows使用的一项主要技术是组件对象模型(Component Object Model,COM)。COM不支持重载方法;相反,它允许方法获取可选参数。为了方便在C#解决方案中集成COM库和组件,C#现在也提供了对可选参数的支持。
可选参数在其他情况下也很有用。有的时候,参数类型差异不足以使编译器区分两个不同的实现,造成无法使用重载技术。在这个时候,可选参数能提供一个简单、好用的解决方案。例如以下方法:
public void DoWorkWithData(int intData, float floatData, int moreIntData)
{
...
}
DoWorkWithData方法获取三个参数:两个int和一个float。现在,假定你想提供只获取两个参数(intData和floatData)的一个DoWorkWithData方法的实现:
public void DoWorkWithData(int intData, float floatData)
{
...
}
写一个语句调用DoWorkWithData方法时,可提供恰当类型的两个或者三个参数,编译器会根据类型信息判断应该调用哪个重载的版本:
int arg1 = 99;
float arg2 = 100.0F;
int arg3 = 101;
DoWorkWithData(arg1, arg2, arg3); // 调用三个参数的重载版本
DoWorkWithData(arg1, arg2); // 调用两个参数的重载版本
到目前为止一切都还好。但是,如果你要实现DoWorkWithData的另外两个版本,它们只获取第一个参数和第二个参数,那么你或许会草率地写出以下的重载版本:
public void DoWorkWithData(int intData)
{
...
}
public void DoWorkWithData(int moreIntData)
{
...
}
现在的问题在于,对于编译器来说,这两个重载版本是完全一致的,程序将无法通过编译。编译器会报告以下错误消息:类型"typename’"已定义了一个名为"DoWorkWithData"的具有相同参数类型的成员。为了理解为什么会这样,可以采取反证的方式。假定上述代码合法,那么执行以下语句时:
int arg1 = 99;
int arg3 = 101;
DoWorkWithData(arg1);
DoWorkWithData(arg3);
应该调用DoWorkWithData的哪个重载版本?在这种情况下,使用可选参数和命名参数可以有效地解决这个问题。
3.4.1 定义可选参数为了指定一个参数是可选的,可以在定义方法时为该参数提供一个默认值。默认值是使用赋值操作符来赋予的。在下面的optMethod方法中,第一个参数是必须的,因为它没有提供默认值,但第二个和第三个参数是可选的:
void optMethod(int first, double second = 0.0, string third = "Hello")
{
...
}
所有可选的参数都只能放在必须的参数之后。调用支持可选参数的一个方法时,采用的方式和调用其他任何方法无异。都是指定方法名,并提供任何必须的参数(实参)。区别在于,调用支持可选参数的方法时,可省略对应的实参。方法运行时,会为这些省略的实参使用默认值。在下例中,第一个optMethod方法调用为全部3个参数都提供了值。第二个调用则只指定了两个实参,这些值应用于第一个和第二个参数。方法运行时,第三个参数将使用默认值"Hello"。
optMethod(99, 123.45, "World"); // 全部3个参数都提供了实参
optMethod(100, 54.321); // 只为前两个参数提供了实参
3.4.2 传递命名参数C#默认根据每个实参在方法调用中的位置判断它们对应于哪个形参。所以,在上一节的第二个示例方法调用中,两个实参分别传给optMethod方法的first和second形参,因为它们在方法声明中的顺序如此。现在,C#还允许按照名称指定参数。利用这个功能,就可以按照不同的顺序传递实参了。为了将一个实参作为命名参数来传递,必须输入参数名,一个冒号,然后是要传递的值。下例执行和上一节的例子相同的功能,只是参数按名称来指定:
optMethod(first : 99, second : 123.45, third : "World");
optMethod(first : 100, second : 54.321);
利用命名参数,实参就可以按任意顺序传递。可以像下面这样重写调用optMethod方法的代码:
optMethod(third : "World", second : 123.45, first : 99);
optMethod(second : 54.321, first : 100);
这个功能还允许你省略实参。例如,调用optMethod方法时,可以只指定first和third这两个参数的值,second参数则使用默认值。如下所示:
optMethod(first : 99, third : "World");
另外,还可兼按位置和名称来指定实参。然而,如果使用这个技术,必须先指定好所有按照位置的实参,再指定命名的实参:
optMethod(99, third : "World"); // 第一个实参是按位置来定的
3.4.3 消除可选参数和命名参数的歧义使用可选参数和命名参数可能造成代码的歧义。你需要理解编译器如何解决这些歧义,否则可能发现应用程序出现非预期的行为。假定optMethod方法被定义成一个重载的方法,如下所示:
void optMethod(int first, double second = 0.0, string third = "Hello")
{
...
}
void optMethod(int first, double second = 1.0, string third = "Goodbye", int fourth = 100 )
{
...
}
这是完全合法的C#代码,它符合方法重载规则。编译器能区分这两个方法,因为两者的参数列表不同。然而,如果试图调用optMethod方法,同时忽略和它的一个或多个可选参数对应的实参,就可能出现问题:
optMethod(1, 2.5. "World");
上述代码同样合法,但应该调用optMethod方法的哪一个版本呢?答案是和方法调用最匹配的那个版本。所以,最后选择的是获取3个参数的版本,而不是获取4个参数的版本。这确实说得通,那么再来看看以下调用:
optMethod(1, fourth : 101);
在上述代码中,对optMethod的调用省略了second和third参数的实参,但通过命名参数的形式为fourth参数提供了实参。optMethod只有一个版本能匹配这个调用,所以这不是一个问题。但是,下面这个调用就有点伤脑筋了:
optMethod(1, 2.5);
这一次,optMethod的两个版本都不能完全匹配提供的实参列表。在两个版本中,second,third和fourth都是可选参数。所以,应该选择获取3个参数的optMethod版本,为third参数使用默认值,还是应该选择获取4个参数的optMethod版本,为third和fourth参数使用默认值呢?答案是两个都不选。编译器认为这是一个存在歧义的方法调用,所以不允许编译应用程序。下面列举了同样存在歧义的optMethod方法调用:
optMethod(1, third : "World");
optMethod(1);
optMethod(second : 2.5, first : 1);
廭剉鉔x0<