Dan Rigsby – Coding Up Style

Developer.Speaker.Blogger

.Net EventHandlers – Best Practices

Posted by Dan Rigsby on March 9th, 2008

One question that comes up a lot is, “How can I can clean up my event handlers in an object?”.  What are the best practices for cleaning up event handlers?  This is actually a pretty important question.  One of the number one reasons for memory drains in .Net is because event handlers aren’t removed.   For instance: if ObjectA is registered for an event on ObjectB and ObjectB goes out of scope and should be garbage collected, it can’t because ObjectA still has a reference to do it via the event handler.  Get into some complex circular patterns, and you are just eating memory.

In an ideal world every time an object registers for an event, it should remove it when that object is destroyed, but what happens when the object it is listening for events on should be destroyed?

One way I use to get around this, is to declare the event as private, then expose it through a public property with Add and Remove accessor methods like so:

private System.EventHandler m_MyEvent;  

public event System.EventHandler MyEvent
{
    add
    {
        m_MyEvent += value;
    }
    remove
    {
        m_MyEvent -= value;
    }
}

Then in your Dispose() method or in some other clean up method, you can set the private event to null to remove all subscriptions to the event.

public void Dispose()
{
    m_MyEvent = null;
}

By setting the private event to null, you are basically telling it to remove any events listening to it. So that there are no more references to the object, so it can be disposed cleaning. This is similar to getting the invocation list of the event and removing all of the delegates:

foreach(System.EventHandler e in MyEvent.GetInvocationList())
{
    MyEvent -= e;
}

You could also use WeakReference objects which can be used to bypass this same situation.  However, it does involve rewriting a good chuck of code, and take a bit of thought to decide where to put it and where not to.  Using the method above can work with a WeakReference as well.  The nice thing about the method above is that the object is cleaning itself up and doesn’t rely on the GC or something else to do its dirty work.

One Response to “.Net EventHandlers – Best Practices”

  1. Jack Says:

    This is a fantastic post. I tested this with a test program and it cleans up the events nicely.


    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;

    namespace Event_Firing_TEST_Profiler
    {
    public partial class Form1 : Form
    {
    public static int totalExecutions = 0;

    public Form1()
    {
    InitializeComponent();
    }

    Record _record = new Record();
    Queue _watchers = new Queue();

    private void timer_Tick(object sender, EventArgs e)
    {
    _record.ID++;
    for(int i = 0; i 10000)
    {
    for(int i = 0; i < _watchers.Count * 0.75; i++)
    {
    RecordWatcher watcher = _watchers.Dequeue();
    //watcher.Dispose();
    }
    _record.ClearEvents();

    }

    _txtInfo.Text = _record.Info;
    }

    private void Form1_Load(object sender, EventArgs e)
    {
    timer.Enabled = true;
    }
    }

    class Record
    {
    public delegate void UpdateHandler(object sender, EventArgs e);
    private event UpdateHandler _onUpdate;

    public event UpdateHandler OnUpdate
    {
    add { _onUpdate += value; }
    remove { _onUpdate -= value; }
    }

    public void ClearEvents()
    {
    _onUpdate = null;
    }

    private int _id;
    private int _firedThisRound;
    private TimeSpan _fireSpan;
    public int ID
    {
    get { return _id; }
    set
    {
    _id = value;
    _firedThisRound = 0;
    DateTime start = DateTime.Now;
    FireEventOnCallerThread(_onUpdate, this, EventArgs.Empty);
    DateTime end = DateTime.Now;
    _fireSpan = end.Subtract(start);
    }
    }
    public void IncrementFired()
    {
    _firedThisRound++;
    }
    public string Info
    {
    get
    {
    int dLength = 0;
    if(_onUpdate != null)
    {
    MulticastDelegate md = _onUpdate;
    Delegate[] d = md.GetInvocationList();
    dLength = d.Length;
    }
    return "delegate.count = " + dLength.ToString("#,0") + ", fired this round = " + _firedThisRound.ToString("#,0") + ", fireSpan = " + _fireSpan.TotalSeconds.ToString("#,0.000") + ", totalExecutions = " + Form1.totalExecutions.ToString("#,0");
    }
    }

    public static void FireEventOnCallerThread(MulticastDelegate md, object sender, object e)
    {
    // If there is no event, then we have nothing to do
    if(md == null) return;

    // Create an argument array for the function
    object[] args = new object[] { sender, e };

    // Loop each delegate in the invocation list
    foreach(Delegate d in md.GetInvocationList())
    {
    ISynchronizeInvoke isi = d.Target as ISynchronizeInvoke;
    if(isi != null && isi.InvokeRequired)
    isi.BeginInvoke(d, args);
    else
    d.DynamicInvoke(args);
    }
    }
    }

    class RecordWatcher : IDisposable
    {
    public Record _record;
    public RecordWatcher(Record record)
    {
    _record = record;
    _record.OnUpdate += new Record.UpdateHandler(_record_OnUpdate);
    }

    void _record_OnUpdate(object sender, EventArgs e)
    {
    Form1.totalExecutions++;
    ((Record)sender).IncrementFired();
    }

    public void Dispose()
    {
    _record.OnUpdate -= new Record.UpdateHandler(_record_OnUpdate);
    }
    }
    }

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>