C#: Signaling state changes through events

[Update: Paulo was not impressed with this post but he didn”t want to point it out in the comments. Next time, don”t be shy :,,) He thinks I should drop the old sytax and write thread-safe code. As always, he”s right. Let”s be clear: if you”re creating a new EventHandler event type, just use the generic EventHandler<T> type. You”ll write less and achieve the same thing. Another thing: if you”re writing multithreaded apps – for instance, Windows Forms apps – then don”t forget to change the code for add/remove and even the code used for raising the event so that it”s thread safe. There are already several good articles on how to achieve thread safety, so I”ll just redirect you to them.]

The CLR”s event model is based on delegates (this post presents the main features of delegates). Period. So, this may be a good time to refresh your knowledged on how delegates work.

Most types defined by the famework use events to communicate state changes to anyone that might be interested in being notified about that kind of operations. Generally, exposing an event implies that:

  1. you”ve defined a new type that is used to pass aditional information to the objects receiving the event notification. By convention, this type is a class that extends the EventArgs class;
  2. Build a new delegate that follows the pattern void Delegate_Name( object sender, Your_event_args_derived_type e)
  3. Add a member that exposes the event to the outside world

You should also keep in mind that the event pattern introduced by .NET recommends that your class defines a protected virtual method that is responsible for raising the event. By doing this, you can override that method in a derived class and perform some actions without incurring in the (small) performance hit associated with handling the event (this is a technique that is commonly used when handling ASP.NET page events on the page itself – ie, on the code behind file). Lets look at a simple example which generates an event when the age of a person changes:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Livro
{
    public class AgeChangedEventArgs:EventArgs
    {
        internal AgeChangedEventArgs(int _age)
        {
            this._age = _age;
        }

        private Int32 _age;

        public int Age
        {
            get { return _age; }
        }
    }

    public delegate void AgeChangedEventHandler(object sender, AgeChangedEventArgs e);

    public class Person
    {
        private Int32 _age;

        public Int32 Age
        {
            get
            {
                return _age;
            }
            set
            {
                if( _age == value)
                {
                    return;
                }

                _age = value;
                OnAgeChanged(new AgeChangedEventArgs(_age));
            }
        }

        protected virtual void OnAgeChanged(AgeChangedEventArgs e)
        {
             if( AgeChanged != null )
             {
                 AgeChanged(this, e);
             }
        }

        public event AgeChangedEventHandler AgeChanged;
    }
    class Start
    {
        public static void Main()
        {
            Person p = new Person();
            p.AgeChanged += PrintAge;
            p.Age = 20;
        }

        public static void PrintAge(object sender, AgeChangedEventArgs e)
        {
            Console.WriteLine(e.Age);
        }
    }
}

As you can see, I start by defining a new EventArgs derived class, which is used to pass the new age to the interesting parties (btw, do notice that this might not be necessary since the object parameter always contains a reference to the Person object that raised the event). Then, a new delegate is defined. The delegate is important because it”ll define the signature of the methods that can handle the event that will be fired by the  class.

Adding the event member is simple: we just need to use the event keyword and choose the delegate we want to “associate” with the event.

As a side note, we could have dismissed the delegate declaration and have used the EventHandler type for setting up the type of the event. This would be a better option for your production code, but i”m a sucker for history and so I”ve decided to do things “a la .NET 1.1” šŸ™‚

As you can see from the previous code, setting up a method to handle the event is really similar to what happened with delegates. Events usage is really simple; what”s interesting (as always!) are their internals. We”ll use the astonishing .NET Reflector tool (probably the best .NET free tool that you should keep in your toolbox) to see what”s going on. we”ll start by looking at what happens when we use the event keyword (I”ll only copy the important bits
from the Person class):

.field private class Livro.AgeChangedEventHandler AgeChanged
.eve
nt Livro.AgeChangedEventHandler AgeChanged
{
        .addon instance void Livro.Person::add_AgeChanged(class Livro.AgeChangedEventHandler)
        .removeon instance void Livro.Person::remove_AgeChanged(class Livro.AgeChangedEventHandler)
}

If you dig a little deeped (by selecting the event on the tree), you”ll see that the add_AgeChangedEventHandler looks like this:

.method public hidebysig specialname instance void add_AgeChanged(class Livro.AgeChangedEventHandler ”value”) cil managed synchronized
{
    .maxstack 8
    L_0000: ldarg.0
    L_0001: ldarg.0
    L_0002: ldfld class Livro.AgeChangedEventHandler Livro.Person::AgeChanged
    L_0007: ldarg.1
    L_0008: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
    L_000d: castclass Livro.AgeChangedEventHandler
    L_0012: stfld class Livro.AgeChangedEventHandler Livro.Person::AgeChanged
    L_0017: ret
}

As you can see, the compiler will add a field of type AgeChangedEventHandler and the event will be transformed on a “special property” which delegates to the add_/remove_ methods added by the compiler. At the end of the day, you”ll end up calling Combine when you use the += syntax to set up method to handle an event.

If you”re worried about synchronization issues, don”t be: the add_/remove_ methods are annotated with the synchronized attribute, which means that they”re thread safe (at least in theory, since the I think things aren”t really that safe when you”re using static methods – but that”s a topic for another post, right? :)).

Now it”s the perfect time to say that, unlike what happens with several other features supported by the C# compiler (ex.: enum declarations), you can define your own “event properties” (ie, event declarations can be “customized”). Here”s a quick example (only the important parts are listed here – note that this code is not thread safe!):

private AgeChangedEventHandler _ageChanged;
public  event AgeChangedEventHandler AgeChanged
{
    add
    {
        _ageChanged += value;
    }
    remove
    {
        _ageChanged -= value;
    }
}

protected virtual void OnAgeChanged(AgeChangedEventArgs e)
{
  if( _ageChanged != null )
  {
        _ageChanged(this, e);
  }
}

By now, you must surelly be worried, right? no? then think about the size of your class if you need to expose several events…not a pretty sight, right? for instance, if you think about windows forms or ASP.NET controls, it”s easy to see that they surelly must be bloated, right? WRONG!

If you look at an event that is raised by one of those controls, you”ll see that they use the Events property introduced by the Control base class (and this happens in windows forms and in ASP.NET applications) to save/get the delegates associated to the events. This property can be seen as a dictionary which is used to store the method that is going to handle the event. So, if you had several events on your class, you should change the previous example so that it looks like this:

protected virtual void OnAgeChanged(AgeChangedEventArgs e)
{
    AgeChangedEventHandler handler = (AgeChangedEventHandler) Events[_ageChangedKey];
  if( handler != null )
  {
        handler(this, e);
  }
}

private EventHandlerList _events;
protected EventHandlerList Events
{
    get
    {
        if(_events == null )
        {
            _events = new EventHandlerList();
        }
        return _events;
    }
}

private static object _ageChangedKey = new object();
public  event AgeChangedEventHandler AgeChanged
{
    add
    {
        Events.AddHandler(_ageChangedKey, value);
    }
    remove
    {
        Events.RemoveHandler(_ageChangedKey, value);
    }
}

Some important notes on the previous snippet:

  • you should define a key that uniquely identifies your event in the hastable maintained by the EventHandlerList
  • You no longer have one field per event with this strategy. This means that you”re not wasting space with unused events
  • The code is not thread safe. nor is the EventHandlerList class. So, if this is important to you, don”t forget to drop a few locks on your code (but please, please do pay attention to what and how you lock).
Advertisements

~ by Luis Abreu on September 24, 2007.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: