深入解析C#中的命名实参和可选实参
visual c# 2010 引入了命名实参和可选实参。利用“命名实参”,您将能够为特定形参指定实参,方法是将实参与该形参的名称关联,而不是与形参在形参列表中的位置关联。利用“可选实参”,您将能够为某些形参省略实参。这两种技术都可与方法、索引器、构造函数和委托一起使用。
在使用命名实参和可选实参时,将按实参出现在实参列表(而不是形参列表)中的顺序计算这些实参。
命名形参和可选形参一起使用时,您将能够只为可选形参列表中的少数形参提供实参。此功能大大方便了对 com 接口(例如 microsoft office 自动化 api)的调用。
命名实参
有了命名实参,您将不再需要记住或查找形参在所调用方法的形参列表中的顺序。可以按形参名称指定每个实参的形参。例如,可以采用标准方式调用计算身体质量指数 (bmi) 的函数,方法是依照该函数定义的顺序按位置发送体重和身高的实参。
calculatebmi(123, 64);
如果不记得形参的顺序,但却知道其名称,您可以按任意顺序(先发送体重或先发送身高)发送实参。
calculatebmi(weight: 123, height: 64); calculatebmi(height: 64, weight: 123);
命名实参还可以标识每个实参所表示的含义,从而改进代码的可读性。
命名实参可以放在位置实参后面,如此处所示。
calculatebmi(123, height: 64);
但是,位置实参不能放在命名实参后面。下面的语句会导致编译器错误。
calculatebmi(weight: 123, 64);
示例
下面的代码实现本节中的示例。
class namedexample { static void main(string[] args) { // the method can be called in the normal way, by using positional arguments. console.writeline(calculatebmi(123, 64)); // named arguments can be supplied for the parameters in either order. console.writeline(calculatebmi(weight: 123, height: 64)); console.writeline(calculatebmi(height: 64, weight: 123)); // positional arguments cannot follow named arguments. // the following statement causes a compiler error. //console.writeline(calculatebmi(weight: 123, 64)); // named arguments can follow positional arguments. console.writeline(calculatebmi(123, height: 64)); } static int calculatebmi(int weight, int height) { return (weight * 703) / (height * height); } }
可选参数
方法、构造函数、索引器或委托的定义可以指定其形参为必需还是可选。任何调用都必须为所有必需的形参提供实参,但可以为可选的形参省略实参。
每个可选形参都具有默认值作为其定义的一部分。如果没有为该形参发送实参,则使用默认值。默认值必须是一个表达式的以下类型之一:
常数表达式;
窗体 new valtype()的表达式, valtype 是值类型,例如 枚举 或 结构;
窗体 默认 (valtype)的表达式, valtype 是值类型。
可选形参在形参列表的末尾定义,位于任何必需的形参之后。如果调用方为一系列可选形参中的任意一个形参提供了实参,则它必须为前面的所有可选形参提供实参。实参列表中不支持使用逗号分隔的间隔。例如,在以下代码中,使用一个必选形参和两个可选形参定义实例方法 examplemethod。
public void examplemethod(int required, string optionalstr = "default string", int optionalint = 10)
下面对 examplemethod 的调用导致编译器错误,原因是为第三个形参而不是为第二个形参提供了实参。
anexample.examplemethod(3, ,4);
但是,如果您知道第三个形参的名称,则可以使用命名实参来完成任务。
anexample.examplemethod(3, optionalint: 4);
intellisense 使用括号指示可选形参,如下图所示。
示例
在下面的示例中,exampleclass 的构造函数具有一个形参,该形参是可选的。实例方法 examplemethod 具有一个必需的形参:required,以及两个可选形参:optionalstr 和 optionalint。 main 中的代码演示了可用于调用构造函数和方法的不同方式。
namespace optionalnamespace { class optionalexample { static void main(string[] args) { // instance anexample does not send an argument for the constructor's // optional parameter. exampleclass anexample = new exampleclass(); anexample.examplemethod(1, "one", 1); anexample.examplemethod(2, "two"); anexample.examplemethod(3); // instance anotherexample sends an argument for the constructor's // optional parameter. exampleclass anotherexample = new exampleclass("provided name"); anotherexample.examplemethod(1, "one", 1); anotherexample.examplemethod(2, "two"); anotherexample.examplemethod(3); // the following statements produce compiler errors. // an argument must be supplied for the first parameter, and it // must be an integer. //anexample.examplemethod("one", 1); //anexample.examplemethod(); // you cannot leave a gap in the provided arguments. //anexample.examplemethod(3, ,4); //anexample.examplemethod(3, 4); // you can use a named parameter to make the previous // statement work. anexample.examplemethod(3, optionalint: 4); } } class exampleclass { private string _name; // because the parameter for the constructor, name, has a default // value assigned to it, it is optional. public exampleclass(string name = "default name") { _name = name; } // the first parameter, required, has no default value assigned // to it. therefore, it is not optional. both optionalstr and // optionalint have default values assigned to them. they are optional. public void examplemethod(int required, string optionalstr = "default string", int optionalint = 10) { console.writeline("{0}: {1}, {2}, and {3}.", _name, required, optionalstr, optionalint); } } }
输出:
default name: 1, one, and 1. default name: 2, two, and 10. default name: 3, default string, and 10. provided name: 1, one, and 1. provided name: 2, two, and 10. provided name: 3, default string, and 10. default name: 3, default string, and 4.
com 接口
命名实参和可选实参,以及对动态对象的支持和其他增强功能大大提高了与 com api(例如 office 自动化 api)的互操作性。
例如,microsoft office excel 的 range 接口中的 autoformat 方法具有七个形参,这七个形参都是可选的。这些形参如下图所示。
在 c# 3.0 和早期版本中,每个形参都需要一个实参,如以下示例所示。
// in c# 3.0 and earlier versions, you need to supply an argument for // every parameter. the following call specifies a value for the first // parameter, and sends a placeholder value for the other six. the // default values are used for those parameters. var excelapp = new microsoft.office.interop.excel.application(); excelapp.workbooks.add(); excelapp.visible = true; var myformat = microsoft.office.interop.excel.xlrangeautoformat.xlrangeautoformataccounting1; excelapp.get_range("a1", "b4").autoformat(myformat, type.missing, type.missing, type.missing, type.missing, type.missing, type.missing);
但是,可以通过使用 c# 4.0 中引入的命名实参和可选实参来大大简化对 autoformat 的调用。如果不希望更改形参的默认值,则可以通过使用命名实参和可选实参来为可选形参省略实参。在下面的调用中,仅为七个形参中的其中一个指定了值。
// the following code shows the same call to autoformat in c# 4.0. only // the argument for which you want to provide a specific value is listed. excelapp.range["a1", "b4"].autoformat( format: myformat );
重载决策
使用命名实参和可选实参将在以下方面对重载决策产生影响:
如果方法、索引器或构造函数的各个形参均为可选,或者按名称或位置与调用语句中的单个实参对应,并且该实参可转换为形参的类型,则该方法、索引器或构造函数是执行的候选项。
如果找到多个候选项,则会将首选转换的重载决策规则应用于显式指定的实参。将忽略可选形参已省略的实参。
如果两个候选项不相上下,则会将没有可选形参的候选项作为首选项,对于这些可选形参,已在调用中为其省略了实参。这是具有较少形参的候选项的重载决策中一般首选项的结果。
推荐阅读