Search code examples
c#scopegarbage-collectionweak-references

ConditionalWeakTable - GC.Collect() behavior, why it does not operate as expected?


Why my weakRef.Target is still alive on the second shot?

Could it be a bug? If not, where is the error?

Result:

weakRef.Target is alive = True, expected true because inst keep a hold on SomeClass.
weakRef.Target is alive = True, expected false, because there is no more ref on SomeClass.

Code:

public static class DelegateKeeper
    {
        private static ConditionalWeakTable<object, Action> cwtAction = new ConditionalWeakTable<object, Action>();
        public static void KeepAlive(Action action) => cwtAction.Add(action.Target, action);
    }

    public class SomeClass
    {
        public void DoSomething() { }
    }

    public static class GcHelper
    {
        public static void Collect()
        {
            // OK surely overkill but just to make sure. I will reduce it when everyting will be understood.
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
            GC.WaitForPendingFinalizers();

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
            GC.WaitForPendingFinalizers();
        }
    }

    SomeClass instanceSomeClass;
    WeakReference<Action> weakRef;

    [TestMethod]
    public void TestLifeOfObject()
    {
        Init();
        GcHelper.Collect();
        Debug.WriteLine($"weakRef.Target is alive = {weakRef.TryGetTarget(out _)}, expected true because inst keep a hold on SomeClass.");

        RemoveLastReferenceOnSomeClass();
        GcHelper.Collect();
        Debug.WriteLine($"weakRef.Target is alive = {weakRef.TryGetTarget(out _)}, expected false, because there is no more ref on SomeClass.");
    }

    private void Init()
    {
        instanceSomeClass = new SomeClass();
        var action = instanceSomeClass.DoSomething;
        weakRef = new WeakReference<Action>(action);
        DelegateKeeper.KeepAlive(action);
    }

    private void RemoveLastReferenceOnSomeClass()
    {
        instanceSomeClass = null;
    }

Solution

  • I also asked the question at Microsoft and got the right answer from Viorel-1: learn.microsoft.com

    Answer: Remove the first "Debug.WriteLine" in sample.

    The reason is the exact same one as this question: Unexpected behavior of object life with WeakReference usage [duplicate] and this one: Collect objects still in scope - GC.Collect

    The reason is: Either if I use weakReference out parameter discard** : "_", there is a reference to the invisble return value that is used and is in scope. Until that reference stay in scope (the time of the function), there is a reference to the weakReference target. I mean the weakReference stay in state "reachable/rooted" for the garbage collector until the function ends.

    By removing the first "Debug.WriteLine", I got the expected behabior. Microsoft reference

    ** "Discards" are placeholder variables that are intentionally unused in application code. In code, "Discard" is represented as "_" (underscore).