.NET中的参数
.NET中的参数
1、默认参数
当设计一个方法的参数时,可为部分或全部参数分配默认值。在调用这些方法的代码可以选择不指定部分实参而是用默认值。初次之外,调用方法时,还可以通过制定参数名称的方式为其传递实参。
声明: public void Test(int a = 0, string b = "test");
向方法传递参数时,编译器按左到右的顺序对实参进行求值。
- 可以为方法、构造器方法和有参属性(索引器)的参数指定默认值。还可以为属于委托定义一部分的参数指定默认值,在调用该委托类型的一个变量时,可以省略实参,接受默认值。
- 有默认值的参数必须放在没有默认值的所有参数之后。
- 默认值必须是编译时能确定的常量值。
- 不要重命名参数变量。否则,任何调用者如果以传参数名的方式传递参数,都必须修改它们的代码。
- 如果方法时从模块的外部调用的,更改参数的默认值是有潜在的危险性的。外部调用会在调用中嵌入默认值,如果以后修改了参数的默认值,但没有重新编译外部调用代码,外部调用还是会传递旧的默认值。
- 如果参数用ref或out关键字,就不能设置默认值,因为没有办法为这些参数传递一个有意义的默认值。
在C#中,一旦为某个参数分配了默认值,编译器就会在内部向该参数应用一个System.Runtime.InteropServices.OptionalAttribut。这个attribute会在最终生成的文件的元数据中持久性的存储下来。除此之外,编译器会向参数应用一个名为System.Runtime.InteropServices.DefaultParameterValueAttirbute,并将这个特性持久性的存储到最终文件的元数据中。然后,会向DefaultParameterValueAttribute的构造器传递在源代码中指定的常量值。这也是.NET 4之前版本的默认参数实现方式。
2、ref和out
默认情况下,CLR假定所有方法参数都是传值的。传递引用类型的对象时,对一个对象的引用(指针)会传给方法,注意这个引用(指针)本身是以传值方式传给方法的。传递值类型的对象时,传给方法的是实例的一个副本,这意味着方法将获得它专用的一个值类型实例副本,调用者中的实例不受影响。
CLR允许以传引用而非传值得方式传递参数。在C#中,是关键字ref和out,这两个关键字告诉C#编译器生成元数据来指明该参数是传引用的。编译器将生成代码来传递参数的地址而不是传递参数本身。
从CLR的角度看,关键字ref和out完全一致,它们都会生成相同的IL代码。另外,元数据也机会完全一致(使用了一个bit来区别是ref还是out)。但是C#编译器对这两个关键字是区别对待的,这两个关键字决定了由哪个方法负责初始化所引用的对象。
为值类型使用out和ref,效果等同于以传值的方式传递引用类型。对于值类型out和ref允许方法操作单一的值类型实例,调用者必须分实例分配内存,被调用者则操作该内存。对于引用类型,调用代码为一个指针分配内存(指向一个引用类型的对象),被调用者操作这个指针。
重要:CLR允许根据使用的是out还是ref参数对方法进行重载。但是如果两个重载方法只有out和ref的区别,那么是不合法的,因为两个签名的元数据表示是完全相同的。
3、params
params只能应用于方法签名中的最后一个参数,这个参数之恩给你标志任意类型的一个一维数组,可为这个参数传递null值,或传递对包含零个元素的数组的引用。
重要:在调用一个参数数量可变的方法时,会照成一些额外的性能损失(除非显式传递null),因为,数组对象必须在堆上分配,数组元素必须初始化,而且数组的内存最终必须垃圾回收。为了降低性能损失,可考虑定义几个没有使用params关键字的重载版本。可参考:System.String类的Concat方法。
4、参数及返回值类型
申明方法的参数类型时,应尽量指定最弱的类型,最好是接口而不是基类。例如:使用IList<T>而不是List<T>。这会使方法更灵活,适用于更广泛的情况。