Search code examples
c#winformstimergarbage-collection

System.Windows.Forms.Timer and Garbage Collection with C#


If I create a System.Windows.Forms.Timer that is not associated with the Form.Designer.cs file (I just instantiate the timer anywhere in my code for example), and I create a System.ComponentModel.Container component and create a control to add to it, say a NotifyIcon(this notifyIcon would not have a Icon associated with it) and add that notifyIcon to my components object THEN instantiate the forms.timer with the component as a parameter for the timer constructor, would the timer ever be GC-ed if the timers' enabled property was set to false during the lifetime of the program AND the timer was never disposed of during the lifetime of the program? Or as long as that component is not disposed of directly, would the timer be safe from garbage collection?

Here is a code example:

NotifyIcon notifyIcon = new NotifyIcon();
notifyIcon.Visible = false;
System.ComponentModel.IContainer components = new System.ComponentModel.Container();
components.add(notifyIcon);
System.Windows.Forms.Timer formTimer = new System.Windows.Forms.Timer(components);
formTimer.Enabled = true;
formTimer.Enabled = false;

Thanks for reading


Solution

  • Theory

    If object A holds a reference to object B, and you assign null to A, then after a while A is garbage collected. if there are still others with a reference to B, then B is not garbage collected. However, if A was the last one with a reference to B, then B will also be collected:

    Person Joseph = new Person
    {
        Id = 1, 
        Address = new Address {Id = 10, ...},
        ...
    };
    
    Joseph.Address = new Address {Id = 11, ...}
    

    After a while Address with Id 10 will be garbage collected, because no one holds a reference to it anymore.

    Person Mary = new Person
    {
        Id = 2, 
        Address = Joseph.Address,
        ...
    };
    Joseph = null;
    

    After a while the Person with Id 1 will be garbage collected, because no one holds a reference to it anymore. However, the Address with Id 11 will not be garbage collected, because the Person with Id 2 holds a reference to it.

    mary = null;
    

    Now no one holds a reference to the Address with Id 11, so it will be garbage collected.

    Back to your question

    • You create a new Container object
    • You create a new NotifyIcon object.
    • You add this NotifyIcon object to the components object
    • You create a new Timer objects, using the constructor that orders the timer to add itself to the Container.

    So the Container object has references to both the created NotifyIcon and Timer objects. As long as these objects are not Removed from the Container, and the container is not Disposed nor garbage collected, this Container will hold these references, and thus neither the notifyIcon nor the Timer will be garbage collected.

    components.Dispose();
    

    According to the reference source of System.ComponentModels.Container this will do the following:

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            lock (syncObj) {
            while (siteCount > 0)
            {
                ISite site = sites[--siteCount];
                site.Component.Site = null;
                site.Component.Dispose();
            }
            sites = null;
            components = null;
        }
    }
    

    So Container.Dispose() will call Dispose() of all added components, and then release the reference to the added components.

    • If you have references to these components elsewhere, then these components will not be garbage collected. However, since they are Disposed, usually they are not useable.
    • If the Container was the only one with references, then after Container.Dispose() the NotifyIcon and the Timer are eligible to be collected.
    • Because you still hold a reference to the Container, the Container itself is not collected. Since it is Disposed, the Container cannot be used anymore.
    • To make sure that the Container is collected, either let the reference go out of scope, or assign null to it.

    In every Form.Dispose() you'll find code similar to:

    private System.ComponentModel.IContainer components = null;
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }
    

    When the Form is Disposed, this.components is Disposed also, meaning that all IComponents that it holds are Disposed, and the references are removed. Even the reference to Container.ComponentCollection is removed. Note that the form still holds a reference to this.components, so even though this.components cannot be used anymore.

    This latter is a bit strange: if you don't hold references to your NotifyIcon / Timer anymore they are garbage collected, however this.Components is not, as long as the form exists. It would have been neater if Form.Dispose also released the reference to this.components