C# Events

An event provides an extensibility point to a class. An event lets a publisher notify subscribers when something happens. Typically the publisher is a vendor supplied UI widget, and the subscriber is the code you are writing.

There are two stages to using an event:

  1. Attach your method to the event.
  2. When the event fires your method gets called.

There are some rules about events which are useful to understand:

  • An event may only be invoked from the class which declares it.
  • Outside that class the only things you can do to the event is add and remove methods (+= and -=).
  • The event can have multiple methods attached to it, which will all be called when the event fires.
  • The event may have zero methods attached. So if you raise an event yourself, you must check for null before calling it.

The following code is a contrived example which illustrates using an event. Say you have a UI button which raises the “ButtonClick” event. You want to display some text when the button is clicked:

using System;
using MbUnit.Framework;

namespace EventDemo
{
    [TestFixture]
    public class EventTest
    {
        public class UiButton
        {
            public event EventHandler ButtonClick;

            public virtual void OnButtonClick()
            {
                if (ButtonClick != null)
                {
                    ButtonClick(this, new EventArgs());
                }
            }
        }

        public class CodeBehind
        {
            public UiButton widget = new UiButton();

            public string TextBoxText { get; set; }

            public void PageLoad()
            {
                widget.ButtonClick += HandleOnClick;
            }

            private void HandleOnClick(Object sender, EventArgs e)
            {
                TextBoxText = "ButtonClicked";
            }
        }

        [Test]
        public void Framework()
        {
            CodeBehind behind = new CodeBehind();
            behind.PageLoad();
            Assert.AreEqual(null, behind.TextBoxText);

            behind.widget.OnButtonClick();
            Assert.AreEqual("ButtonClicked", behind.TextBoxText);
        }
    }
}

If this were real code you would only be writing the CodeBehind class. The UiWidget and Framework code would be hidden away in the vendor library. This is one of the aspects which I think makes understanding events tricky; you typically only see a small part of what’s going on.

In the “PageLoad” method the HandleOnClick method is attached to the ButtonClick event. There are a number of approaches for attaching a method to the event:

  • Attach a named method programmatically:
    widget.ButtonClick += HandleOnClick;
    
  • Attach an anonymous method programmatically:
    widget.ButtonClick += (Object sender, EventArgs e) => { TextBoxText = "ButtonClicked"; };
    
  • Attach a named method declaritivly, on an asp.net page in-front:
    <asp:UiButton id="widget" runat="server" OnButtonClick="HandleOnClick" />
    

The signature of the event-handling method must match the signature of the event-handler delegate. So in the example, the HandleOnClick method must match the method signature of the EventHandler delegate:

public delegate void EventHandler(Object sender, EventArgs e);

The event handler delegate will always look similar to this. The only variation is in the type of the EventArgs parameter. This is because the delegate follows the following conventions:

  • The return type is void.
  • The method takes two parameters.
  • The first parameter is the object which raised the event. Its type is object and is not null.
  • The second parameter is the event arguments. Its either the EventArgs class or a derivative of it, and is not null.

These conventions don’t have to be followed. Take the first one – the return type should be void. Before I read up about events, I defined my own event with a custom delegate which returned an int. It would have gone badly wrong though, if another developer added a second method to the event. In that case I shouldn’t have used an event at all – just a straight delegate would have been fine.