public string TestProperty { get; set; } ChangeValue(Program p) { //此时传递进来的p副本和p都指向new Program { TestProperty = "init" } //对p副本所指向内存块的TestProperty属性做赋值操作, //而不是对p副本做赋值操作,那么也就是说p副本的指向并未改变 //实际上操作的内存块依然是new Program { TestProperty = "init" } //所以p副本所指向内存块的TestProperty属性已经指向"update"了 p.TestProperty = ; //这里只是改变了一个地址,就是p副本和p共同指向的内存块里面的TestProperty属性的地址 } Main() { Program p = }; //p变量存的是对new Program { TestProperty = "init" }的引用(内存地址:0x001) //那么传递的是p变量的副本(本质也就是对new Program { TestProperty = "init" }的引用的副本,也是0x001) //这2个0x001都存储在线程堆栈上,并且都指向new Program { TestProperty = "init" }的托管堆内存块 ChangeValue(p); //ChangeValue既没有改变p的指向,也没有改变p副本的指向 //p和p副本依然指向同一个内存块 //由于ChangeValue修改了该内存块TestProperty属性的值,所以输出结果“update” Console.WriteLine(p.TestProperty); }
public string TestProperty { get; set; } ChangeValue(Program p) { //此时传递进来的p副本和p都指向new Program { TestProperty = "init" } p = new Program();//对p副本做赋值操作,这个时候,p副本已经指向另外一个内存块0x002了,而p依然还是指向0x001 //对p副本所指向内存块0x002的TestProperty属性做赋值操作 //p副本所指向内存块0x002的TestProperty属性已经指向"update"了 p.TestProperty = ; //这里改变了二个地址,就是p副本的地址和0x002里面的的TestProperty属性的地址 } Main() { Program p = }; //p变量存的是对new Program { TestProperty = "init" }的引用(内存地址:0x001) //那么传递的是p变量的副本(本质也就是对new Program { TestProperty = "init" }的引用的副本,也是0x001) //这2个0x001都存储在线程堆栈上,并且都指向new Program { TestProperty = "init" }的托管堆内存块 ChangeValue(p); //ChangeValue并没有改变p的指向,而是将p副本的指向由0x001改成0x002,然后又将0x002的TestProperty改成“update”了 //所以p指向的0x001内存块没有受到任何影响 //那么p指向的0x001内存块里面的TestProperty值并未改变,所以输出结果“init” Console.WriteLine(p.TestProperty); }
通过以上代码的讲解和分析,我想我们可以窥探出其本质:
值传递的本质是传“值”的副本,只不过值类型变量的“值”是实例本身,而引用类型变量的“值”是实例的引用,这么一说就清晰很多了是吧!
在进行参数的引用传递时,当传递的参数为值类型时,实际上传递的是该值类型实例的引用(不是实例副本)。
因此方法操作的是值类型实例的引用,在方法中对参数所做的任何更改都将反映在该变量中。
值类型通过引用传递时,不会对值类型进行装箱。
ChangeValue(ref int value) { value = 0; } Main() { int value = 100; //实际上传递的是value实例的引用(类似于指针) ChangeValue(ref value); //由于ChangeValue方法修改了value的引用所指向的内存块里面的值(实例本身),所以输出是:0 Console.WriteLine(value); }
通过指针操作来更进一步的加深理解,注意看下图的实例的指针地址和实例引用的指针地址是一样的:
由此可以看出这是同一个内存块,所以ChangeValue改变就是实例所在的内存块里面的值。 4、引用类型参数的引用传递。