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

Yar*_*rko 15 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++;
 }
}
Run Code Online (Sandbox Code Playgroud)

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++;
 }
}
Run Code Online (Sandbox Code Playgroud)

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

Eri*_*ert 20

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

这取决于你所说的"受影响".堆栈上的变量是垃圾收集器的,因此它们肯定会影响垃圾收集.

由于t的位置在堆栈上,因此该内存不能被垃圾收集或压缩,并且Increment()中的引用变量将始终指向t的正确内容.

"不能"在这里使用是一个奇怪的词.首先使用堆栈的关键是因为堆栈仅用于永远不需要压缩的数据,并且其生命周期始终已知的,因此永远不需要进行垃圾回收.这就是为什么我们首先使用堆栈.你好像把马放在马前.让我重复一遍,以确保它是明确的:我们这个东西存储在堆栈上的原因是因为它并不需要被收集或压实,因为它的寿命是已知的.如果它的生命周期未知,那么它就会堆积.例如,迭代器块中的局部变量因此而在堆上.

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

正确.

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

不.没关系.

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

是的.虽然我更喜欢它的方式是p是变量t的别名.

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

不.垃圾收集器知道托管引用; 这就是为什么他们被称为托管引用.如果gc移动了东西,托管引用仍然有效.

如果你已通过一个实际的指针到T使用不安全的代码,那么你会被要求引脚 T的容器到位,使垃圾收集器就知道不动它.您可以使用C#中的fixed语句,或者通过为要固定的对象创建GCHandle来实现.

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

是的.如果不这样做,那将是相当脆弱的.

我根本不用担心这件事吗?

不.你正在考虑这个就像一个非托管的C++程序员--C++让你做这项工作,但C#没有.请记住,托管内存模型的重点是让您不必考虑这些内容.

当然,如果你喜欢担心这些东西,你总是可以使用"不安全"的功能来关闭这些安全系统,然后你可以写堆和堆栈腐败的错误到你的心脏的内容.


Jon*_*eet 6

不,你不需要担心它.基本上,调用方法(DoIt)具有对实例的"实时"引用Test,这将阻止它被垃圾收集.我不确定它是否可以压缩 - 但我怀疑它可以,GC能够发现哪些变量引用是被移动对象的一部分.

换句话说 - 别担心.无论是否可以压缩,都不应该导致问题.