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

我一直在试图找出.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上被压缩(我能找到)与将托管引用传递给非托管代码有关。 希望这是因为我不必担心托管代码中的任何托管引用。 🙂

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

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

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

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

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

正确。

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

不。 没关系。

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

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

但是,如果垃圾收集器在紧凑期间移动此对象,那么会不会在Increment()中弄乱对t的引用?

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

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

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

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

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

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

当然,如果你喜欢担心这些东西,你总是可以使用“不安全”的function来关闭这些安全系统,然后你就可以编写堆栈并将堆坏的错误写入你的内心。

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

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

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

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

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