一、可选参数和命名参数

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

internal static class Program {
private static Int32 s_n = ; private static void M(Int32 x=, 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(, "X"); // 3. 等同于: M(5, "A", DateTime.Now, Guid.NewGuid());
M(, 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 = ;
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[] { , , , };
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 = ;
for (Int32 x = ; x < values.Length; x++)
sum += values[x];
return sum;
}

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

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

  也可以这样:

 // 显示 "15"
Console.WriteLine(Add(, , , , ));
  由于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没有提供对常量参数/对象的支持。

 

[CLR via C#]9. 参数的更多相关文章

  1. 【CLR in c#】参数

    1.可选参数和命名参数 设计一个参数时,可为部分或全部参数分配默认值,调用这些方法的代码可以选择不指定部分实参,接受默认值,还可以通过制定参数名称的方式传递实参.如下 class CLR可选参数 { ...

  2. CLR via C#(10)-参数

    一. 命名参数.可选参数 命名参数和可选参数是在Visual C#2010中引入的新特性. 笨地儿我个瓜不兮兮的,今天才知道. 可选参数:定义方法时为参数设置默认值,调用该方法时可以省略为某些形参指定 ...

  3. CLR via C#深解笔记四 - 方法、参数、属性

    实例构造器和类(引用类型) 构造器(constructor)是允许将类型的实例初始化为良好状态的一种特殊方法.构造器方法在“方法定义元数据表”中始终叫.ctor. 创建一个引用类型的实例时: #1, ...

  4. <NET CLR via c# 第4版>笔记 第9章 参数

    9.1 可选参数和命名参数 class Program { private static int s_n = 0; private static void M(int x = 9, string s ...

  5. CLR via c#读书笔记六:参数

    注:书本第9单参数 CLR默认所有方法参数都传值.引用本身是值引的,意味左方法能修改对象,而调用都能看到这些修改.值类型,传的是实例的一个副本,所以调用者不受影响. (和以前理解的不一样.默认都是传值 ...

  6. 重温CLR(六)方法和参数

    实例构造器和类(引用类型) 构造器是将类型的实例初始化为良好状态的特殊方法.构造器方法在“方法定义元数据表”中始终叫做.ctor(constructor的简称).创建引用类型的实例时,首先为实例的数据 ...

  7. CLR总览

    Contents 第1章CLR的执行模型... 4 1.1将源代码编译成托管代码模块... 4 1.2 将托管模块合并成程序集... 6 1.3加载公共语言运行时... 7 1.4执行程序集的代码.. ...

  8. CLR 完全介绍

    From: http://msdn.microsoft.com/zh-cn/magazine/cc164193.aspx http://msdn.microsoft.com/en-us/magazin ...

  9. [译]C# 7系列,Part 8: in Parameters in参数

    原文:https://blogs.msdn.microsoft.com/mazhou/2018/01/08/c-7-series-part-8-in-parameters/ 背景 默认情况下,方法参数 ...

随机推荐

  1. Faster R-CNN CPU环境搭建

    操作系统: bigtop@bigtop-SdcOS-Hypervisor:~/py-faster-rcnn/tools$ cat /etc/issue Ubuntu LTS \n \l Python版 ...

  2. Eclipse中集成Tomcat

    问题: 很多时候在Eclipse中启动Tmocat后,不能访问本机的localhost:8080主页,并且其他项目也不能访问. 原因: 打开Tomcat下的webapp后也找补到项目目录,这是因为Ec ...

  3. [leetode]Binary Search Tree Iterator

    用个stack模拟递归即可 /** * Definition for binary tree * struct TreeNode { * int val; * TreeNode *left; * Tr ...

  4. saiku 分布式实践

    saiku比较吃内存,一旦人多了,那么内存可能不够,所以会考虑主从结构,分担压力.为了保证数据的稳定性,也会有类似的考虑,那么问题来了,如何实现saiku的分布式搭建哪? 我阅读了一些国内的文章,没有 ...

  5. mshadow笔记

    矩阵维度表示和正常相反. a[2][3],行2列3,a.shape.shape_[0]=3,a.shape.shape_[1]=2. pred.Resize( Shape2( batch_size, ...

  6. VMware虚拟机无法识别U盘解决方案

    1. 本机情况: Win7操作系统,VMware虚拟机,虚拟机版本:VMware 7.1,安装Ubuntu10.10,现要求在主机上插入U盘,在虚拟机中显示.   2. 遇到问题: U盘只在Win7主 ...

  7. Python 中的进程、线程、协程、同步、异步、回调

    进程和线程究竟是什么东西?传统网络服务模型是如何工作的?协程和线程的关系和区别有哪些?IO过程在什么时间发生? 一.上下文切换技术 简述 在进一步之前,让我们先回顾一下各种上下文切换技术. 不过首先说 ...

  8. linux samba 服务配置及日志管理

    2012-01-16    安装samba共需3个rpm包   samba-common-3.0.23c-2.i386.rpm  samba-3.0.23c-2.i386.rpm  samba-cli ...

  9. easyui 键盘控制tree 上下

    $.extend($.fn.tree.methods, { highlight: function(jq, target){ return jq.each(function(){ $(this).fi ...

  10. 如何准备PMP考试?

    东西在精,而不在多.话不多说,干货如下: 1.参加培训,不要持续时间太长,通常情况下3个月时间足够了:许多和我一起参加培训的学员,有时候准备6个月时间,反而没有3个月冲刺的时间考试结果好. 2.培训老 ...