Main() { //下面对 Change 的调用导致编译器报错,原因是调用者为任意一个形参提供了实参,则它必须为前面的所有可选形参提供实参。 //Change(3, 2, , 4); //如果你想跳过第三个可选形参,则可以使用命名实参来达到目的。 Change(3, optionalParam2: 4, parameter1: 99); }
智能提示使用中括号指示可选形参,如下图所示:
命名实参和可选参数这两种技术都可与方法、索引器、构造函数和委托一起使用。
params,数目可变参数
如果方法签名的参数列表存在多个数量不固定的形参,那么params可以帮你解决这个问题。
在方法声明中的 params 关键字之后不允许任何其他参数,并且在方法声明中只允许一个 params 关键字。
Change(params object[] aryParameters) { } Main() { //如果不传实参,则aryParameters长度为0 Change(); Change(3); Change(3, 4, 8); Change(, }); }
方法解析与重载决策
如果同时使用命名实参、可选参数,params ,方法重载等功能时,可能会造成同一个方法调用或者实参列表可以适用多个方法签名的情况,那么就需要编译器对其做出方法解析和重载决策。
多个方法都适用调用的实参列表时,优先选择无可选参数的方法,如下图所示:
多个方法都适用调用的实参列表时,优先选择形参类型更加具体的方法,如下图所示:
多个方法(且方法的形参均为可选)都适用调用的实参列表时,优先选择可选参数更少的方法,如下图所示:
参数传递 【重难点】
概念1:值类型,值类型的变量直接包含其数据,分配在线程堆栈(Thread Stack)上。
概念2:引用类型,引用类型的变量存储对其数据(对象实例)的引用(内存地址),该引用类型的变量(内存地址)会被分配到线程堆栈上,而被引用的数据(对象实例)则会被分配到托管堆(Heap)。
概念3:参数的值传递。
概念4:参数的引用传递。
以上4种概念,一定不能混淆,特别是引用类型和引用传递。
由于本篇博文主要是讲解参数,所以在此就不对值类型和引用类型的基础概念做深入讲解了。
一般按照参数类型和传递方式的不同,可以分为以下4种参数传递的情况:
在进行参数的值传递时,当传递的参数为值类型时,实际上传递的是该值类型实例的一个拷贝副本。因此方法操作的是实例副本,所以不会对实例本身构成任何影响。
ChangeValue<T>(T oldValue) { T newValue = default(T); oldValue = newValue; } Main() { int a = 100; //传递的是值类型实例的副本,所以针对方法内部的改变丝毫不会影响到实例本身 ChangeValue(a); Console.WriteLine(a);//输出是:100 }
通过指针操作来更进一步的加深理解,注意看下图的实例的指针地址和实例副本的指针地址是不一样的:
由此可以看出这是2个不同的内存块,所以ChangeValue改变的仅仅只是实例副本的内存块里面的值。
2、引用类型参数的值传递。在进行参数的值传递时,当传递的参数为引用类型时,实际上传递的是该引用类型实例的引用的一个拷贝副本(稍微有点绕),因此方法操作的是引用类型实例的引用的副本。
如果方法不改变引用副本的指向,那么在方法中对参数所做的任何更改都将反映在该变量中。
但是,如果方法改变了引用副本的指向,那么不会对实例本身构成任何影响。
有点绕且拗口,接下来,我们上几个典型的代码例子,并加以讲解,从而帮助我们深层次的理解值传递。
ChangeValue(string value) { //此时传递进来的value副本和value都指向"init" value = ;//value副本做赋值操作,分配一个新的string类型实例对象(new string("update")),value副本已经指向 "update"(地址为0x02),而value仍然还是指向"init"(地址为0x01) } Main() { ; //value变量存的是对"init"的引用(内存地址:0x01) //那么传递的是value变量的副本(本质也就是对"init"的引用的副本,也是0x01) //这2个0x01都存储在线程堆栈上,并且都指向"init"的托管堆内存块 ChangeValue(value); //ChangeValue方法只是改变了value副本的指向,value本身的指向不受任何影响,所以结果可想而知输出“init” Console.WriteLine(value); }