8.5.3 新特性:命名和可选参数
C#4.0的新增特性之一就是命名和可选参数,这是一个十分独特的特性。在4.0版本之前,要调用一个方法,必须传入方法签名规定的参数个数,并且每个参数的类型也要一致,这在某些情况下是十分繁琐的,如果某个参数可以不传,又会怎样?怎样指定某个参数的默认值?怎样不按照方法签名规定的参数顺序传值?这就是我们要研究的命名和可选参数,它能满足你的这些愿望!还等什么?我们开始吧!
1.可选参数
可选参数,顾名思义,它不是必需的,英文叫做optional。我们知道,位于方法签名中的参数为形参,它扮演着“占位符”的角色,如果不为它指定值,可能会导致运行出错或不可知的结果(这里不考虑声明了形参,而在方法体却没有使用的情况),我们可以为这样的参数指定一个默认值,如代码清单8-3所示。
代码清单8-3 可选参数
int result=ms.Add(20);
int result=ms.Add(20,10);
public int Add(int a,int b=1)
{
return a+b;
}
如果没有可选参数,我们必须通过方法重载来达到相同目的,如代码清单8-4所示。
代码清单8-4 使用方法重载替代可选参数
int result=ms.Add(20);
int result=ms.Add(20,10);
public int Add(int a,int b)
{
return a+b;
}
public int Add(int a)
{
return a+1;
}
关于方法重载,我们会在8.9节进行介绍,想要提前了解的读者可以自行参阅。
对比代码清单8-3和代码清单8-4,实现相同的目的,显然前者更简洁、更方便!当然,你也可以传入两个实参,此时以你传入的实参为准,而不使用默认值。
在图8-8中,在传入参数不同的情况下,形参所代表的值和返回值情况见表8-1。
图 8-8 可选参数调用示意图
可选参数虽然好用,但使用它是要遵守一定规则:
❑可选参数不能为参数列表第一个参数,它必须位于所有必选参数之后;
❑可选参数必须指定一个默认值;
❑可选参数的默认值必须是一个常量表达式,不能为变量;
❑所有可选参数以后的参数都必须是可选参数。
提示 不仅方法的参数可以设定为可选,在后面要讲到的知识点中,类的构造函数、索引器、委托都可以指定某些参数为必选或者可选。并且,可以使用OptionalAttribute标签指定可选的参数。
2.命名参数
可选参数解决的是参数默认值的问题,而命名参数解决的是参数顺序的问题。命名参数将我们从记忆每个方法数目繁多的参数列表中解放了出来,每个参数都有一个名称(推荐使用有意义的名称),下一步就可以使用参数名来对参数进行赋值(实参),而不必在意参数的实际顺序如何。是不是听起来激动人心?下面使用一个计算矩形面积的小例子进行说明。先来看看不使用命名参数该如何,如代码清单8-5所示。
代码清单8-5 不使用命名参数的情况
public static void Main()
{
MethodSample ms=new MethodSample();
int l=10;
int w=20;
int result=ms.CalculateRectangleArea(l,w);
Console.WriteLine("The Rectangle's area is:{0}m2",result);
}
public int CalculateRectangleArea(int length,int width)
{
if(length<width)
{
return length*width;
}
return 0;
}
在上述代码中,CalculateRectangleArea方法要求传入两个参数:
❑矩形的长度(length)
❑矩形的宽度(width)
并且,这两个参数是有顺序的,前者为长度,后者为宽度。并且此方法的逻辑要求如果长度小于宽度,才计算面积,否则返回0,这里的逻辑毫无意义,仅起到约束参数顺序的作用。
我们为这个方法传入了两个实参:l和w,其值分别为10和20,满足逻辑要求,因此输出此矩形面积值:
The Rectangle's area is:200 m2
下面,我们使用命名参数对其进行改写,如代码清单8-6:
代码清单8-6 使用命名参数的情况
public static void Main()
{
MethodSample ms=new MethodSample();
int l=10;
int w=20;
int result=ms.CalculateRectangleArea(width:w,length:l);
Console.WriteLine("The Rectangle's area is:{0}m2",result);
}
public int CalculateRectangleArea(int length,int width)
{
if(length<width)
{
return length*width;
}
return 0;
}
在上述代码中,我们传入的实参顺序为先宽度,后长度,这在不使用命名参数的情况下由于第一个参数被认为是长度,后者被认为是宽度,因此(20<10)表达式不成立,应该输出0,但这里因为使用命名参数,虽然顺序不对,但因为实参信息中包含了参数名信息,因此运算的结果仍然是正确的,length形参被赋值10,width形参被赋值20,满足方法逻辑,运行结果同代码清单8-5。
3.重载决策机制
可选和命名参数导致了重载决策机制,先看下面这种情况,编译器会做何种选择呢?如代码清单8-7所示。
代码清单8-7 重载据侧机制
M(string s,int i=1);
M(object o);
M(int i,string s="Hello");
M(int i);
M(5);
当遇到这种情况的时候,编译器会根据以下规则进行判断,我们先说规则,再来分析上面这段代码。规则如下:
❑如果所有参数要么都可选,要么有一个和参数类型兼容的参数(根据名称或位置);
❑如果有多种选择,重载决策机制优先选择为参数指定了具体值的重载,而忽略那些没有为可选参数提供值的情况;
❑如果有两种选择待选,则优先选择没有省略可选参数的重载。
下面,依据上述规则来分析在代码清单8-7的情况下,M(5);执行的是哪个重载。
首先,M(string,int)被淘汰,因为实参5不能被隐式转换为string类型;
其次,M(int,string)符合条件,是待选之一,因为它的第二个参数是可选的;
显然,M(object)和M(int)也都是待选的重载;
但是,M(int,string)和M(int)都要优于M(object),因为5到int的转换要好于5到object的转换;
最后,M(int)要优于M(int,string),因为没有可选参数被省略。
因此,M(5);最后调用的是M(int)方法。