这一节主要讲参数,参数是方法的一部分,所以这节课也可以看作是对方法的进一步学习。
本节内容:
- 传值参数
- 输出参数
- 引用参数
- 数组参数
- 具名参数
- 可选参数
- 扩展方法(this 参数)
值参数

参见 C# 图解教程 第五章 方法。
传值参数 -> 值类型

传值参数 -> 引用类型,并且新创建对象

这种状况很少见,一般情况都是传进来引用它的值,而不是连接到新对象去(基本只有面试题会考这个)。
注:当参数类型为 string 时,在方法内部修改参数的值,对应的是此处创建对象这种情况。 因为 string 是 immutable 的,所以在方法内部对 string 赋值实际是“创建新的 string 实例再赋值”,最终方法外部的 string 并不会改变。
GetHashCode()
Object.GetHashCode() 方法,用于获取当前对象的哈希代码,每个对象的 Hash Code 都不一样。
通过 Hash Code 来区分两个 Name 相同的 stu 对象。
class Program{static void Main(string[] args){var stu = new Student() { Name="Tim"};SomeMethod(stu);Console.WriteLine(stu.Name);Console.WriteLine(stu.GetHashCode());}static void SomeMethod(Student stu){stu = new Student { Name = "Tim" };Console.WriteLine(stu.Name);Console.WriteLine(stu.GetHashCode());}}class Student{public string Name { get; set; }}
传值参数 -> 引用类型,只操作对象,不创建新对象

这种通过传递进来的参数修改其引用对象的值的情况,在工作中也比较少见。
因为作为方法,其主要输出还是靠返回值。我们把这种修改参数所引用对象的值的操作叫做方法的副作用(side-effect),这种副作用平时编程时要尽量避免。
引用参数 ref

引用参数 -> 值类型

static void Main(string[] args){int y = 1;IWantSideEffect(ref y);Console.WriteLine(y);}static void IWantSideEffect(ref int x){x += 100;}
引用参数 -> 引用类型,创建新对象

class Program{static void Main(string[] args){var outterStu = new Student() { Name = "Tim" };Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);Console.WriteLine("-----------------");IWantSideEffect(ref outterStu);Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);}static void IWantSideEffect(ref Student stu){stu = new Student() { Name = "Tom" };Console.WriteLine("HashCode={0}, Name={1}",stu.GetHashCode(),stu.Name);}}class Student{public string Name { get; set; }}

引用参数 -> 引用类型,不创建新对象只改变对象值

对象的 HashCode 没有改变过。
class Program{static void Main(string[] args){var outterStu = new Student() { Name = "Tim" };Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);Console.WriteLine("-----------------");SomeSideEffect(ref outterStu);Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);}static void SomeSideEffect(ref Student stu){stu.Name = "Tom";Console.WriteLine("HashCode={0}, Name={1}", stu.GetHashCode(), stu.Name);}}class Student{public string Name { get; set; }}

注:上面示例中使用传值参数(不用 ref)结果也将一样,但内部机理不同。
传值参数创建了副本,方法里面的 stu 和 outterStu 不是一个对象,所指向的内存地址不一样,但是存储的地址是相同的,都存储的是 Student 实例在堆内存中的地址。
引用参数 stu 和 outterStu 指向的是同一个内存地址,这个内存地址里面存储的就是 Student 实例在堆内存中的地址。
输出形参 out

输出参数 -> 值类型

static void Main(string[] args){Console.WriteLine("Please input first number:");var arg1 = Console.ReadLine();double x = 0;if (double.TryParse(arg1, out x) == false){Console.WriteLine("Input error!");return;}Console.WriteLine("Please input second number:");var arg2 = Console.ReadLine();double y = 0;if (double.TryParse(arg2, out y) == false){Console.WriteLine("Input error!");return;}double z = x + y;Console.WriteLine(z);}
自己实现了带有输出参数的 TryParse:
class Program{static void Main(string[] args){double x = 0;if(DoubleParser.TryParse("aa",out x)){Console.WriteLine(x);}}}class DoubleParser{public static bool TryParse(string input,out double result){try{result = double.Parse(input);return true;}catch{result = 0;return false;}}}
输出参数 -> 引用类型

引用类型的输出参数实例
class Program{static void Main(string[] args){Student stu = null;if(StudentFactory.Create("Tim", 34, out stu)){Console.WriteLine("Student {0}, age is {1}",stu.Name,stu.Age);}}}class Student{public int Age { get; set; }public string Name { get; set; }}class StudentFactory{public static bool Create(string stuName,int stuAge,out Student result){result = null;if (string.IsNullOrEmpty(stuName)){return false;}if (stuAge < 20 || stuAge > 80){return false;}result = new Student() { Name = stuName, Age = stuAge };return true;}}
数组参数 params
- 必需是形参列表中的最后一个,由 params 修饰
- 举列:String.Format 方法和 String.Split 方法
使用 params 关键字前:
class Program{static void Main(string[] args){var myIntArray = new int[] { 1, 2, 3 };int result = CalculateSum(myIntArray);Console.WriteLine(result);}static int CalculateSum(int[] intArray){int sum = 0;foreach (var item in intArray){sum += item;}return sum;}}
使用 params 后,不再需要单独声明数组:
class Program{static void Main(string[] args){int result = CalculateSum(1, 2, 3);Console.WriteLine(result);}static int CalculateSum(params int[] intArray){int sum = 0;foreach (var item in intArray){sum += item;}return sum;}}
我们早在 WriteLine 方法中就用到了 params。
又一个用到了数组参数(params)的例子。
具名参数
具名参数:参数的位置不再受约束。
具名参数的优点:
提高代码可读性
参数的位置不在受参数列表约束
具名参数实例:
class Program{static void Main(string[] args){PrintInfo("Tim", 34);PrintInfo(age: 24, name:"Wonder");}static void PrintInfo(string name, int age){Console.WriteLine("Helllo {0}, you are {1}.",name,age);}}
可选参数
参数因为具有默认值而变得“可选”
不推荐使用可选参数
扩展方法

无扩展方法:
class Program{static void Main(string[] args){double x = 3.14159;// double 类型本身没有 Round 方法,只能使用 Math.Round。double y = Math.Round(x, 4);Console.WriteLine(y);}}
有扩展方法后:
class Program{static void Main(string[] args){double x = 3.14159;// double 类型本身没有 Round 方法,只能使用 Math.Round。double y = x.Round(4);Console.WriteLine(y);}}static class DoubleExtension{public static double Round(this double input,int digits){return Math.Round(input, digits);}}

当我们无法修改类型源码时,可以通过扩展方法为目标数据类型追加方法。
LINQ 也是扩展方法的一大体现。
LINQ 实例
class Program{static void Main(string[] args){var myList = new List<int>(){ 11, 12, 9, 14, 15 };//bool result = AllGreaterThanTen(myList);// 这里的 All 就是一个扩展方法bool result = myList.All(i => i > 10);Console.WriteLine(result);}static bool AllGreaterThanTen(List<int> intList){foreach (var item in intList){if (item <= 10){return false;}}return true;}}
All 第一个参数带 this,确实是扩展方法。
总结
各种参数的使用场景总结:
- 传值参数:参数的默认传递方法
- 输出参数:用于除返回值外还需要输出的场景
- 引用参数:用于需要修改实际参数值的场景
- 数组参数:用于简化方法的调用
- 具名参数:提高可读性
- 可选参数:参数拥有默认值
- 扩展方法(this 参数):为目标数据类型“追加”方法
