C#4.0新特性之“缺省参数”的实现
回顾C#发展的历史,C#1.0完全是模仿Java,并保留了C/C++的一些特性如struct,新学者很容易上手;C#2.0加入了泛型,也与
Java1.5的泛型如出一辙;C#3.0加入了一堆语法糖,并在没有修改CLR的情况下引入了Linq,简直是神来之笔,虽然很多项目出于各
种各样如性能之类的原因没有采用,但非常适合小型程序的快速开发,减轻了程序员的工作量,也提高了代码的可读性;C#4.0增加
了动态语言的特性,从里面可以看到很多javascript、python这些动态语言的影子。虽然越来越偏离静态语言的道路,但从另一个
角度来说,这些特性也都是为了提高程序员的生产力。至于被接受与否,还是让时间来说话吧。
C#4.0关于缺省参数的新特性,相信大家都不会陌生。所谓缺省参数,顾名思义,就是在声明方法的某个参数的时候为之指定一个默认值,在调用该方法 的时候如果采用该默认值,你就无须指定该参数。和很多语言层面特性(语法糖)的实现一样,缺省参数也是编译器为我们玩的一个小花招。缺省参数最终体现为两 个特殊的自定义特性OptionalAttribute和DefaultParameterValueAttribute 。
目录
一、缺省参数的用法
二、实现缺省参数的两个特性:OptionalAttribute和DefaultParameterValueAttribute
三、直接通过OptionalAttribute和DefaultParameterValueAttribute 定义缺省参数
一、缺省参数的用法
比如下面一个TestMethod方法,后面两个参数bar和baz就是缺省参数,默认值分别为“Bar”和“Baz”。
1: static void TestMethod(string foo, string bar = "Bar", string baz = "Baz")
2: {
3: Console.WriteLine("{0, -5} - {1, -5} - {2, -5}", foo, bar, baz);
4: }
在调用TestMethod的时候,我们自由地选择采用缺省的参数值,或者覆盖该缺省值。
1: static void Main(string[] args)
2: {
3: TestMethod("Foo");
4: TestMethod("Foo", "Bar1");
5: TestMethod("Foo", "Bar1", "Baz1");
6: }
下面是输出结果:
1: Foo - Bar - Baz
2: Foo - Bar1 - Baz
3: Foo - Bar1 - Baz1
缺省参数的使用有两个简单的限制,其一是:缺省参数的声明只能放在普通参数之后。如下代码中定义的TestMethod方法中,缺省参数bar后面 跟一个非缺省参数baz,这样的代码是不能通过编译的(编译错误信息为:Optional parameters must appear after all required parameters)。
1: static void TestMethod(string foo, string bar = "Bar", string baz)
2: {
3: Console.WriteLine("{0, -5} - {1, -5} - {2, -5}", foo, bar, baz);
4: }
但是,缺省参数后面可以跟数组参数(params参数),实际上无论在什么情况下,params参数都只能是最后一个声明的参数。关于缺省参数的声明的位置限制,主要重载方法的识别机制决定的,这一点大家都很容易理解。
缺省参数的另一个限制是:指定的缺省值必须是一个常量,这就实际上为作为缺省参数的数据类型作了限制——只能是系统定义的基元类型。下面定义的 TestMethod方法中,我们定义了一个DateTime类型的缺省参数,并将参数缺省值作为DateTime.Now。由于 DateTime.Now不是常量,所以这样的代码也不能通过编译(编译错误消息:Default parameter value for 'date' must be a compile-time constant)。
1: static void TestMethod(DateTime date = DateTime.Now)
2: {
3: //Others...
4: }
二、实现缺省参数的两个特性:OptionalAttribute和DefaultParameterValueAttribute
为什么缺省参数的默认值只能接受常量呢?如果你了解了缺省参数的本质,这就不是一个问题。那么缺省参数究竟是如何实现的呢?
和很多语言层面特性(语法糖)的实现一样,缺省参数也是编译器为我们玩的一个小花招,而真正编译后的东西都是我们再熟悉不过的玩意儿。当包含缺省参 数的C#代码经过编译后,缺省参数体现在两个特殊的自定义特性OptionalAttribute和 DefaultParameterValueAttribute 。前者将参数标识为缺省参数,后者指定其缺省值。
1: [ComVisible(true), AttributeUsage(AttributeTargets.Parameter, Inherited=false)]
2: public sealed class OptionalAttribute : Attribute
3: {
4: }
5:
6: [AttributeUsage(AttributeTargets.Parameter)]
7: public sealed class DefaultParameterValueAttribute : Attribute
8: {
9: public DefaultParameterValueAttribute(object value);
10: public object Value {get; }
11: }
对于最开始我们定义的TestMethod方法,编译后的形式如下所示。
1: private static void TestMethod(string foo,
2: [Optional, DefaultParameterValue("Bar")] string bar,
3: [Optional, DefaultParameterValue("Baz")] string baz)
4: {
5: //Others..
6: }
正是因为缺省参数的默认值最终是作为DefaultParameterValueAttribute的参数存在的,所以它必须是常量。
三、直接通过OptionalAttribute和DefaultParameterValueAttribute 定义缺省参数
既然缺省参数最终体现为OptionalAttribute和DefaultParameterValueAttribute 这两个特性,我们是否可以直接通过它们来定义缺省参数呢?答案是:当然可以,下面的代码一样可以正常执行。
1: static void Main(string[] args)
2: {
3: TestMethod("Foo");
4: TestMethod("Foo","Bar1");
5: TestMethod("Foo","Bar1","Baz1");
6: }
7:
8: private static void TestMethod(string foo,
9: [Optional, DefaultParameterValue("Bar")] string bar,
10: [Optional, DefaultParameterValue("Baz")] string baz)
11: {
12: //Others..
13: }
如果调用含有缺省参数的方法,并且没有显示指定该参数,编译器在编译的时候会自动将默认值附加上去。对于上面的Main方法,下面是与之等效的编译后代码。
1: private static void Main(string[] args)
2: {
3: TestMethod("Foo", "Bar", "Baz");
4: TestMethod("Foo", "Bar1", "Baz");
5: TestMethod("Foo", "Bar1", "Baz1");
6: }
虽然说我们通过OptionalAttribute和DefaultParameterValueAttribute 这两个特性也可以定义缺省参数,但是当我们将缺省参数定义在普通参数之前是,编译器不会报错。倒是方法中缺省参数实际上就相当于普通参数了。
1: static void Main(string[] args)
2: {
3: //TestMethod("Foo","Baz");
4: //上面的方法调用无效
5: TestMethod("Foo","Bar1","Baz1");
6: }
7: private static void TestMethod(string foo,
8: [Optional, DefaultParameterValue("Bar")] string bar,
9: string baz)
10: {
11: //Others..
12: }四.dynamic ExpandoObject
熟悉js的朋友都知道js可以这么写 :
1var t =newObject();
2t.Abc = ‘something’;
3t.Value = 243;现在这个js动态语言的特性,我们也可以在c#中使用了,前提是将一个变量声明为ExpandoObject类型。如下例:
1staticvoidMain(string[] args)
2{
3dynamic t =newExpandoObject();
4t.Abc ="abc";
5t.Value = 10000;
6Console.WriteLine("t's abc = {0},t's value = {1}", t.Abc, t.Value);
7Console.ReadLine();
8 |
} |
方法参数之命名参数
命名参数让我们可以在调用方法时指定参数名字来给参数赋值,这种情况下可以忽略参数的顺序。如下方法声明:
1 |
static void DoSomething(int height, int width, string openerName, string scroll) { |
2 |
Console.WriteLine("height = {0},width = {1},openerName = {2}, scroll = {3}",height,width,openerName,scroll); |
3 |
} |
我们可以这样来调用上面声明的方法
1 |
static void Main(string[] args) |
2 |
{ |
3 |
DoSomething( scroll : "no",height : 10, width : 5, openerName : "windowname"); |
4 |
Console.ReadLine(); |
5 |
} |
很显然的这是一个语法糖,但是在方法参数很多的情况下很有意义,可以增加代码的可读性。
dymanic主要应用于下面的场景:1、自动反射
2、COM组件互操作
3、混合编程,例如IronRuby和IronPython
4、处理Html DOM对象
浙公网安备 33010602011771号