第 9 章 参数

第 9 章 参数

本章内容:

9.1 可选参数和命名参数

规则和原则

如果在方法中为部分参数指定了默认值,请注意以下附加的规则和原则。

  • 可为方法、构造器方法和有参属性(C#索引器)的参数指定默认值。还可以属于委托定义一部分的参数指定默认值。以后调用该委托类型的变量时可省略实参来接受默认值。
  • 有默认值的参数必须放在没有默认值的所有参数之后。“参数数组”这种参数必须放在所有参数(包括有默认值的这些)之后,而且数组本身不能有一个默认值。
  • 默认值必须是编译时能确定的常量值。这些参数的类型可以是C# 认定的基元类型、枚举类型、能设为null的任何引用类型。
  • 不要重命名参数变量
  • 如果方法从模块外部调用,更改参数的默认值具有潜在的危险性。
  • 如果参数用refout关键字进行了标识,就不能设置默认值。
  • 实参可按任意顺序传递,但命名实参只能出现在实参列表的尾部。
  • 可按名称将实参传给没有默认值的参数,但所有必须的实参都必须传递(无论按位置还是按名称),编译器才能编译代码。
  • C# 不允许省略逗号之间的实参
  • 如果参数要求 ref/out,为了以传参数名的方式传递实参,请使用下面这样的语法:
1
2
3
4
5
6
// 方法声明
private static void M(ref Int32 x) { ... }

// 方法调用:
Int32 a = 5;
M(x: ref a);

9.1.2 DefaultParameterValueAttributeOptionalAttribute

在 C# 中,一旦为参数分配了默认值,编译器就会在内部向该参数应用定制特性System.Runtime.InteropServices.OptionalAttribute。该特性会在最终生成的文件的元数据中持久性地存储下来。此外,编译器向参数应用 System.Runtime.InteropServices.DefaultParameterValueAttribute 特性,并将该属性持久性存储到生成的文件的元数据中。然后,会向 DefaultParameterValueAttribute 的构造器传递你在源代码中指定的常量值。
之后,一旦编译器发现某个方法调用缺失了部分实参,就可以确定省略的是可选的实参,并从元数据中提取默认值,将值自动嵌入调用中。

9.2 隐式类型的局部变量

var声明局部变量只有一种简化语法,它要求编译器根据表达式推断具体数据类型

vardynamic 的区别
  • var关键字只能声明方法内部的局部变量,而dynamic关键字适用于局部变量、字段和参数。
  • 表达式不能转型为var,但能转型为 dynamic
  • 必须显式初始化用var声明的变量,但无需初始化用 dynamic 声明的变量。

9.3 以传引用的方式向方法传递参数

CLR 允许以传引用而非传值的方式传递参数。C# 用关键字 outref 支持这个功能。

CLR 不区分 outref,意味着无论用哪个关键字,都会生成相同的 IL 代码。另外,与那数据也几乎完全一致,只有一个 bit 除外,它用于记录声明方法时指定的是 out 还是 ref
如果方法的参数用 out 来标记,表明不指望调用者在调用方法之前初始化好了对象。被调用的方法不能读取参数的值,而且在返回前必须向这个值写入。
如果方法的参数用 ref 来标记,调用者就必须在调用该方法前初始化参数的值,被调用的方法可以读取值以及/或者向值写入。

9.4 向方法传递可变数量的参数

为了接受可变数量的参数,方法要像下面这样声明:

1
2
3
4
5
6
7
8
9
10
static Int32 Add(params Int32[] values) {
// 注意:如果愿意,可将 values 数组传给其他方法

Int32 sum = 0;
if (values != null) {
for (Int32 x = 0; x < values.Length; x++)
sum += values[x];
}  
return sum;
}

9.5 参数和返回类型的设计规范

声明方法的参数类型时,应尽量指定最弱的类型,宁愿要接口也不要基类。
一般最好是将方法的返回类型声明为最强的类型

9.6 常量性

有的语言(比如非托管 C++)允许将方法或参数声明为常量,从而禁止实例方法中的代码更改对象的任何字段,或者更改传给方法的任何对象。CLR 没有提供这个功能。