再谈CLR:值类型按照引用传递(以及与装箱拆箱的区别)
经常被问到这样的问题:值类型能不能按引用传递?传递之后又是什么样的处理方式
当然是可以的,不管是现在还是以前都可以。我们来看看下面两个方法的区别
static void TestMethod(int a) { a = 5; Console.WriteLine(a); } static void TestMedhot2(ref int a) { a = 5; Console.WriteLine(a); }
我们知道int是值类型,在第一个方法中,我们按照默认的方式进行传递,其实就是所谓的按值传递。
我们看到在IL_0002这个地方,是把a这个变量压入了栈。也就是说,此时在栈中是有一个a变量代表的值的 ,例如5
然后我们再来看另外一个方法。首先从直观上看,int32&和int32肯定是有区别的,我们都知道在C++中,&表示指针的意思
然后,我们并没有看到它将a这个变量压入栈中。那么他到底是做了什么事情呢
注意,虽然按照引用传递,但这个与“装箱和拆箱”还不是一回事情。什么时候发生装箱拆箱呢?就是将值类型转换为了引用类型(一般指Object).假设我们如下的方法
static void TestMethod3(object a) { Console.WriteLine(a); }
然后,我们在Main里面去调用
int a = 7; TestMethod(a); TestMedhot2(ref a); TestMethod3(a);
这里调用TestMethod3的时候会发生装箱
也就是说,如果仅仅是按引用传递参数的话,那么不会发生装箱和拆箱的问题。也就是说,它并没有产生另外一份数据,而是用指针的方式指向了参数所代表的那份数据而已(这份数据可能在栈上面,也可能在堆上面),但总之是一个指针引用,所以说,按照引用传递的情况,我们如果在TestMetho2中修改了a的值,那么后续访问a这个变量,它的值就确实被改变了。
思考一下:为什么说可能在堆上面呢?
下面有一个完整的例子
using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { int a = 7; Console.WriteLine("原始值:{0}",a); TestMethod(a); Console.WriteLine("按值传递调用后:{0}", a); TestMedhot2(ref a); Console.WriteLine("按引用传递调用后:{0}", a); TestMethod3(a);//这里发生装箱 Console.WriteLine("装箱操作传递调用后:{0}", a); Console.WriteLine(a); Console.Read(); } static void TestMethod(int a) { a = 5; Console.WriteLine(a); } static void TestMedhot2(ref int a) { a = 5; Console.WriteLine(a); } static void TestMethod3(object a) { int _a = (int)a;//这里发生拆箱 Console.WriteLine(_a); } } }