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>。