Search code examples
c#garbage-collectionclosures

C# Action, Closure, and Garbage Collection


Do I need to set MyAction to null so that garbage collection will be able to proceed with either of these classes?

I am less concerned when both classes are to have almost the same lifespan. My question is more appropriate when Class1’s lifespan is much longer then Class2 or when Class2’s lifespan is much longer then Class1.

The code here is stripped down. Assume that both Class1 and Class2 contain other members and methods that may affect their lifespan.

public class Class1 : IDisposable
{
    public Action<string> MyAction { get; set; }

    // Is this necessary?
    public void Dispose()
    {
        MyAction = null;
    }
}

public class Class2
{
    string _result = string.Empty;

    public void DoSomething()
    {
        Class1 myClass1 = new Class1();
        myClass1.MyAction = s => _result = s;
        myClass1.Dispose();
    }
}

Solution

  • Do I need to set MyAction to null so that garbage collection will be able to proceed with either of these classes?

    No. Any time you "Dispose" of a managed resource, odds are good you are doing it wrong. Let the garbage collector do its work.

    I am less concerned when both classes are to have almost the same lifespan. My question is more appropriate when Class1’s lifespan is much longer then Class2 or when Class2’s lifespan is much longer then Class1.

    The question doesn't make any sense; classes do not have lifetimes. Storage locations have lifetimes. Precisely which storage locations are you worried about? Can you clarify the question?

    I note that there are very serious concerns about extending lifetimes of closed-over variables via closures; your question however is so vague that it is hard to understand whether you are running into such a situation. Let me demonstrate:

    class Expensive
    {
        public byte[] huge = MakeHugeByteArray();
    }
    
    class Cheap
    {
        public int tiny;
    }
    
    class C
    {
        public static Func<Cheap> longLived;
        public static void M()
        { 
            Expensive expensiveLocal = new Expensive();
            Cheap cheapLocal = new Cheap();
            Func<Expensive> shortLived = ()=>expensiveLocal ;
            C.longLived = ()=>cheapLocal;
        }
    }
    

    What is the lifetime of local variable expensiveLocal? The lifetime of a local variable is typically short; typically local variables live no longer than the method activation. However, being in a closure extends the lifetime of a local variable arbitrarily long, to the lifetime of the closure. In this particular case, both lambdas share a closure, and that means that the lifetime of local variable expensiveLocal is at least as long as the lifetime of local variable cheapLocal, which has an indefinitely long lifetime because a reference to the closure has just been stored in a static field that lives forever. That big byte array might never be reclaimed, even though the only thing that seems to reference it has been collected long ago; the closure is a hidden reference.

    Many languages have this problem; C#, VB, JScript and so on all have lexical closures that are not partitioned to group variables by lifetime. We are considering changing C# and VB to have better lifetime management for closures but that is far-in-the-future work at this point, so no guarantees.