C# ref和out关键字内存层面剖析

小白版本

C#中数据类型分为值类型和引用类型
在这里插入图片描述
干货:
方法声明的参数列表中的参数叫做形参,实际调用方法时传递给方法的参数叫做实参,调用方法时,程序会首先将实参的值传递给对应的形参,而后执行方法体中的代码。对于引用类型的参数,实参和形参会指向同一个对象。而对于值类型的参数,实参的值将被复制一份给形参,方法代码中的对形参的修改不会影响到实参。

为了解决这一问题,C#中提供了方法参数的引用传递方式,ref关键字。

此时形参是实参的”引用”,二者指向同一个变量,如果代码中修改了形参的值,实参的值也会改变,这样就能真正起到交换参数值的效果了。
先来看一个栗子:

	//一会要用到的方法
	private void CorrectCount(int count)
    {
         count = 11;
    }
	private void CorrectCount(ref int count)
    {
         count = 11;
    }
    
	int count = 0;
	Debug.Log("Origin count: " + count);
	CorrectCount(count);
	Debug.Log("Correct count: " + count);
	CorrectCount(ref count);
	Debug.Log("Correct count ref : " + count);

	//输出:
	Origin count: 0
	Correct count: 0
	Correct count ref : 11

我们再看一个特殊的栗子:

	//一会要用到的方法
	private void CorrectCount(string count)
    {
         count = "11";
    }
	private void CorrectCount(ref string count)
    {
         count = "11";
    }
	string count = "0";
	Debug.Log("Origin count: " + count);
	CorrectCount(count);
	Debug.Log("Correct count: " + count);
	CorrectCount(ref count);
	Debug.Log("Correct count ref : " + count);

	//输出:
	Origin count: 0
	Correct count: 0
	Correct count ref :11

嗯?刚刚不是说,ref是为了解决值类型无法在方法内外真正交换参数值的问题吗? string 明明是引用类型,为啥子也不能【黑人问号脸】
这里需要特别注意的是,string虽然是引用类型,但这个东东太特殊了,C#对它进行了特殊处理,虽然它本身是引用类型,但相关操作都会导致它产生一个新的string对象,你看,你传进去是小明,人家在里面改了名字叫老王了。所以如果是string类型,也要用ref。
那还有其他的吗。不,没了,其他的引用类型(就剩class了)随便用。就算你class里面定义了很多值类型,在方法里面也修改了, 没得关系。但如果你的实参直接是class.int 对不起,你这个是值类型。

ref弄透彻了,再来说说它兄弟out

out关键字,加了它的就叫输出型参数(ref 是引用型参数)。其实两个用法差不多,只是有一些小区别罢了。
那就来看看他们的区别把:
round one:
ref:我可进可出(可以传值进来,我也可以传出去)
out:我只出不进(不能传值进来,但我可以传出去)
round two:
ref:进来之前必须初始化
out:进来之前爱咋地咋地,进来后,我就给你初始化(必须赋值才能出)
round three:
ref:我们俩兄弟不能构成重载
out:对,我们俩兄弟不能构成重载(如果两个方法只是ref和out关键字不同,其他的方法名/参数类型等都相同的话,不构成重载,编译器会报错)
举个简单的out的栗子:

        private void CorrectCountOut(out int count)
        {
            count = 33;
        }
        
        int count;
        CorrectCountOut(out count);
        Debug.Log("Correct count out: " + count);

		//输出
		Correct count out: 33

大神版本

先看一段代码的运行时:

在这里插入图片描述
CLR默认所有方法参数都是传值的。传递引用类型的对象时,引用对象(或者说指向对象的指针)被传给方法。注意,引用(指针)本身是传值的,意味着方法能够修改对象,而调用者能看到这些修改。对于值类型的实例,传给方法的是实例的一个副本,意味着方法将获得它专用的一个值类型实例副本,调用者中的实例不受影响。

CLR允许以传引用而非传值的方式传递参数。C#用关键字out和ref支持这个功能。两个关键字都告诉C#编译器生成元数据来指明该参数是传引用的。编译器将生成代码来传递参数的地址,而非参数本身

对于传引用方式(ref/out)传给方法的变量,他的类型必须与方法签名中声明的类型相同(需要完全一致,继承关系也不可以)。 为什么呢,因为方法预期的是某个类型的引用,而传进来的却不是这个类型的,会报错: ”无法将某类型转换成某类型“, 根本原因是为了保障类型安全,防止在方法内对类型进行了原意类型不存在的操作。想要解决这个问题,可以用泛型来定义方法就可以。

举例:ref

Func1(num)
public void Func1(int v1){}        //v1是一个值,num的副本
Func2(ref num)
public void Func2(ref int v2){}    //v2是一个引用(指针),指向num所在的地址(调用Func2时将num的地址传入)
Func3(obj)
public void Func3(object o1){}     //o1是一个值,o1指向obj所指向的地址
Func4(ref obj)
public void Func4(ref object o2){} //o2是一个引用(指针),指向obj所在的地址(调用Func4时将obj的地址传入)

在这里插入图片描述
out等同于ref

Func1(num)
public void Func1(int v1){}        //v1是一个值,num的副本
Func2(out num)
public void Func2(out int v2){}    //v2是一个引用(指针),指向num所在的地址(调用Func2时将num的地址传入)
Func3(obj)
public void Func3(object o1){}     //o1是一个值,o1指向obj所指向的地址
Func4(out obj)
public void Func4(out object o2){} //o2是一个引用(指针),指向obj所在的地址(调用Func4时将obj的地址传入)

从CLR的角度,ref和out是一样的,都导致传递指向实例的一个指针。
但对编辑器来说,会有所区别(ref在外必须初始化,out在内必须初始化后使用),以保证代码的正确性。

对于重载,因为CLR认为ref和out是一样的,所以以下重载是不支持的,只能留取其中一个。

public void Func1(ref int num){}
public void Func1(out int num){}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值