且听疯吟 如此生活三十年
Reference in CSharp

起因是 V2EX 的一个帖子.
C++ 中引用是一个很基础但是也很容易忽略的问题,那么在 C# 中呢?看下面的 Gist.

gist 5898137

分析

C# 中引用类型参数是按引用(即指针地址)来传递的,所以我们使用第一个 Swap 方法——结果是 a 和 b 的值没有变化。
再使用 ref 试试,OK,a 和 b 的值交换了。

再看没有使用 ref 的 Swap 方法。temp = a 实际上是令 temp 形参作为 Swap 函数的局部变量,即在栈上开辟空间存储了实参 a 指向的堆地址(形参 a 和 b 同理)。
操作完成后,可以看出,仅仅是在栈上的形参 temp、a、b 所存储的堆地址发生了改变,而原实参 a、b 所存储的堆地址并没有发生变化。

C# 的引用传递和值传递

引用类型作为参数时:

  • 在修改变量本身时,结果类似于值传递,即不会改变传递前的变量的值,换句话说就是值传递传的是对象的值拷贝,而引用类型参数的值实际上就是其指向的堆对象的指针地址
    即值传递传的是对象的值拷贝,函数内参数对象是调用时传递的对象的栈中对象的拷贝。

  • 在修改变量的属性或字段时是引用传递,会影响到传递前的变量的值
    如代码中,如果交换 a 和 b 的成员的值,则会修改实际对象的值。比如 SwapValue 方法可以成功交换。
    这是因为形参 a 指向了堆中对象,修改其字段值实际上也就修改了该对象的字段,而实参 a 和 形参 a 指向的是同一对象,所以也就修改了实参 a 的值

  • 参数使用了 ref 后,才是引用传递。不管修改变量本身还是修改变量的属性或字段,都会影响到传递前的变量的值
    因为使用 ref 后,传递了实参 a 自身在栈上的地址而不是堆中对象的地址(即 C/C++ 中指针的指针)。因此改变形参 a 实际上就相当于改变了实参 a。同时通过操纵引用可以间接操纵 a 的字段。

Other

  • Stack 堆栈,Heap 托管堆
  • 事实上关于引用传递和值传递这两个概念实际上没有很好的定义,反而容易产生误解
  • 一般情况下可以将 C# 的引用理解为指针,但是实际上它们还是有区别的

P.S. 如果你使用 ReSharper 的话,你会发现第一个 Swap 方法中 ReSharper 提示 a 和 b value assigned is not used,交换变量的三行代码其实都可以 Remove。


Update

看起来好像解释的更不清楚了
关于所谓的「引用传递」和「值传递」,其实大可不必纠结这个名称,弄清楚这些是怎么作用的就好

感觉变成了「看了一些容易误解的技术文章,然后写了一些更容易误解的文章」的循环 😅