c#-方法参数(值参数、引用参数、输出参数、数组参数、可选参数、具名参数调用、this参数【扩展方法】)

值参数

值参数是使用最多的参数。
声明时不带任何修饰符的形参就是值参数(形式参数)。值形参对应一个局部变量,它的初始化值来自调用方法时提供的实参(实际参数)。方法内部对形参的改变不会影响方法外部的实参,言外之意就是形参是方法内的一个局部变量,方法调用时形参拷贝了传入的实参的值,此后,形参和实参再无半分瓜葛。

class Program
{
    
    
	static void Main(string[] args)
	{
    
    
		int y = 100;

		Program.PrintAdd(y); // 输出 101
		Console.WriteLine(y); // 输出100
	}

	static void PrintAdd(int x)
	{
    
    
		x = x + 1;
		Console.WriteLine(x);
	}
}

值参数是引用数据类型

当值参数是引用数据类型时,形参拷贝的是实参的引用,但本质上实参和形参还是两个不同的变量。不同的点在于:

  1. 基本数据类型存的直接是基本数据的值,如int类型存的就是整数。
  2. 引用数据类型存的是被引用对象的地址,如类的对象的变量存的就是对象的地址。

容易搞混的一点是:
当传入一个对象给一个方法时,方法内通过该对象对其字段进行修改后,方法外部的对象的对应字段会被修改,这可能会被一些人认为此时不是值参数。其实还是值参数,形参拷贝实参的值,只是这个值在引用数据类型中它是地址。拷贝之后,形参和实参各自保存的值是同一个地址,此时在方法内部可以通过该形参对该地址进行访问。如果这时使用形参.字段(字段类型可以是基本数据类型和引用数据类型)的方式修改某一字段的值,修改的是形参指向的地址存的对象的字段,并没有修改形参本身,因为形参保存的只是被引用对象的地址。只有形参 = 新对象的方式才是修改形参本身。形参 = 新对象并不会导致实参 == 新对象,以此,还是符合值参数的定义。

class Student
{
    
    
	public string name;
	public int age;
}
class Program
{
    
    
	static void Main(string[] args)
	{
    
    
		Student stu = new Student();
		stu.name = "yy";
		stu.age = 100;

		Program.Test(stu);

		// 到这里stu的字段的值分别是:
		name = "xx"; 
		age = 100;
		// 新旧hash code 是不一样的
		Console.WriteLine("hash code: {0}", stu.GetHashCode());
	}

	static void Test(Student stu)
	{
    
    
		// 通过形参stu访问了一个Student对象的字段name,并修改之,此修改外部能感知
		stu.name = "xx"; 
		stu = new Student(); // 形参新赋值了一个新对象,外部实参依旧是旧的对象
		// 这两次赋值是对新对象的字段的赋值,并不会对
		// 旧对象的字段造成影响
		stu.name = "bb";
		stu.age = 20;
		Console.WriteLine("hash code: {0}", stu.GetHashCode());
	}
}

引用参数

ref修饰符声明的形参就是引用参数。与值参数不同的是,引用参数并不创建新的存储位置。也就是说,引用参数的实参和形参引用的就是同一个存储位置。

class Program
{
    
    
	static void Main(string[] args)
	{
    
    
		double a = 20;
		Add(ref a); // a == 21
		Console.WriteLine(a); // a == 21
	}

	static void Add(ref int a)
	{
    
    
		a = a + 1;
		Console.WriteLine(a);
	}
}

输出参数

out修饰符声明的形参就是输出参数。与引用参数类似,输出参数不创建新的存储位置,输出参数的形参的存储位置恰恰就是实参的存储位置,即输出参数的形参实参完全就是同一个变量。

输出参数的设计就是希望从方法的内部得到值。输出参数的初值会在方法内部被覆盖,因此初值没有意义,无需赋初值。可以把输出参数理解成额外的函数返回值。

class Program
{
    
    
	static void Main(string[] args)
	{
    
    
		double result;
		// 调用时也显式地写上out,让人一看就知道是输出参数
		bool b = TryAdd("1", "100", out result);
		if (b)
			Console.WriteLine("{0} + {1} = {2}", "1", "100", result);
	}

	// a和b能被解析成double并相加成功的话才返回true
	static bool TryAdd(string a, string b, out double res)
	{
    
    
		try
		{
    
    
			res = double.Parse(a) + double.Parse(b);
			return true;
		} catch {
    
    
			res = 0;
			return false;
		}
	}
}

引用参数 VS 输出参数

关键字out和关键字ref等效的,就是说,无论使用哪个关键字,都会生成相同的元数据和IL代码。
但是,C#编译器将两者区别对待。这两个关键字的区别在于谁负责初始化

  1. out: 调用者不希望在调用方法之前初始化对象,被调用的方法不能读取对象的值,而且被调用的方法必须在返回之前对其赋值。
  2. ref: 调用者必须在调用方法之前首先初始化参数的值,被调用的方法可以读取参数或为参数赋值。

数组参数

params修饰符声明的形参就是数组参数

// 普通的数组参数声明
void Method(int[] arr){
    
    ...}
// 调用时需要先创建一个数组,然后再传入
int[] array = new int[]{
    
    1, 2, 3};
XXX.Method(array);

// 如果加上修饰符params就可以直接在调用方法时顺序输入值

// 声明方法
void Method(params int[] arr){
    
    ...}
// 调用
XXX.Method(1, 2, 3);
// 可以直接输入而无需创建数组

注意:一个方法的参数列表只能有0个或1个params参数,并且params参数只能在参数列表的最后。

可选参数

声明方法时给参数提供默认值的就是可选参数。参数因为具有默认值而变得可选

...
// 声明一个参数有默认值的方法
public void Method(string name = "李四", int age = 20){
    
    ...}
...

// 因为Method方法有默认值,所以参数是可选的
// 不传入实参,形参默认使用的是默认值
XXX.Method(); // name = "李四", age = 20

具名参数调用

具名参数调用可以算是一种编写代码的习惯,一般来说,我们调用函数时只填写参数的值(或者变量的名字),如XXX.Method(100, "你好");,这算是匿名调用。而具名调用要求把参数的名称也显式的写出来。例如:

...
// 方法
public void Method(int id, string name){
    
    ...}
...

// 匿名调用
XXX.Method(1, "张三");

// 具名调用
XXX.Method(id:1, name:"张三");
// 具名调用可以忽略参数列表的顺序
XXX.Method(name:"张三", id:1); // 这样写等同于上面

this参数(扩展方法)

扩展方法又称this参数,可以对一个不能修改源码,但又需要增加方法的类进行增加方法。
方法必须是public staic修饰的。
下面的例子是对string类增加了ToInt方法:

class Pragram
{
    
    
    static void Main(string[] args)
    {
    
    
        string a = "100";
        // 因为input参数就是this,会
        // 自动传入a,所以不用显式传入
        // 参数
        int b = a.ToInt();
    }
}

// 声明一个静态类,这是string的扩展类
static class StringExtension
{
    
    
	// 对string扩展了ToInt方法
	// 该方法可以将字符串转成int
	// this修饰的必须是第一个参数
    public static int ToInt(this string input)
    {
    
    
        int res;
        try
        {
    
    
            res = int.Parse(input);
        }
        catch
        {
    
    
            res = -1;
        }
        return res;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_45345384/article/details/128477208