Is this really a mistake in optimizing JIT, or am I missing something?

[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);
    }
}

This code only crashes in Release mode on the line Assert.Fail(), although the variable relayis still in scope, and therefore we still have a strong reference to the instance, so WeakReference should not be dead yet.

UPD: To clarify a bit: I understand that it can be "optimized". But depending on this optimization, the variable indicatorwould matter 0or 1, that is, the actual visible change in behavior.

UPD2: From the C # Language Specification, Section 3.9

- , , , . # , , . , , - , , ( ) .

, , , " " ( # , CLR, - ). CLR/JIT.

UPD3: CLR - " ":

... . . , . , (JIT) . , , .

, , , . , /, .

UPD4: .NET Framework:

    // This method DOES NOT DO ANYTHING in and of itself.  It used to
    // prevent a finalizable object from losing any outstanding references 
    // a touch too early.  The JIT is very aggressive about keeping an 
    // object 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);

, .

+5
1

, , - . , JIT , , .

. 2 3.9 .

,

, , , , , ( ) , .

; , ( ) . , # , , . , . , , - ( , , , if(false)), .

WeakReference , . , .

WeakReference - , ; ; ( ) , , . , , :

public class MyClass
{
    public MyClass(object foo)
    {
        Console.WriteLine(foo);
    }
}

:

var relay = new Relay();
...
var myClass = new MyClass(relay);

, relay, MyClass . , WeakReference "" , , , .

; , ( ) . relay - , (, , ) . DisableOptimizations , ( ) , , , , .

+8

All Articles