一、参数
1.参数是什么?
很多情况下,我们声明一个方法,就是为了它的返回值。这我们只需要在方法头中指定方法的返回类型,并且在方法体中使用return语句即可完成。但是,还有一些情况下,只通过这种方式是实现不了的。比如,需要将一些外部数据数据传入方法参与运算,或者需要多个返回值。这时,我们就需要一种称为“参数”的变量来充当这个传递员的角色。
参数就是声明在方法头的参数列表中的特殊变量,它的作用就是为方法传入数据或将方法体中得到的结果传出方法。
2.形参和实参
1).形参
形参就是声明在方法头的参数列表中的一种本地变量。它的声明和普通的本地变量一样,需要有类型和变量名;一个方法可以有任意数目的形参声明,多个形参声明之间用逗号分隔;除了output参数而外,参数在使用之前都要初始化为明确的值,否则方法就会报错。下面是一个带有两个形参列表的方法示例:
1 /// <summary> 2 /// 输出两数之和 3 /// </summary> 4 /// <param name="x">int类型的形参x</param> 5 /// <param name="y">float类型形参y</param> 6 public void PrintSum(int x, float y) 7 { 8 // 在方法体中,用实参初始化之后的形参的使用和本地变量一样 9 float z = x + y; 10 Console.WriteLine("{0} + {1} = {2}", x, y, z); 11 }
2).实参
上面提到,调用一个带参数的方法时,形参的值必须在代码开始执行之前被初始化。这个用于初始化形参的变量或者表达式就称作实参(argument)。实参位于方法调用的参数列表中;每个实参的数据类型必须和声明它的形参的数据类型一致,或者可以隐式转换。下面是调用上一个方法传递实参的示例:
1 // 调用方法,传递实参。注意:实参必须是一个确定的值或者具有确定值的表达式 2 p.PrintSum(1, 2f);
3.值参数
值参数就是实参通过复制“实际值”到形参的方式把数据传递到方法体。值参数是默认的参数类型。当使用值参数调用方法时,系统做如下操作:第一步,在栈中为形参分配空间;第二步,复制实参的值到形参。下面是一个值参数的示例:

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var p = new Program(); 6 var a1 = new MyClass(); 7 var a2 = 10; 8 // 方法调用:传实参,值参数的实参不一定是变量,可以是计算成相应类型值的表达式 9 p.MyMethod(a1, a2 + 5); 10 } 11 12 // 方法声明:声明形参 13 void MyMethod(MyClass f1, int f2) 14 { 15 f1.val += 5; 16 f2 += 5; 17 } 18 } 19 20 class MyClass 21 { 22 public int val = 20; 23 }
下图说明这一方法调用过程:
4.引用参数
引用参数就是一种实参复制引用地址到实参的参数。使用引用参数的方式:第一,在方法的声明和调用中都要使用ref修饰符;第二,实参必须是变量,而且在方法调用之前必须被赋值。引用参数的形参变量和实参变量指向相同的内存位置,在方法执行过程中对实参做的任何修改对形参都是可见的。
下面的示例改写了MyMethod方法:

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var p = new Program(); 6 var a1 = new MyClass(); 7 var a2 = 10; 8 // 方法调用:传实参,引用参数的实参必须是一个已赋值的变量 9 // p.MyMethod(ref a1, ref a2 + 5); 错误,实参必须是一个已赋值的变量。 10 p.MyMethod(ref a1, ref a2); 11 } 12 13 // 方法声明:使用ref关键字声明引用形参 14 void MyMethod(ref MyClass f1, ref int f2) 15 { 16 f1.val += 5; 17 f2 += 5; 18 } 19 } 20 21 class MyClass 22 { 23 public int val = 20; 24 }
下图说明上面方法调用过程:
5.输出参数
输出参数用于从方法体将数据传出到调用代码。输出参数的使用方式:第一,在声明和调用方法的时候都必须使用output修饰符;第二,实参必须是变量,该变量没有必要赋值,因为它会在方法体内部赋值,然后传出方法。int类型的TryPase方法就是一个使用输出参数的典型例子: public static bool TryParse(string s, out int result);
下面是使用输出参数改写上面方法的示例:

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var p = new Program(); 6 MyClass a1 = null; 7 int a2; 8 // 方法调用:使用out修饰符传递实参 9 p.MyMethod(out a1, out a2); 10 } 11 12 // 方法声明:使用out修饰符声明输出形参 13 void MyMethod(out MyClass f1, out int f2) 14 { 15 // 在方法体内部为输出参数赋值 16 f1 = new MyClass(); 17 f1.val = 5; 18 f2 = 10; 19 } 20 } 21 22 class MyClass 23 { 24 public int val = 20; 25 }
下图是该示例执行过程的说明:
6.命名参数
从参数的位置方面来讲,至此,我们使用的参数的每个形参和实参的位置都是一一对应的,这称为位置参数。位置参数是默认的参数形式。C#4.0中提出了命名参数的概念:命名参数在方法声明中的形参列表和位置参数一样,没有什么变化;在方法调用的时候,采用“形参名:实参值”的形式指定参数的名字,就可以以任意的顺序在方法调用的参数列表中列出实参。在方法调用中,可以将位置参数和命名参数混合使用,但所有位置参数必须在命名参数之前列出。

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var p = new Program(); 6 MyClass a1 = new MyClass(); 7 int a2 = 10; 8 // 方法调用:使用命名参数 9 p.MyMethod(f2: a2, f1: a1); 10 11 // 方法调用:位置参数和命名参数混合使用,但是位置参数必须在命名参数之前。 12 p.MyMethod(a1, f2: a2); 13 14 } 15 16 // 方法声明:使用默认的位置参数 17 void MyMethod(MyClass f1, int f2) 18 { 19 f1.val += 5; 20 f2 += 10; 21 } 22 } 23 24 class MyClass 25 { 26 public int val = 20; 27 }
7.可选参数
C#4.0引入了可选参数:就是在方法声明的时候使用初始化变量的方法为参数提供默认值,而在方法调用的时候,既可以包含该参数,也可以省略它。可选参数的使用条件:第一,可选参数的类型必须为值类型或者值为null的引用类型;第二,可选参数必须在所有必填参数之后,如果有params参数,必须在可选参数之后。下面是可选参数的示例:

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var p = new Program(); 6 MyClass a1 = new MyClass(); 7 int a2 = 10; 8 // 方法调用:为可选参数f2提供显式值 9 p.MyMethod(a1, a2); 10 11 // 方法调用:使用可选参数的默认值。 12 p.MyMethod(a1); 13 14 } 15 16 // 方法声明:为int类型的参数f2提供默认值 17 void MyMethod(MyClass f1, int f2 = 5) 18 { 19 f1.val += 5; 20 f2 += 10; 21 } 22 } 23 24 class MyClass 25 { 26 public int val = 20; 27 }
8.参数数组
从参数的个数来讲,前面所列出的所有参数类型都是一个形参对应一个实参。而参数数组就是:零个或多个实参对应一个特殊的形参。参数数组的使用条件:
一个参数列表中只能有一个参数数组;如果有,它必须是参数列表中的最后一个参数。
1).参数数组的声明方式:第一,在参数的数据类型前面使用params修饰符。第二,参数声明为数组变量。下面是一个使用参数数组声明的方法示例:

1 class MyClass 2 { 3 // 使用参数数组声明一个方法 4 public void ListInts(params int[] intVals) 5 { 6 if (intVals != null && intVals.Length > 0) 7 { 8 foreach (var item in intVals) 9 { 10 int x = item * 10; 11 Console.WriteLine("X = {0} ", x); 12 } 13 } 14 } 15 }
2).带参数数组的方法的调用方式:
有两种方式调用带参数数组的方法:第一种,使用一个用逗号表达式分隔的与参数类型相匹配的元素列表。第二种,使用一个与参数类型相匹配的元素的一维数组。下面是一个使用两种方式调用上面上面的带参数数组的方法的示例:

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var a1 = new MyClass(); 6 int x = 1, y = 2, z = 3; 7 // 调用方式一:使用一个用逗号表达式分隔的int型元素的列表。 8 a1.ListInts(x, y, z); 9 Console.ReadKey(); 10 11 // 调用方式二:使用一个int类型元素的一维数组。 12 int[] arr = { 1, 2, 3 }; 13 a1.ListInts(arr); 14 Console.ReadKey(); 15 } 16 }
二、方法重载
1.什么是方法重载?
方法重载就是在一个类中声明一个以上具有不同签名的同名方法。方法重载的目的:解决方法的重名问题,提高方法的可用性。
方法签名的组成:方法名、参数类型、参数个数、参数顺序和参数的修饰符。注意:方法的返回值类型和方法的形参名称并不是方法签名的一部分。
2.示例
下面的类中声明了三个具有不同签名的同名方法,构成了方法重载。

1 class MyClass 2 { 3 // 1.使用参数数组声明一个名为ListInts的方法 4 public void ListInts(params int[] intVals) 5 { 6 if (intVals != null && intVals.Length > 0) 7 { 8 foreach (var item in intVals) 9 { 10 int x = item * 10; 11 Console.WriteLine("X = {0} ", x); 12 } 13 } 14 } 15 16 // 2.重载1:声明了普通string数组类型的形参 17 public void ListInts(string[] vals) 18 { 19 string result = string.Empty; 20 foreach (var item in vals) 21 { 22 result += item; 23 } 24 Console.WriteLine(result); 25 } 26 27 // 重载2:声明了string类型形参 28 public string ListInts(string s) 29 { 30 return s + "......"; 31 } 32 33 }
三、递归
方法递归就是方法自己调用自己。方法递归在为树绑定数据和无限级分类的设计当中用得比较多,是一种很优雅 解决方案。在现实生活中,有老和尚给小和尚讲故事的例子(从前有座山......),它就是一个典型的递归。
下面是一个采用了递归的方式计算整数i的阶乘的方法:

1 // 计算i的阶乘 2 public int Factorial(int i) 3 { 4 if (i<=1) 5 { 6 return i; 7 } 8 // 采用递归 9 return i * Factorial(i - 1); 10 }