九、各种参数和扩展方法
更详细、准确的描述可以看方法参数按值传递。 修饰符启用按引用传递语义,包括只读和
out
参数等区别。 了解不同的参数传递模式以及如何使用它们。 参数修饰符允许一系列可选参数。 - C# | Microsoft Learn
传值参数
会在内存上创建实际参数的副本。
值类型
引用类型
可以通过getHashCode
方法,来查看哈希值,因为哈希值具有唯一性,所以可以通过这个判断是否是同一对象实例。
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
Student stu = new Student() {Name = "Tim"};
SomeMethod(stu);
//打出来的是Tom
Console.WriteLine(stu.GetHashCode());
//此时打出来的是Tim
//通过哈希值可以看出,两个对象实例并不一样
}
//注意此时,两个stu在内存上面的地址是不一样的
//但是存储着同一样的堆上面同一个对象实例的地址
static void SomeMethod(Student stu) {
stu = new Student() { Name = "Tom"};
Console.WriteLine(stu.GetHashCode());
}
}
class Student {
public string Name { get; set; }
}
}
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
Student stu = new Student() {Name = "Tim"};
UpdateObject(stu);
Console.WriteLine(stu.GetHashCode() + " " + stu.Name);
/*
* 此时会发现,两者的哈希值是一样的
* 也就说明两个变量引用的是同一个对象实例
*/
}
static void UpdateObject(Student stu) {
stu.Name = "Tom";//这并不是方法的主要作用,而是副作用
//side-effect
Console.WriteLine(stu.GetHashCode() + " " + stu.Name);
}
}
class Student {
public string Name { get; set; }
}
}
这就是传引用类型,和传值类型的区别。
其根本都是因为,引用类型变量引用的堆上面的地址,而值类型的变量代表着的是栈上面数据的地址。
引用参数
不会在内存上创建实际参数的副本,甚至引用参数的地址直接就是实际参数在栈上面的地址。
引用参数,的主要作用是,打算修改一个变量的值。
值类型
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
int y = 1;
IWantSideEffect(ref y);
/*
* 调用的时候必须带上ref
* 也是为了提醒程序员,这里是引用调用的
* 这也是C#是强类型语言的表现
*/
Console.WriteLine(y);
}
static void IWantSideEffect(ref int x) {
x += 100;
}
}
}
引用类型
原本引用的对象实例被替换了
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
Student outterStu = new Student() { Name = "Tim" };
Console.WriteLine(outterStu.GetHashCode() + " " + outterStu.Name);
Console.WriteLine("----------------------");
IWantSideEffect(ref outterStu);
Console.WriteLine(outterStu.GetHashCode() + " " + outterStu.Name);
}
static void IWantSideEffect(ref Student stu) {
stu = new Student() {Name = "Tom"};
Console.WriteLine(stu.GetHashCode() + " " + stu.Name);
}
}
class Student {
public string Name { get; set; }
}
}
两个变量在栈上面的内存都是一样的。
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
Student outterStu = new Student() { Name = "Tim" };
Console.WriteLine(outterStu.GetHashCode() + " " + outterStu.Name);
Console.WriteLine("----------------------");
SomeSideEffect(ref outterStu);
Console.WriteLine(outterStu.GetHashCode() + " " + outterStu.Name);
}
/* outterStu和方法中的stu
* 甚至在栈上面都是同一个地址,并且存储了堆上面一个对象实例的地址
*/
static void SomeSideEffect(ref Student stu) {
stu.Name = "Tom";
Console.WriteLine(stu.GetHashCode() + " " + stu.Name);
}
}
class Student {
public string Name { get; set; }
}
}
输出参数
输出参数的作用是,为了向外输出。
所以可以先不赋值
值类型
使用输出参数的实例:
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
Console.WriteLine("Please input first number:");
string str1 = Console.ReadLine();
double x = 0;
bool b1 = double.TryParse(str1, out x);
if(b1 == false ) {
Console.WriteLine("Input error!");
return;
}
Console.WriteLine("Please input second number:");
string str2 = Console.ReadLine();
double y = 0;
bool b2 = double.TryParse(str2, out y);
if (b2 == false) {
Console.WriteLine("Input error!");
return;
}
double z = x + y;
Console.WriteLine($"{x} + {y} = {z}");
}
}
}
声明输出参数:
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
Console.WriteLine("Please input first number:");
string str1 = Console.ReadLine();
double x = 100;
bool b1 = double.TryParse(str1, out x);
if(b1 == true ) {
Console.WriteLine(x + 1);
}
}
}
class DoubleParser {//自己实现的带有输出参数的方法
public static bool TryParse(string str, out double result) {
try {
result = double.Parse(str);
return true;
} catch {
result = 0;//实际的double.TryParse()方法,也是这样的
return false;
}
}
}
}
引用类型
当输出参数是引用类型和是值类型,实际上是差不多的
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
Student stu = null;
bool b = StudentFactory.Create("Tom", 20, out stu);
if(b == true) {
Console.WriteLine($"Student {stu.Name}, Age {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
修饰。
示例:
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
int result = CalculateSum(1, 2, 3);
Console.WriteLine(result);
string str = "Tim;Tom,Amy.Lisa";
string[] res = str.Split('.', ';', ',');
foreach (var name in res)
{
Console.WriteLine(name);
}
}
/* params修饰符,
* 即可以传入多个参数,组成的数组
*/
static int CalculateSum(params int[] intArray) {
int sum = 0;
foreach (var item in intArray)
{
sum += item;
}
return sum;
}
}
}
具名参数
调用一个方法时,传进去的参数是带有名字的。
参数的位置,不受约束。
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
PrintInfo("Tom", 12);//这是不具名调用
PrintInfo(age : 12, name: "Tom");
/* 具名调用的优点:
* 1. 提高代码的可读性
* 2. 使用具名调用时,参数的位置就不受参数列表的顺序的约束了
* 3. 具名参数并不是参数的种类,而是参数的使用方法
*/
}
static void PrintInfo(string name, int age) {
Console.WriteLine($"Hello {name}, your are {age}");
}
}
}
可选参数
参数因为声明时具有默认值,而变得可选
不推荐使用可选参数
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
PrintInfo();
}
static void PrintInfo(string name = "Tom", int age = 12) {
Console.WriteLine($"Hello {name}, your are {age}");
}
}
}
扩展方法(this参数)
namespace ParametersExample {
internal class Program {
static void Main(string[] args) {
double x = 3.14159;
double y = x.Round(4);
/* 括号里面只写一个参数的原因
* x就是第一个参数,所以只需要写第二个参数
* 当无法对一个类型的源码进行修改的时候,
* 我们可以使用扩展方法,来为这种目标数据类型追加方法
*/
Console.WriteLine(y);
}
}
/* 创建扩展方法,先创建静态类
*/
static class DoubleExtension {
public static double Round(this double input, int digits) {
double result = Math.Round(input, digits);
return result;
}
}
}
使用示例:
using System.Linq;
namespace ParametersExample {
internal class Program {
/* 当我们想要判断一个数组
* 是否大于10
*/
static void Main(string[] args) {
List<int> myList = new List<int>() {11, 12, 13, 14, 15};
//bool result = AllGreaterThanTen(myList);
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参数):为目标数据类型“追加”方法
*/
Console.WriteLine(result);
}
static bool AllGreaterThanTen(List<int> intList) {
foreach (var item in intList)
{
if(item <= 10) {
return false;
}
}
return true;
}
}
}
## 总结
> * 传值参数:参数的默认传递方式
> * 输出参数:用于除返回值外还需要输出的场景
> * 引用参数:用于需要修改实际参数值的场景
> * 数组参数:用于简化方法的调用
> * 具名参数:提高可读性
> * 可选参数:让参数拥有默认值
> * 扩展方法(this参数):为目标数据类型“追加”方法