第18节 传值/输出/引用/数组/具名/可选参数,扩展方法
传值参数
声明时不带修饰符的形参是值参数。一个值形参对应于一个局部变量,只是它的初始值来自该方法调用所提供的相应实参。
允许方法将新值赋给值参数。这样的赋值只影响由该值形参表示的局部存储位置,而不会影响在方法调用时由调用方给出的实参。
1)值类型
注 :值参数创建变量副本;对值参数的操作永远不影响变量的值
2)引用类型,并且新创建对象
注 :值参数创建变量副本;对值参数的操作永远不影响变量的值
class Program
{
static void Main(string[] args)
{
Student oldStu = new Student() { Name = "Tom" }; //Ctrl + . :将作用域中的 stu 全部转换成 oldStu
SomeMethod(oldStu);
Console.WriteLine("{0},{1}", oldStu.GetHashCode(), oldStu.Name); //GetHashCode()是object类的一个方法,得到代表对象的唯一值
}
static void SomeMethod(Student stu)
{
stu = new Student() { Name = "Tom" };
Console.WriteLine("{0},{1}", stu.GetHashCode(), stu.Name);
}
}
class Student
{
public string Name { get; set; } //设置 Name 属性
}
![](https://img-blog.csdnimg.cn/8e635e88940840d8ad41da382b630ea6.png#pic_center)
3)引用类型,只操作对象,不创建新对象
注 :对象还是那个对象,但对象里的值(字段/属性)已经改变
class Program
{
static void Main(string[] args)
{
Student stu = new Student() { Name = "Tom" };
UpdateObject(stu);
Console.WriteLine("HashCode = {0},Name = {1}", stu.GetHashCode(), stu.Name);
}
static void UpdateObject(Student stu)
{
stu.Name = "Tim"; //副作用(side-effect),在编程时要尽量避免
Console.WriteLine("HashCode = {0},Name = {1}", stu.GetHashCode(), stu.Name);
}
}
class Student
{
public string Name { get; set; } //设置 Name 属性
}
![](https://img-blog.csdnimg.cn/89753eb35bee48dd8ab8e6ef7cec0eca.png#pic_center)
输出参数
用 out 修饰符声明的形参,输出参数不创建新的存储位置。变量在可以作为输出形参传递之前不一定需要明确赋值。在方法返回之前,该方法的每一个输出形参都必须明确赋值。
1)值类型
注:
输出参数并不创建变量的副本
方法体内必须要有对输出变量的赋值的操作
使用out修饰符显式指出——此方法的副作用是通过参数向外输出值
从语义上来讲——ref 是为了“改变”,out 是为了“输出”
调用带有输出参数的方法(TryParse())
static void Main(string[] args)
{
Console.WriteLine("Please input first number:");
string arg1 = Console.ReadLine();
double x = 0;
bool b1 = double.TryParse(arg1, out x); //TryParse只能得到是否正确解析,但是不能抛出异常
if(b1 == false)
{
Console.WriteLine("Input error!");
return;
}
Console.WriteLine("Please input second number:");
string arg2 = Console.ReadLine();
double y = 0;
bool b2 = double.TryParse(arg2, out y);
if(b2 == false)
{
Console.WriteLine("Input error!");
return;
}
double z = x + y;
Console.WriteLine("{0}+{1}={2}", x, y, z);
}
声明带有输出参数的方法
class Program
{
static void Main(string[] args)
{
double x = 100;
bool b1 = DoubleParser.TryParse("200", out x);
if(b1 == true)
{
Console.WriteLine(x + 1);
}
}
}
class DoubleParser
{
public static bool TryParse(string input,out double result)
{
try
{
result = double.Parse(input);
return true;
}
catch
{
result = 0;
return false;
}
}
}
2)引用类型
class Program
{
static void Main(string[] args)
{
Student stu = null; //错误地写成了Student stu = new Student()
bool b = StudentFactory.Create("Tim", 34, out stu);
if(b == true)
{
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}; //错误地写成 result = new Student(){stuName, stuAge};
return true;
}
}
引用参数
引用形参是用 ref 修饰符声明的形参。与值参数不同,引用形参并不创建新的存储位置。相反,引用形参表示的存储位置恰是在方法调用中作为实参给出的那个变量所表示的存储位置。
变量在可以作为引用形参传递之前,必须先明确赋值。
在方法的声明和调用上都要写 ref
![](https://img-blog.csdnimg.cn/88ffbb283a64477d8cc5c9bd0b6cdf2c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5bCP5bCP5Li75Lq65YWs,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
1)值类型
注:引用参数并不创建变量的副本;使用 ref 修饰符显式指出——此方法的副作用是改变实际参数的值,有意地利用这个作用。
class Program
{
static void Main(string[] args)
{
int y = 100;
IWantSideEffect(ref y); //方法调用,在方法的声明和调用上都要写 ref
Console.WriteLine(y);
}
static void IWantSideEffect(ref int x) //方法声明,在方法的声明和调用上都要写 ref
{
x = x + 1;
}
}
2)引用类型,创建新对象
注:引用参数并不创建变量的副本;使用 ref 修饰符显式指出——此方法的副作用是改变实际参数的值,有意地利用这个作用。
class Program
{
static void Main(string[] args)
{
Student 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; }
}
![](https://img-blog.csdnimg.cn/099453083e0e4e7b8b2bc07990070c78.png#pic_center)
3)引用类型,不创建新对象只改变对象值
注:此时与传值参数在效果上并无不同,但机理不一样,面试题中可能会出现。
–> 传值参数:在内存中创建了实际参数的副本,stu参数 和 outterStu变量 所指向的内存地址是不一样的,但是不一样的内存地址里面存储着相同的地址,即实例在堆内存中的地址。
–> 引用参数(引用类型):stu参数 和 outterStu变量 所指向的内存地址是相同的,而该内存地址里面存储的是对象在堆内存中的地址。
class Program
{
static void Main(string[] args)
{
Student 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; }
}
![](https://img-blog.csdnimg.cn/bc1576743b124de5b490f9860d2f078b.png#pic_center)
数组参数
1)必须是形参列表中最后一个,由params修饰
2)举例:String.Format 方法和 String.Split 方法
class Program
{
static void Main(string[] args)
{
//形参由params修饰,不需要提前声明数组,只需要将数组元素一一列出即可
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;
}
}
String.Split() 方法
string str = "Tim;Tom,Amy.Lisa";
string[] result = str.Split(';', ',', '.');
foreach (var name in result)
{
Console.WriteLine(name);
}
具名参数(参数的使用方法)
参数的位置不再受约束
优点:1)提高代码的可读性;2)参数的位置不再受参数列表的约束
举例:
static void Main(string[] args)
{
//不具名调用,在调用方法时,参数的位置要一一对应
PrintInfo("Tim", 34);
//具名调用,参数的位置不再受参数列表的约束,
//并提高了代码的可读性
PrintInfo(age:34,name:"Tim");
}
static void PrintInfo(string name, int age)
{
Console.WriteLine("Hello {0}, you are {1}", name, age);
}
可选参数
参数因为具有默认值而变得“可选”
不推荐使用可选参数
static void Main(string[] args)
{
//在方法的声明时,给定了初始值,
//所以在调用时参数可以选择默认值
PrintInfo();
}
static void PrintInfo(string name = "Tim", int age = 34)
{
Console.WriteLine("Hello {0}, you are {1}", name, age);
}
扩展方法(this参数)
当无法对一个类型的源码进行修改时,可以使用扩展方法为目标数据类型追加方法。
1)方法必须是公有、静态的,即被public static 所修饰
2)必须是形参列表中的第一个,由 this 修饰
3)必须有一个静态类(一般类名为 SomeTypeExtension)来统一收纳对 SomeType 类型的扩展方法
4)举例:LINQ (语言集成查询)方法
class Program
{
static void Main(string[] args)
{
double x = 3.14159;
//x 是 Round方法的第一个参数
double y = x.Round(4);
Console.WriteLine(y);
}
}
static class DoubleExtension
{
public static double Round(this double input, int digits) // this
{
double result = Math.Round(input, digits);
return result;
}
}
class Program
{
static void Main(string[] args)
{
List<int> myList = new List<int>() { 11, 12, 13 };
//判断 myList 里面是否全大于10
//All是扩展方法,隶属于Enumerable静态类
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;
}
}
各种参数的使用场景总结
传值参数:参数的默认传递方式
输出参数:用于除返回值外还需要输出的场景
引用参数:用于需要修改实际参数值的场景
数组参数:用于简化方法的调用
具名参数:提高可读性
可选参数:参数拥有默认值
扩展方法(this 参数):为目标数据类型“追加”方法