代码改变世界

CLR via C# 边读边想 09 - 参数

2012-07-06 15:25  richardzhaoxb  阅读(140)  评论(0编辑  收藏  举报

Optional and Named Parameters

当给方法设计参数时,可以给一个或所有的参数设置默认值,在调用时,这些有默认值的参数就成了可选参数。

但是如果全部(或者有几个连续的好几个)都是可选参数,要指定其中的值就很容易混淆,所以微软又想出一招,可以在调用时用方法的形参名来标记参数。

使用可选参数时要注意一下事项:

  • 可选参数不能出现在必填参数之前。
  • 可选参数的值必须是在编译时可确定的常量,可以是原生类型,枚举类型,值为null的引用类型。经常会用到 default, 和 new 关键字,例如:
1 private static void M(Int32 x = 9, String s = “A”, DateTimedt = default(DateTime), Guidguid = new Guid()) {
2     Console.WriteLine(“x={0}, s={1}, dt={2}, guid={3}”, x, s, dt, guid);
3 }
  • 使用 可选参数 是有风险的,如果定义处的默认值改变,在调用处如果不重新编译是不会得到改变后的默认值的。
  • 使用 named parameter 也是是有风险的,一旦方法定义中的形参名改变都会导致调用处 named parameter 也要改变。
  • 可选参数不能是 ref 或 out 的。

 

Passing Parameters by Reference to a Method

默认情况下,CLR 认为所有的参数是传值的方式。

对于参数是引用类型,也是把对象的引用地址值传过去,所以方法中改变对象的值,调用者也可以看到对象的变化,因为形参和实参都是指向同一个对象。

对于参数是值类型,是把实参的值copy一份给形参,在方法中对形参的改变不会影响调用者的实参。

在CLR中也允许传引用的方式,在C#中使用 关键字 ref 和 out。

从CLR 的角度看, ref 和 out 没什么区别,两者的不同只是针对编译器会做一些检查。如果是 out 的,在方法中不能读,返回之前必须给out赋值。 对于 ref 的值,传入方法之前必须先赋一个初始值。下面看 ref 和 out 的例子:

 1 public sealed class Program {
 2     public static void Main() {
 3         Int32 x; // x is uninitialized
 4         GetVal(out x); // x doesn’t have to be initialized.
 5         Console.WriteLine(x); // Displays "10"
 6     }
 7     private static void GetVal(out Int32 v) {
 8         v = 10; // This method must initialize v.
 9     }
10 }
 1 public sealed class Program {
 2     public static void Main() {
 3         Int32 x = 5; // x is initialized
 4         AddVal(ref x); // x must be initialized.
 5         Console.WriteLine(x); // Displays "15"
 6     }
 7     private static void AddVal(ref Int32 v) {
 8         v += 10; // This method can use the initialized value in v.
 9     }
10 }

对于引用类型来说,加不加 ref 和 out 视乎没有太大的区别。

 

Passing a Variable Number of Arguments to a Method

假如我们要写一个方法,把传入的整数数组的值加起来返回,我们可以如下:

 1 static Int32 Add(Int32[] values) {
 2     // NOTE: it is possible to pass the 'values'
 3     // array to other methods if you want to.
 4     Int32 sum = 0;
 5     if (values != null) {
 6         for (Int32 x = 0; x < values.Length; x++)
 7         sum += values[x];
 8     }
 9     return sum;
10 }

我们调用时,应该这样写:

1 public static void Main() {
2     // Displays "15"
3     Console.WriteLine(Add(new Int32[] { 1, 2, 3, 4, 5 } ));
4 }

这样写是不是很丑,微软给我们提供了一个优雅的写法,使用 params 关键字修饰方法的数组参数,这个参数必须是最后一个参数。那么之前的定义改为:

 1 static Int32 Add(params Int32[] values) {
 2     // NOTE: it is possible to pass the 'values'
 3     // array to other methods if you want to.
 4     Int32 sum = 0;
 5     if (values != null) {
 6         for (Int32 x = 0; x < values.Length; x++)
 7         sum += values[x];
 8     }
 9     return sum;
10 }

调用方法:

1 public static void Main() {
2     // Displays "15"
3     Console.WriteLine(Add(1, 2, 3, 4, 5));
4 }

 

Parameter and Return Type Guidelines

当定义方法时,尽量用一些基本类型,尽量使用接口,而不是基类。例如你要操作一个集合,参数类型最好用 IEnumerable<T>,ICollection<T> 。而不要用 List<T>。因为 IEnumerable<T> 的参数可以是 数组、List<T>,字符串,等。 应用范围更加广。就算你真的是需要一个list,也可以用 IList<T> 接口。

 

相反,定义方法的返回类型时,要尽可能的精准(strongest type)。但是如果你希望你的方法实现有一定的可扩展性,也可以返回 weakest type,例如,当你确定这个方法只返回 List<T> 时,就定义返回类型是 List<T>,但是如果你觉得可能以后也允许返回数组,那就应该定义为返回 IList<T>。