zoukankan      html  css  js  c++  java
  • [CLR via C#]9. 参数

    一、可选参数和命名参数

      在设计一个方法的参数时,可为部分或全部参数分配默认值。然后,调用这些方法的代码时可以选择不指定部分实参,接受默认值。此外,调用方法时,还可以通过指定参数名称的方式为其传递实参。比如:

    internal static class Program {
       private static Int32 s_n = 0;
       
       private static void M(Int32 x=9, String s = "A",
    DateTime dt = default(DateTime), Guid guid = new Guid()) {
          Console.WriteLine("x={0}, s={1}, dt={2}, guid={3}, x, s, dt, guid");
       }
        
       public static void Go() {
          // 1.等同于: M(9, "A", default(DateTime), new Guid());
          M();
     
          // 2. 等同于: M(8, "X", default(DateTime), new Guid());
          M(8, "X");
     
          // 3. 等同于: M(5, "A", DateTime.Now, Guid.NewGuid());
          M(5, guid: Guid.NewGuid(), dt: DateTime.Now);
     
          // 4. 等同于: M(0, "1", default(DateTime), new Guid());
          M(s_n++, s_n++.ToString());
     
          // 5. 等同于s: String t1 = "2"; Int32 t2 = 3;
          //             M(t2, t1, default(DateTime), new Guid());
          M(s: (s_n++).ToString(), x: s_n++);
       }
    }
      
      在定义的方法中,如果为部分参数指定了默认值,需注意下述原则:
      1)可以为方法、构造器方法和有参属性(C#索引器)的参数指定默认值。还可为属于委托定义一部分的参数指定默认值。然后,在调用该委托类型的一个变量时,可以省略实参,以接受默认值。
      2)有默认值的参数必须放在没有默认值的所有参数之后。换言之,一旦定义了一个有默认值的参数,它右边的所有参数也必须有默认值。但有个例外:"参数数组"这种参数必须放在所有参数(包括有默认值的这些)之后,而且数组本身不能有一个默认值。
      3)默认值必须是编译时能确定的常量值。这些参数的类型可以是C#认定的基元类型,还包括枚举类型,以及设为null的任何引用类型。对于任何值类型的一个参数,可将默认值设为值类型的一个实例,并让它的所有字段都包含零值。可以用default关键字或者new关键字来表达这个意思。如在M方法中设置dt参数和guid参数的默认值,就是用的这两种语法。
      4)注意不要重新命名(即修改)参数变量名称。否则,任何调用者如果以传参数名的方式传递实参,都必须修改它们的代码。
      5)如果方法是从模块的外部调用的,更改参数的默认值具有潜在的危险性。调用方会在它的调用中嵌入默认值。如果以后更改参数的默认值,但没有重新编译调用方所在的代码,它在调用你的方法时就会传递就得默认值。可考虑将默认值设为0/null作为哨兵值(起到占位子作用)使用。
      6)如果参数使用ref或out关键字进行了标识,就不能设置默认值。因为没有办法为这些参数传递一个有意义的默认值。
     
      使用可选或命名参数调用一个方法时,还要注意下述原则:
      1)实参可按任何顺序传递;但是,命名实参只能出现在实参列表的尾部。
      2)可按名称将实参传给没有默认值的参数。
      3)C#不允许省略都好之间的实参,比如M(1, ,DateTime.Now)。
      4)如果参数需要ref/out,为了以传参数名的方式传递实参,请使用下面语法:       
     // 方法声明
     private static void M(ref Int32 x) { ... }
     // 方法调用
     Int32 a = 5;
     M(x: ref a);
     .....
      在C#中,一旦为某个参数分配了一个默认值,编译器就会在内部像该参数应用一个定制attibute,即System.Runtime.InteropServices.OptionalAttribute。这个attribute会在最终生成的文件的元数据中持久性地存储下来。此外,编译器还会向参数引用一个名为System.Runtime.InteropServices.DefaultParameterValueAttribute的attribute,并将这个attribute持久性存储在最终文件的元数据中,然后,会向DefaultParameterValueAttribute的构造器中传递你在源代码中指定的常量值。之后,一旦编译器发现一个方法调用缺失了部分实参,就可以确定省略的是可选的实参,并从元数据中提取它们的默认值,将这些值自动嵌入调用中。
      之后,一旦编译器发现一个方法调用缺失了部分实参,就可以确定省略的是可选的实参,并从元数据中提取它们的默认值,并将这些值自动嵌入调用中。

     

    二、隐式类型的局部变量

      针对一个方法中的隐式类型的局部变量,C#允许根据初始化表达式的类型来判断它的类型。

    private static void ImplicitlyTypedLocalVariables() {
          var name = "Jeff";
          ShowVariableType(name);    // 类型是: System.String
     
          // var n = null;           // 错误
          var x = (Exception)null;   // 可以这样写,但没意义
          ShowVariableType(x);       // 类型是: System.Exception
     
          var numbers = new Int32[] { 1, 2, 3, 4 };
          ShowVariableType(numbers); // 类型是: System.Int32[]
     
          // 针对复杂类型,可减少打字量
          var collection = new Dictionary<String, Single>() { { ".NET", 4.0f } };
     
          // 类型是: System.Collections.Generic.Dictionary`2[System.String,System.Single]
          ShowVariableType(collection);
     
          foreach (var item in collection) {
             // 类型是: System.Collections.Generic.KeyValuePair`2[System.String,System.Single]
             ShowVariableType(item);
          }
       }
      隐式类型的局部变量是局部变量,不能用它声明方法的参数。也不能声明一个类型的字段。 
      用var声明的局部变量只是一种简化语法,它要求编译器根据一个表达式推断具体的数据类型。var关键字只能用于声明方法内部的局部变量,而dynamic关键字可用于局部变量,字段和参数。表达式不能转型为var,但可以转型为dynamic。必须实现初始化化var声明的变量,但无需初始化用dynamic声明的变量。
     
    三、以传递引用的方式向方法传递参数
      默认情况下,CLR假定所有的方法参数都是传值的。
      传递引用类型的对象时,对一个对象的引用(或者说指向对象的指针)会传给方法。但这个引用(或指针)本身是以传值方式传给方法的。这意味着方法能修改对象,而调用者能看到这些修改。对于值类型的实例,传给方法的是实例的一个副本,这意味着方法将获取它专用的一个值类型实例副本,调用中的实例不受影响。
      CLR中允许以传引用而非传值的方式传递参数。在C#中,这是用关键字out和ref。这两个关键字都告诉C#编译器生成的元数据来指明该参数时传引用的。编译器将生成代码来传递参数的地址,而不是传递参数本身。
      从CLR角度看,关键字out和ref完全一致。这就是说,无论用哪个关键字,都会生成相同的IL代码。另外,元数据也几乎一致。只有一个bit除外,它用于记录声明方法时指定的是out还是ref。
      C#编译器是将者两个关键字区别对待的,而且这个区别决定了有哪个方法负责初始化所引用的对象。
      如果方法的参数用out来标记,表明不指望调用者在调用方法之前初始化好了对象。被调用的方法不能读取参数的值,而且在返回前必须向这个值写入。相反,如果方法的参数用ref来标记,调用者就必须在调用方法前初始化参数的值,被调用的方法可以读取值或者写入值。
      为值类型使用out和ref,效果等同于以传值的方式传递引用类型。对于值类型,out和ref允许方法操纵单一的值类型实例。调用者必须为实例分配内存,被调用者则操纵该内存中的内容。
      对于引用类型,调用代码为一个指针分配内存(该指针指向一个引用类型的对象),被调用者则操纵这个指针。正因为如此,仅当方法"返回"对"方法知道的一个对象"的引用时,为引用类型提供out和ref才有意义。
     
    四、向方法传递可变数量的参数
      有的时候,开发人员想定义一个方法来获取可变数量的参数。为了声明方法接受可变数量的参数,如下:
       private static Int32 Add(params Int32[] values) {
          Int32 sum = 0;
          for (Int32 x = 0; x < values.Length; x++)
             sum += values[x];
          return sum;
       }

      params关键字只能应用于方法参数列表的最后一个参数。

      我们调用时可以这样:
     //显示 "15"
     Console.WriteLine(Add(new Int32[] { 1, 2, 3, 4, 5 }));

      也可以这样:

     // 显示 "15"
     Console.WriteLine(Add(1, 2, 3, 4, 5));
      由于params关键字的存在,所以可以这么做。params关键字告诉编译器向参数引用System.ParamArrayAttribute的一个实例。 
      只有方法的最后一个参数才能用params关键字(ParamArrayAttribute)来标记。另外,这个参数只能标识任意类型的一个一位数组。可为这个参数传递null值,或传递对包含另个元素的一个数组的引用。
     // 显示"0"
     Console.WriteLine(Add());
     Console.WriteLine(Add(null));
      那么如果写一个方法来获取任意数量、任意类型的参数呢?只需要修改方法原型,让它获取一个Object[]而不是Int32[]。比如
    private static void DisplayTypes(params Object[] objects) {
          foreach (Object o in objects)
             Console.WriteLine(o.GetType());
       }

    五、参数和返回类型的指导原则

      1)声明方法的参数类型时,应尽量指定最弱的类型,最好是接口而不是基类。    
      例如,如果要写一个方法处理一组数据项,最好是用接口(比如IEnumerable<T>)来声明方法的参数,而不要使用强数据类型(比如List<T>)或者更强的接口类型(比如ICollection<T>或IList<T>):
        
        //
        public void MainpulateItems<T>(IEnumerable<T> collection) { ... }
        //不好    
        public void MainpulateItems<T>(List<T> collection) { ... }
     
        //好:该方法使用弱参数类型
        public void ProcessBytes(Stream someStream) { ... }
        //不好:该方法使用强参数类型
        public void ProcessBytes(FileStream someStream) { ... }

      2)一般最好将方法的返回类型声明为最强的类型,以免受限于特定类型。例如:

        //好:该方法使用强返回值类型
        public FileStream ProcessBytes() { ... }
        //不好:该方法使用弱返回值类型
        public Stream ProcessBytes() { ... }
      第一个方法是首选的,它允许方法的调用者选择将返回对象视为一个FileStream对象或者一个Stream对象。但是,第二个方法要求调用者将返回对象视为一个Stream对象。总之,确保调用者在调用方法时有尽量大的灵活性,使方法的应用范围更大。

    六、常量性

    CLR没有提供对常量参数/对象的支持。
     
  • 相关阅读:
    windows平台HTTP代理server搭建(CCproxy)
    张冬:OpenPOWER CAPI为什么这么快?(二)
    编程算法
    从U盘安装win8系统
    两个队列模拟一个栈
    jQuery源代码学习笔记:jQuery.fn.init(selector,context,rootjQuery)代码具体解释
    安装Linux系统到u盘
    Windowsclient开发简单介绍(四)
    咏南中间件集群
    delphi调用oracle存储过程(ODAC)
  • 原文地址:https://www.cnblogs.com/zxj159/p/3544227.html
Copyright © 2011-2022 走看看