深入解析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 );

重载决策
使用命名实参和可选实参将在以下方面对重载决策产生影响:
如果方法、索引器或构造函数的各个形参均为可选,或者按名称或位置与调用语句中的单个实参对应,并且该实参可转换为形参的类型,则该方法、索引器或构造函数是执行的候选项。
如果找到多个候选项,则会将首选转换的重载决策规则应用于显式指定的实参。将忽略可选形参已省略的实参。
如果两个候选项不相上下,则会将没有可选形参的候选项作为首选项,对于这些可选形参,已在调用中为其省略了实参。这是具有较少形参的候选项的重载决策中一般首选项的结果。

(0)

相关推荐

  • c#方法中调用参数的值传递方式和引用传递方式以及ref与out的区别深入解析

    复制代码 代码如下: #define Test using System; namespace Wrox.ProCSharp.ParameterTestSample...{ class ParemeterTest ...{    static void TestInt(int[] ints,int i)    ...{        ints[0] = 100;        i = 100;    } static void TestInt(int[] ints, ref int i)    

  • asp.net(C#)函数对象参数传递的问题

    复制代码 代码如下: class Program { static void Main(string[] args) { TestClass objA = new TestClass(); objA.Name = "I am ObjA"; Console.WriteLine(String.Format("In Main:{0}", objA.Name)); TestFun(objA); Console.WriteLine(String.Format("In

  • 理解C#中参数的值和引用以及传递结构和类引用的区别

    值与引用参数之间的区别: 在 C# 中,既可以通过值也可以通过引用传递参数.在调用环境中通过引用传递参数允许函数成员(方法.属性.索引器.运算符和构造函数)更改参数的值,并保持该更改.若要通过引用传递参数,请使用 ref 或 out 关键字. 下面的示例阐释值与引用参数之间的区别: class Program { static void Main(string[] args) { int arg; // Passing by value. // The value of arg in Main

  • C#难点逐个击破(1):ref参数传递

    一般情况,方法的参数传递是通过值进行传递的,即一个对象作为参数传递给方法使用,该对象便进驻到该参数对象所在指针的内存空间(使用C来描述),也就是该对象在此位置创建了副本,当方法运行结束时,该副本将会被销毁:这种传递方式的使用占据了日常方法传参的绝大多数. 另一种情况是引用传递,它与值传递方式不同,对象传递给方法时对方法参数并没有影响,仍然返回的是受原始参数取值影响的方法,即MethodInstance(ref _refValue)调用 Method(ref _arg)方法,但_refValue对

  • c#线程间传递参数详解

    线程操作主要用到Thread类,他是定义在System.Threading.dll下.使用时需要添加这一个引用.该类提供给我们四个重载的构造函数(以下引自msdn). Thread (ParameterizedThreadStart)  初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托.  Thread (ThreadStart)  初始化 Thread 类的新实例. Thread (ParameterizedThreadStart, Int32)  初始化 Threa

  • C# 运用params修饰符来实现变长参数传递的方法

    可变数目参数的好处就是在某些情况下可以方便地对参数个数不确定情况的实现,例如计算任意数字的加权和,链接任意字符串为一个字符串等.看下例子: 复制代码 代码如下: public class Test2 {     public static void Main()     {         ShowName("小A"); //这里可以指定任意长度的参数也可以传递不同类型的参数,但要改参数类型为object        ShowName("小A", "小B

  • C#和asp.net中链接数据库中参数的几种传递方法实例代码

    复制代码 代码如下: #region 参数传递方法第一种     //参数设置方法(第一种) //SqlParameter sp = new SqlParameter("@Name", str_Name); //SqlParameter sp2 = new SqlParameter("@Pwd", str_Pwd); //cmd.Parameters.Add(sp); //cmd.Parameters.Add(sp2); #endregion //简单的一般使用第一

  • 一道关于C#参数传递的面试题分析

    本文实例分析了一道关于C#参数传递的面试题.分享给大家供大家参考.具体如下: 1.定义一个类MPigeon,调用一个Swap交互函数,问最后实参的结果是什么 类定义: public class MPigeon { public string Name { get; set; } } 交换函数: public void Swap(object a,object b) { object temp=a; a=b; b=temp; } 测试代码: private void button2_Click(o

  • C#实现向函数传递不定参数的方法

    本文实例讲述了C#实现向函数传递不定参数的方法.分享给大家供大家参考.具体实现方法如下: using System; class Min{ public int MinVla(params int [] nums){ int m; if (nums.Length == 0){ Console.WriteLine("Error: no arguments."); return 0; } m = nums[0]; foreach ( int val in nums){ if ( val &

  • C#中的多线程多参数传递详解

    之前做了一个小的应用程序,用的是c#语言,涉及到了多线程的多参数传递,经过查找资料总结了一下解决方案! 第一种解决方案的原理是:将线程执行的方法和参数都封装到一个类里面.通过实例化该类,方法就可以调用属性来实现间接的类型安全地传递多个参数.看如下代码: 复制代码 代码如下: using System;using System.Threading; //ThreadWithState 类里包含了将要执行的任务以及执行任务的方法public class ThreadWithState {//要用到的

  • C#传递参数到线程的方法汇总

    本文汇总整理了传递参数到线程的方法供大家参考,非常实用,具体内容如下: 首先我们要知道什么是线程,什么时候要用到线程,如何去使用线程,如何更好的利用线程来完成工作. 线程是程序可执行片段的最小单元,是组成运行时程序的基本单元,一个进程有至少一个线程组成.一般在并行处理等待事件的时候要用到线程,如等待网络响应,等待I/O通讯,后台事务处理等情况.使用线程其实很简单,在.net框架下面你首先要定义一个函数来完成一些工作,然后实例化一个线程对象Thread thrd = new Thread(new

  • C# 使用匿名函数解决EventHandler参数传递的难题

    首先,动态生成PictureBox,很简单, PictureBox box = new PictureBox() ; box.ImageLocation = imageRoad ; 其次,给PictureBox添加右键菜单,也不难, ContextMenu menu = new ContextMenu(); box.ContextMenu = menu ; 然后,要给右键菜单增加"删除"项,并实现删除图片事件.这个,比较麻烦. MenuItem item = new MenuItem(

随机推荐