C#参数按引用和.net垃圾回收

C#参数按引用和.net垃圾回收,c#,pass-by-reference,C#,Pass By Reference,我一直在试图弄清楚.NET垃圾收集系统的复杂性,我有一个与C#参考参数有关的问题。如果我理解正确,方法中定义的变量存储在堆栈上,不受垃圾收集的影响。因此,在本例中: public class Test { public Test() { } public int DoIt() { int t = 7; Increment(ref t); return t; } private int Increment(ref int p) { p++; } } DoIt

我一直在试图弄清楚.NET垃圾收集系统的复杂性,我有一个与C#参考参数有关的问题。如果我理解正确,方法中定义的变量存储在堆栈上,不受垃圾收集的影响。因此,在本例中:

public class Test
{
 public Test()
 {
 }

 public int DoIt()
 {
  int t = 7;
  Increment(ref t);
  return t;
 }

 private int Increment(ref int p)
 {
  p++;
 }
}
DoIt()的返回值将是8。由于t的位置在堆栈上,因此无法对内存进行垃圾收集或压缩,Increment()中的引用变量将始终指向t的正确内容

然而,假设我们有:

public class Test
{
 private int t = 7;

 public Test()
 {
 }

 public int DoIt()
 {
  Increment(ref t);
  return t;
 }

 private int Increment(ref int p)
 {
  p++;
 }
}

现在,t存储在堆上,因为它是我的类的特定实例的值。如果我将此值作为引用参数传递,这不可能是个问题吗?如果我将t作为参考参数传递,p将指向t的当前位置。但是,如果垃圾收集器在压缩过程中移动此对象,这不会弄乱Increment()中对t的引用吗?或者垃圾收集器会更新通过传递引用参数创建的引用吗?我真的需要担心吗?唯一提到的担心在MSDN上压缩内存(我可以找到)是关于将托管引用传递给非托管代码。希望这是因为我不必担心托管代码中的任何托管引用

不,你不必担心。基本上,调用方法(
DoIt
)有一个对
Test
实例的“活动”引用,这将防止它被垃圾收集。我不确定它是否可以被压缩,但我怀疑它可以,因为GC能够发现哪些变量引用是被移动对象的一部分


换句话说,别担心。无论它是否可以压缩,都不会给你带来问题。

这正是你在最后一句中提到它的方式。GC将在压缩堆时移动所有需要的引用(对非托管内存的引用除外)

请注意,使用堆栈或堆与值或引用类型的实例变量相关。值类型(结构和“简单”类型,如int、double等)总是在堆栈上,类总是在堆中(堆栈中的是指向实例分配内存的引用-指针)

编辑:正如下面评论中正确指出的,第二段写得太快了。如果值类型实例是类的成员,它将不会存储在堆栈中,而是像其他成员一样存储在堆中

如果我理解正确,方法中定义的变量存储在堆栈上,不受垃圾收集的影响

这取决于你所说的“受影响”是什么意思。堆栈上的变量是垃圾收集器的根,因此它们肯定会影响垃圾收集

由于t的位置在堆栈上,因此无法对内存进行垃圾收集或压缩,Increment()中的引用变量将始终指向t的正确内容

“不能”在这里用起来很奇怪。首先,使用堆栈的意义在于,堆栈仅用于不需要压缩的数据,并且其生存期总是已知的,因此不需要对其进行垃圾收集。这就是我们首先使用堆栈的原因。你似乎是本末倒置。让我重复一遍,以确保清楚:我们之所以将这些东西存储在堆栈上,是因为它不需要收集或压缩,因为它的生命周期是已知的。如果不知道它的生存期,那么它将被丢弃。例如,迭代器块中的局部变量因此而进入堆

现在,t存储在堆上,因为它是我的类的特定实例的值

如果我将此值作为引用参数传递,这不可能是个问题吗

没有。那很好

如果我将t作为参考参数传递,p将指向t的当前位置

是的。尽管我更喜欢这样想,p是变量t的别名

但是,如果垃圾收集器在压缩过程中移动此对象,这不会弄乱Increment()中对t的引用吗

没有。垃圾收集器知道托管引用;这就是为什么它们被称为托管引用。如果gc移动对象,则托管引用仍然有效

如果您使用不安全的代码传递了一个指向t的实际指针,那么您需要将t的容器固定到位,以便垃圾收集器知道不要移动它。您可以使用C#中的fixed语句,或者通过为要固定的对象创建GCHandle来执行此操作

垃圾收集器是否更新通过传递引用参数创建的引用

是的。如果没有,它会很脆弱

我真的需要担心吗

没有。你把这个当成一个非托管C++程序员来考虑——C++让你做这项工作,但是C不这样做。记住,托管内存模型的全部目的是让您不必考虑这些东西


当然,如果你喜欢担心这件事,你可以随时使用“不安全”功能关闭这些安全系统,然后你就可以随心所欲地编写堆和堆栈腐蚀性bug。

第二段完全错了。正确的语句是“值类型存储在使用C#的Microsoft实现的CLI的Microsoft实现中的堆栈上,当值类型是局部或临时变量且局部不是lambda或匿名方法的封闭外部变量且块不是迭代器块时。”感谢您的精确性。我已经修改了答案。底线:规范承诺它会工作,因此实现将使它工作。