[TestFixture]
public class Tests
{
private class Relay
{
public Action Do { get; set; }
}
[Test]
public void OptimizerStrangeness()
{
var relay = new Relay();
var indicator = 0;
relay.Do = () => indicator++;
var weak = new WeakReference(relay);
GC.Collect();
var relayNew = weak.Target as Relay;
if (relayNew == null) Assert.Fail();
relayNew.Do();
Assert.AreEqual(1, indicator);
}
}
Assert.Fail()
尽管事实relay
变量仍在范围内,但该代码仅在 Release 模式下失败,因此我们仍然对实例有强引用,因此 WeakReference 一定还没有死。
UPD:澄清一下:我意识到它可以“优化掉”。但是取决于这个优化indicator
变量将具有0
或1
值,即我们有实际可见的行为变化。
UPD2:来自 C# 语言规范,第 3.9 节
如果除了运行析构函数之外,任何可能的继续执行都无法访问该对象或其任何部分,则该对象被视为不再使用,并且它有资格进行销毁。C# 编译器和垃圾收集器可能会选择分析代码以确定将来可能使用对对象的哪些引用。例如,如果作用域内的局部变量是对对象的唯一现有引用,但在从过程中的当前执行点开始的任何可能的继续执行中从未引用该局部变量,则垃圾收集器可能(但不需要)将对象视为不再使用。
从技术上讲,这个对象可以并且将通过继续执行来访问,因此不能被视为“不再使用”(实际上 C# 规范没有提到弱引用,因为它是 CLR 的方面而不是编译器 - 编译器输出很好)。将尝试搜索有关 CLR/JIT 的内存管理信息。
UPD3:以下是有关 CLR 内存管理的一些信息- “释放内存”部分:
...每个应用程序都有一组根。每个根要么引用托管堆上的一个对象,要么设置为空。应用程序的根包括全局和静态对象指针、线程堆栈上的局部变量和引用对象参数,以及 CPU 寄存器。垃圾收集器可以访问即时 (JIT) 编译器和运行时维护的活动根列表。使用此列表,它检查应用程序的根,并在此过程中创建一个包含从根可到达的所有对象的图。
有问题的变量肯定是局部变量,因此它是可访问的。如此说来,这个提及非常快速/模糊,所以我很高兴看到更具体的信息。
UPD4:来自 .NET Framework 的来源:
// This method DOES NOT DO ANYTHING in and of itself. It's used to
// prevent a finalizable object from losing any outstanding references
// a touch too early. The JIT is very aggressive about keeping an
// object's lifetime to as small a window as possible, to the point
// where a 'this' pointer isn't considered live in an instance method
// unless you read a value from the instance. So for finalizable
// objects that store a handle or pointer and provide a finalizer that
// cleans them up, this can cause subtle ----s with the finalizer
// thread. This isn't just about handles - it can happen with just
// about any finalizable resource.
//
// Users should insert a call to this method near the end of a
// method where they must keep an object alive for the duration of that
// method, up until this method is called. Here is an example:
//
// "...all you really need is one object with a Finalize method, and a
// second object with a Close/Dispose/Done method. Such as the following
// contrived example:
//
// class Foo {
// Stream stream = ...;
// protected void Finalize() { stream.Close(); }
// void Problem() { stream.MethodThatSpansGCs(); }
// static void Main() { new Foo().Problem(); }
// }
//
//
// In this code, Foo will be finalized in the middle of
// stream.MethodThatSpansGCs, thus closing a stream still in use."
//
// If we insert a call to GC.KeepAlive(this) at the end of Problem(), then
// Foo doesn't get finalized and the stream says open.
[System.Security.SecuritySafeCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static extern void KeepAlive(Object obj);
如果您有兴趣,请参阅此处了解更多详细信息。