1.简介
之前C#(2.0)和java一样是一门的纯粹的面向对象的语言,他们都使用重载而不是可选参数。但是实际上使用的其他外部程序,COM组件却经常不要求指定所有参数(这在很多VC,VB编写的组件或者操作IronPython的对象的时候很常见,他们一直使用可选参数)。这会导致一个C#程序员不得不用Type.Missing塞满整个参数列表。不过C#4.0终于支持命名参数/可选参数了。程序员可以在方法调用的时候通过命名参数指定可选参数。而这一切都是为了让.Net 4.0的动态语言运行库(DLR)在动态绑定的时候具有更好的兼容性。
2.命名与可选参数
这个对C#来说是又一个新特征,但对C++,VB,Python etc. 的程序员来说这只是很自然的一个特征。C# 4.0种的可选参数和其他语言中的用法大致相同,这里不需要VB中额外的关键词修饰,也不能像C++中只用点点点来表示可以无视,倒是和python比较像,下面的声明是合法的:
可选参数Foo
static void Foo(int a, String s = "i'm a string",
dynamic b =null, MyClass c = null)
简单来讲,C#4.0中使用可选参数必须遵循以下几条原则:
0).可选参数必须有个编译时常量作为其默认值。如果是除String之外的引用类型(包括那个特殊的dynamic类型),默认值只能是null。下面的声明是不能通过编译的:
代码
static void Foo(int a, String s = "i'm a string",
dynamic b = 2, MyClass c = new MyClass())
1).可选参数必须从右往左出现在参数列表中(必须后出现),可选参数右边的参数(如果有的话)必须是可选参数。下面的声明是不能通过编译的:
代码
static void Foo(String s = "i'm a string", int a,
dynamic b = null, MyClass c = null)
2).可选参数的赋值必须通过命名参数的方式指定,即必须使用可选参数的参数名称对其进行赋值。而非可选参数则不一定要用命名参数。如下的调用都是合法的:
Foo calls
Foo(2);
Foo(a: 2);
Foo(2, s: "hello", b: 3.14);
Foo(2, s: "hello", b: 3.14, c:new MyClass());
Foo(2, c: new MyClass());
特别的,一旦调用方法时使用的是命名参数,则命名参数的位置可以是任意顺序,如下的调用是合法的:
代码
Foo(b:3.14, c:new MyClass(), a:2);
唯一的影响就是上述调用中会对b先求值,然后再是c和a。函数总是按照参数出现的顺序进行求值操作的。
另外,可选参数不仅适用于普通的方法,还适用于构造器,索引器中,本质上它们没有什么不同。
3.可选参数与重载决策
毫无疑问,命名参数和可选参数让CLR在方法的重载决策(overload resolution)变得稍微复杂了一些,不用担心,这里你只需要搞清楚重载决策的下面几个特点就可以了:
0).在带可选参数的方法签名中,重载决策不会认可被可选参数代替的重载版本,比如下面两个声明:
public static void Foo(int a, String s = "i'm a string", dynamic b = null, MyClass c = null);
public static void Foo(int a, String s = "i'm a string");
如果按照以下方式调用,编译器会提示你它已经被上面两个方法confused了:
Foo(2);
Foo(a: 2);
1).在调用方式同样合法的情况下,重载决策会优先选择不带可选参数的重载版本。比如下面两个方法:
public static void Foo(int a, String s = "i'm a string", dynamic b = null, MyClass c = null);
public static void Foo(int a);
如果使用以下方式调用:被调用的会是void Foo(int a);这个版本:
Foo(2);
Foo(a: 2);
2).在调用方式同样合法的情况下,重载决策会优先选择类型最为匹配(最易转化)的重载,例如下面两个方法:
public static void Foo(byte a, String s = "i'm a string", dynamic b = null, MyClass c = null);
public static void Foo(object a);
Foo(2)和Foo(a:2)都将调用前一个方法,因为int到byte是值类型之间的转化,其代价要比从int转到object的代价低。
4.C#4.0中的COM互操作
在上一篇文章中提到的动态类型绑定和本文前面提到的命名参数,可选参数都为CLR的COM互操作提供了便利。有了动态类型,访问COM对象时不必再对返回的COM对象进行显式的类型转换了,因为返回的是一个dynamic类型的对象,我们可以直接在它上面调用方法/属性/索引器等,例如excelApp.Cells[row,column].Value = "some value"; 这里的Cells[row,column]返回的是一个dynamic类型的对象,而之前都是返回的object需要显式转换才能操作它,比如:((Excel.Range)excelApp.Cells[row,column]).Value = "some value";
而有了可选参数,广大程序员们也不必再猛力用Type.Missing填充整个参数列表了,比如Office12的Excel.ApplicatioinClass中就利用了可选参数定义了一个intersect方法:
代码
public virtual Range Intersect(Range Arg1, Range Arg2, object Arg3 = Type.Missing, object Arg4 = Type.Missing, object Arg5 = Type.Missing, object Arg6 = Type.Missing, object Arg7 = Type.Missing, object Arg8 = Type.Missing, object Arg9 = Type.Missing, object Arg10 = Type.Missing, object Arg11 = Type.Missing, object Arg12 = Type.Missing, object Arg13 = Type.Missing, object Arg14 = Type.Missing, object Arg15 = Type.Missing, object Arg16 = Type.Missing, object Arg17 = Type.Missing, object Arg18 = Type.Missing, object Arg19 = Type.Missing, object Arg20 = Type.Missing, object Arg21 = Type.Missing, object Arg22 = Type.Missing, object Arg23 = Type.Missing, object Arg24 = Type.Missing, object Arg25 = Type.Missing, object Arg26 = Type.Missing, object Arg27 = Type.Missing, object Arg28 = Type.Missing, object Arg29 = Type.Missing, object Arg30 = Type.Missing);
换做以前要手动指定这三十个参数的确是一件抓狂的事情。
在性能方面,由于C#4种编译器可以把PIAs按需部分编译到你的程序集中,不必每次都完全load这组庞大的互操作程序集,这对提高COM交互性能很有帮助。另外还有一个语法糖就是在COM调用的时候可以不使用ref传递参数,这避免了程序员自行创建临时变量来hold这些参数,不过这里不使用ref并不代表不按引用传递而按值传递,实际上C#编译器会自行帮你创建临时变量并任然按引用把参数传递给调用者。
5.总结
C#4.0中很大一部分特征弥补了它之前的一些令开发者不爽的地方,无论是动态类型还是可选参数,新的C#让那些和各种组件(COM,IronPython etc.)打交道的程序员获得一定程度的解脱。C#越来越变得以人为本,更确切地,以程序员为本。