1

Can anyone explain what happens here?

delegate void TestDelegate(string val);
class Program
{
    static void Main(string[] args)
    {
        Test p = new Test();
        string s = "Main";

        TestDelegate strongRef = (val) =>
            {
                Console.WriteLine(val + ": " + s);
            };

        p.AssignLambda(strongRef);

        while (true)
        {
            p.CallLambda();
            Thread.Sleep(1000);
            System.GC.Collect();
        }
    }
}

class Test
{
    WeakReference _ref;
    internal void AssignLambda(TestDelegate del)
    {
        _ref = new WeakReference(del);
    }

    internal void CallLambda()
    {
        TestDelegate del = _ref.Target as TestDelegate;
        if (del != null)
        {
            del("Delegate Alive");
        }
        else
        {
            Console.WriteLine("Delegate Dead");
        }
    }
}

I am expecting "Delegate Alive: Main" to get printed continuously. But I get "Delegate Dead" printed continuously.

Note: I compile this as 64 bit application. It works fine as expected if I compile it as 32 bit Application.

Compiler: VS 2012, Target Framework: .NET 4.0, OS: Win 7 64 bit professional

user2242746
  • 369
  • 4
  • 7
  • `if I compile it as 32 bit Application. Compiler: VS 2012, Target Framework: .NET 4.0, OS: Win 7 64 bit professional` ....? so what happens you didn't complete your comment/ statement – MethodMan Aug 20 '14 at 18:00
  • If I compile it as 32 bit application, I get "Delegate Alive: Main" – user2242746 Aug 20 '14 at 18:02
  • 2
    Based on this code, you *should* be expecting "Delegate Dead" to be printed, because you force a garbage collection cycle while there are no strong references to the delegate object. – cdhowie Aug 20 '14 at 18:04
  • good catch `cdhowie` – MethodMan Aug 20 '14 at 18:05
  • 1
    Why should it be alive? You're only holding a weak reference to the delegate and there are no other references. There are no actual rooted references to it, so there is no reason for it to not get GC-ed. The GC is of course not *obligated* to collect it, so it is not *wrong* to not collect it, so it saying that it is alive is also valid. – Servy Aug 20 '14 at 18:05
  • Just now compiled it for .net 4.5. Still I get different results for 32 bit and 64 bit. – user2242746 Aug 20 '14 at 18:06
  • See here for a discussion of when an object referred to by a variable in a method can get garbage collected: http://stackoverflow.com/questions/17130382/understanding-garbage-collection-in-net/17131389#17131389 – dbc Aug 20 '14 at 18:07
  • 1
    @user2242746 Both results are entirely valid, so getting different results in different runtimes *that are free to perform either action* is not a problem. It is merely an indication that implementation details that are subject to change do in fact sometimes actually change between versions. This is why you shouldn't rely on implementation details – Servy Aug 20 '14 at 18:08

1 Answers1

3

Based on this code, the delegate object should be collected when you call System.GC.Collect(), because there are no strong references to the delegate object at that time -- the only reference is a weak one, which does not prevent collection.

It is possible that the difference in behavior is due to different JIT engines between the 32-bit and 64-bit CLRs; it is possible (and very likely) that a reference to the delegate object remains in a CPU register when using the 32-bit JIT, but not when using the 64-bit JIT. This would explain why the object is always collected in one case but not in another.

This should be considered an implementation detail. As the GC is not guaranteed to be deterministic, your code should not rely on particular behavior from the GC.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • I have slightly modified the code to hold a strong reference besides a weak reference. But still I get "Delegate Dead" in 64 bit mode. Here is my modified code: – user2242746 Aug 20 '14 at 18:14
  • @user2242746 Clearly you *aren't* holding onto a valid reference if the object is being collected. In all likelihood the reference you are holding onto is itself eligible for collection. – Servy Aug 20 '14 at 18:16
  • 1
    @user2242746 As I theorized earlier, your reference is in fact eligible for collection. Locals are eligible for collection as soon as they can no longer be read from, which is the case here. – Servy Aug 20 '14 at 18:19
  • 2
    @user2242746 `strongRef` does not get used once you enter the while loop. Therefore its target is actually eligible for collection! Try adding `GC.KeepAlive(strongRef);` after the while loop; this should prevent it from being collected. (Locals that cannot possibly be used again do not maintain strong references; the `GC.KeepAlive()` call is a documented way to guarantee that a local is considered "used again" at a later time.) – cdhowie Aug 20 '14 at 18:19
  • Thanks a lot cdhowie and Servy. I am able to hold on to the delegate after using GC.KeepAlive. – user2242746 Aug 20 '14 at 18:26