using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Linq; namespace ParameterType { class Program { /* 各种参数主要使用场景总结: 》传值参数:是参数的默认传递方式,参数是实参值得一个副本,对传值参数得操作不会影响实参的值(引用类型变量做形参的副作用是可能会修改其所指向的对象数据),一般用于读取实参的值的场景 》输出参数:用于除返回值外还需要传出信息的场景 》引用参数:用于修改实际参数值的场景 》数组参数:用于简化以数组作为参数的方法的调用 》具名参数:用于提高可读性,且传递参数时可以不受顺序(即默认的位置参数)的约束 》可选参数:即带有默认值的参数(注意可选参数的前提是值传递即值参数,默认值在编译期可以确定) 》this参数:即扩展方法,可以在不修改目标类型源码的情况下,给目标类型追加一个我们想要它提供的方法 */ static void Main(string[] args) { Console.WriteLine("------------------------------值参数测试----------------------------------"); int x = 88, y = 99; Console.WriteLine("Before Swap In Main Method:x = {0}, y = {1}", x, y); CValueParameter.SwapIntValue(x, y); Console.WriteLine("After Swap In Main Method:x = {0}, y = {1}", x, y); Console.WriteLine(); Stu stu = new Stu() { Name = "Lisa", Age = 18 }; Console.WriteLine("Before Change In Main Method: stu's hashcode:{0}, std's Name:{1}", stu.GetHashCode(), stu.Name); CValueParameter.ChangeStu(stu); Console.WriteLine("After Change In Main Method: stu's hashcode:{0}, std's Name:{1}", stu.GetHashCode(), stu.Name); Console.WriteLine(); Console.WriteLine("------------------------------引用参数测试-----------------------------------"); Console.WriteLine("Before Swap In Main Method:x = {0}, y = {1}", x, y); CRefParameter.SwapIntValue(ref x, ref y); Console.WriteLine("After Swap In Main Method:x = {0}, y = {1}", x, y); Console.WriteLine(); Stu rstu = new Stu() { Name = "Mily", Age = 16 }; Console.WriteLine("Before Change In Main Method: rstu's hashcode:{0}, std's Name:{1}", rstu.GetHashCode(), rstu.Name); CRefParameter.ChangeNewStu(ref rstu); Console.WriteLine("After Change In Main Method: rstu's hashcode:{0}, std's Name:{1}", rstu.GetHashCode(), rstu.Name); Console.WriteLine(); Console.WriteLine("Before Change In Main Method: rstu's hashcode:{0}, std's Name:{1}", rstu.GetHashCode(), rstu.Name); CRefParameter.ChangeOldStu(ref rstu); Console.WriteLine("After Change In Main Method: rstu's hashcode:{0}, std's Name:{1}", rstu.GetHashCode(), rstu.Name); Console.WriteLine(); Console.WriteLine("------------------------------输出参数-----------------------------------"); string sd1 = "3.14159", sd2 = "werw"; double ret; double.TryParse(sd1, out ret); Console.WriteLine("double.TryParse sd1 ret = {0}", ret); double.TryParse(sd2, out ret); Console.WriteLine("double.TryParse sd2 ret = {0}", ret); COutParameter.MyDoubleTryParse(sd1, out ret); Console.WriteLine("MyDoubleTryParse sd1 ret = {0}", ret); COutParameter.MyDoubleTryParse(sd2, out ret); Console.WriteLine("MyDoubleTryParse sd2 ret = {0}", ret); Stu sstu; if (COutParameter.CreateStu("Jack", 20, out sstu)) { Console.WriteLine($"sstu name:{sstu.Name}, age:{sstu.Age}"); } Console.WriteLine("------------------------------数组参数-----------------------------------"); int sum = CArrayParam.GetSum(1, 2, 3, 4, 5, 6); // 如果GetSum的那那个数组参数,没有param修饰,则需要先定义一个数组传递给它 Console.WriteLine($"sum of "1, 2, 3, 4, 5, 6" is {sum}"); string strNames = "Mike,Jack,Lisa,Peter"; string[] names = strNames.Split(','); // Console.WriteLine("names:{0}", names); // 这样默认只获取首元素 foreach(var name in names) { Console.Write(name); Console.Write(" "); } Console.WriteLine(); Console.WriteLine(); Console.WriteLine("------------------------------具名调用-----------------------------------"); CNamedParam.ShowMessage(name: "Mike", id: 9527, address: "Beijing"); Console.WriteLine(); Console.WriteLine("------------------------------扩展方法(this参数)-----------------------------------"); double pi = 3.1415926; double rdpi = pi.Round(4); // 调用扩展方法,就仿佛double类型本来就存在那个方法一样 Console.WriteLine($"rdpi = {rdpi}"); // 使用LINQ List<int> nList = new List<int>() { 11, 12, 1, 14, 15 }; bool bRet = nList.All(i => i > 10); // 这里的这个All就是一个扩展方法, List类型本身并没有All这个方法,它是定义在LINQ命名空间下的public static class Enumerable中 Console.WriteLine("Is all list number is greater than 10? {0}", (bRet ? "Yes" : "No")); } } class Stu { public int Age { get; set; } public string Name { get; set; } } // 值参数(即参数是实参值的副本,类比C++中的值传递),其参数类型又可以分为值类型和引用类型两种情况讨论 // 使用值参数的主要作用(目的)是为了使用它的值,并不是为了修改它的值(如果使用引用类型形参修改了实参,这就属于值参数带来的副作用) class CValueParameter { // 当参数是值类型时(值类型变量,变量的值就是变量的数据,存储在栈内存) static public void SwapIntValue(int x, int y) { int temp = x; x = y; y = temp; Console.WriteLine("In SwapIntValue Method:x = {0}, y = {1}", x, y); } // 当参数类型时引用类型时(引用类型的变量,变量的值(存储在栈内存上)是一个内存地址,该地址指向存储对象实体数据的一片堆内存) // 当以传值方式传递引用类型变量时形参复制的是内存地址(变量的值),并不是对象的实体数据。但是最终,形参和实参,都指向了同一片地址(即存储对象实体数据的堆内存) // 这种情况就类似C++的指针传递,指针变量本身是值传递,形参复制指针变量的值(即内存地址),最终形参和实参指向同一片内存 public static void ChangeStu(Stu stu) { // 重新创建一个新的对象,会把原来复制的实参地址丢弃 // stu = new Stu() { Name = "Peter", Age = 19 };// 此时的stu与实参,完全没有关系了 // 不创建对象,则stu和实参所指向的对象相同 stu.Name = "Mike"; // 操作对象的数据(注意正常情况下不会通过值参数这种方式来修改引用类型实参对象的数据,这种操作叫做值参数的副作用,应该尽量避免,如果需要这种方式应该明确使用引用参数/输出参数这种) Console.WriteLine("In ChangeStu Method: stu's hashcode:{0}, std's Name:{1}", stu.GetHashCode(), stu.Name); } public static void ChangeString(string str) { str = "world"; // 不要使用string来做测试,string有点特殊,它每次字符串内容更改时都会重新new新的存储空间。即等价于 str = new string("world"); Console.WriteLine("In ChangeString str's hashcode:{0}, str's value:{1}", str.GetHashCode(), str); } } // 引用参数(即形参和实参引用相同的对象,类比C++的引用传递),使用ref修饰符修饰(在声明形参和实参传递时都需要ref修饰) // 引用参数相当于是实参的一个别名,对形参的任何修改等价于直接修改实参 // 使用引用参数的主要作用(目的)是就是为了修改它的值,即使用之前它已经有一个初值在方法内部做一些处理后会修改它的值 class CRefParameter { // 不创建新的对象 static public void SwapIntValue(ref int x, ref int y) { int temp = x; // 这里的temp是复制x的值 x = y; y = temp; Console.WriteLine("In SwapIntValue Method:x = {0}, y = {1}", x, y); } public static void ChangeOldStu(ref Stu stu) { stu.Name = "Jhon"; // 此时的效果和值参数是一样的,但是本质不一样,值参数是创建了实参的值副本,引用参数只是实参的别名没有创建副本 Console.WriteLine("In ChangeOldStu Method: stu's hashcode:{0}, std's Name:{1}", stu.GetHashCode(), stu.Name); } // 创建新的对象 public static void ChangeNewStu(ref Stu stu) { // 此时的stu相当于实参的一个别名,操作stu就相当于操作实参 stu = new Stu() { Name = "Peter", Age = 19 };// 此时实参的指向也被修改 Console.WriteLine("In ChangeNewStu Method: stu's hashcode:{0}, std's Name:{1}", stu.GetHashCode(), stu.Name); } } // 输出参数,和引用参数类似,但是其主要目的是向调用者传出信息,所以传递给它的实参并不要求有初始值,但是在方法返回前必须对输出参数赋值。 // 使用out修饰符修饰(在声明形参和实参传递时都需要out修饰) // 使用输出参数的主要作用(目的)是通过该参数传出方法处理后的结果 class COutParameter { public static bool MyDoubleTryParse(string input, out double ret) { try { ret = double.Parse(input); return true; } catch { ret = 0.0; // 系统自带的TryParse在解析失败时也是会覆盖原来的值 return false; } } public static bool CreateStu(string name, int age, out Stu ret) { ret = null; if (string.IsNullOrEmpty(name)) { return false; } if (age < 3 || age > 30) { return false; } ret = new Stu() { Name = name, Age = age }; return true; } } // 数组参数,就是可以接受一组同类型的参数,比如Console.WriteLine方法就有一个重载形式接受params object[]参数 // 注意:数组参数必须是方法参数列表中的最后一个 class CArrayParam { public static int GetSum(params int[] numbers) { int sum = 0; foreach(var n in numbers) { sum += n; } return sum; } } // 具名参数(说白了就是调用的时候,指定参数名,这样就可以不按顺序传递参数了,默认都是位置参数) class CNamedParam { public static void ShowMessage(int id, string name, string address) { Console.WriteLine($"id:{id} name:{name} address:{address}"); } } // 可选参数(就是C++中带默认值的缺省参数,参数因为有默认值而变得"可选", C#中不推荐使用可选参数) // 前提:参数是值传递即值参数,不允许是引用、输出、数组参数,因为参数的默认值必须在编译时确定 // 所有必选参数必须在可选参数之前,如果有params参数,则数组参数在最后。 // 参数传递时省略必须从最后一个可选参数开始 class COptionalParam { public static void ShowMessage(int id, string name, string address, string sex = "man") { Console.WriteLine($"id:{id} name:{name} address:{address} sex:{sex}"); } } // 扩展方法,也被称为this参数。目的就是给一个已存在的类型尤其是那种别人写好的类型,你无法修改其源码,但是又想给它添加一个方法。 // 扩展方法必须是publi static的,this参数必须是参数列表的第一个参数,用this修饰需要扩展的类型 // 必须用一个静态类,一般取名为 "需要扩展的类型+Extension" 如下DoubleExtension。通过这个静态类来统一管理对目标类型的扩展方法 // C#语言中的LINQ就是大量使用了扩展方法。 // 如下:标识给double类型添加一个四舍五入的方法,默认double类型里面是没有这个方法 static class DoubleExtension { // this修饰你需要扩展的那个类型 public static double Round(this double input, int digits) { double result = Math.Round(input, digits); return result; } } }