Friday, 14 November 2008

Passing around references to events (almost)

Have you ever wished that you could pass a reference to an event on an object in C#? Perhaps you wanted to tell another object to monitor an event on something that you own. How do you say that in C#? If you've used C# 3 for any length of time you'll know that, through a magic combination of lambda expressions and Expression trees, it is possible to pass around references to properties and methods in a nice, type-safe, refactoring-proof way. But up till now I've not read of anyway to do the same for events1.

I've been doing some thinking about this recently, and I think I've invented a nice solution that will work in many cases. One case where I needed it was when creating a class to help me unit test events. I wanted an easy way to monitor a class, prod it and poke it in various ways, and then check that it raised the appropriate events. I came up with the EventAsserter. Hopefully I'll be able to share this with you in its full glory before long, but for now, I'll show a prototype as an example of using my event-passing technique.

Getting Hooked

The assumption is that you are calling a method, and want to be able to tell that method to hook up to an event of your choosing on an object that you specify. The basic idea is for you to pass into the method a delegate that allows it to call you back with its own event handler for you to hook up appropriately.

It looks like this:

using System;
using System.Diagnostics;

namespace EventMonitor
{
    class Program
    {
        static void Main(string[] args)
        {
            var eventRaiser = new EventRaiser();
            var eventAsserter = new EventAsserter<EventArgs>(handler => eventRaiser.TestEvent += handler);

            eventRaiser.RaiseEvent();

            eventAsserter.AssertEventOccurred();
        }

        class EventRaiser 
        {
            public event EventHandler<EventArgs> TestEvent;

            public void RaiseEvent()
            {
                TestEvent(this, EventArgs.Empty);
            }
        }

        class EventAsserter<TEventArgs> where TEventArgs : EventArgs
        {
            bool _eventOccurred;

            public EventAsserter(Action<EventHandler<TEventArgs>> attacher)
            {
                attacher((object sender, TEventArgs e) => _eventOccurred = true); ;
            }

            public void AssertEventOccurred()
            {
                Trace.Assert(_eventOccurred, "Event did not occur");
            }
        }
    }
}

It's a very simple example. I've got a dummy class, EventRaiser, that exists only to supply and raise an event. Then there's my prototype EventAsserter. This needs to be a generic type (taking the Type of the EventArgs from the event that you're wanting to handle) so that it knows what kind of event handler to supply: that's what it does in its constructor. It receives a delegate, and it calls this delegate back, passing through an event handler, and it expects that the delegate will hook the handler up to the correct event. Finally, the Main method ties everything together. As it constructs a new instance of EventAsserter  (in line 11) it hooks it up to the Test event of EventRaiser. Then it fires the event, and checks that it was indeed raised.

A Fallback solution for awkward cases

The sharp-eyed amongst you will have immediately spotted the flaw in my cunning plan: this will only work if the events that you are interested in have been declared using EventHandler<T>. This ought to be the case in a lot of code written since .Net 2.0, when the generic EventHandler was introduced. But what if you have events using custom delegates - PropertyChangedEventHandler for example, like the modified version of EventRaiser below?

You could special-case the technique for each type of event you want to handle, creating an overload for each type of EventHandler. Perhaps a nicer solution is to use the adapter pattern:

// ...
var eventAsserter = new EventAsserter<PropertyChangedEventArgs>(
                handler => new PropertyChangedEventAdapter(eventRaiser).PropertyChanged += handler);
// ...

public class PropertyChangedEventAdapter
{
        public event EventHandler<PropertyChangedEventArgs> PropertyChanged;

        public PropertyChangedEventAdapter(INotifyPropertyChanged source)
        {
            source.PropertyChanged += HandleEvent;
        }

        private void HandleEvent(object sender, PropertyChangedEventArgs e)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(sender, e);
            }
        }
}

Or, you could consider this solution below: it's less elegant, and encapsulation takes a bit of a beating, but workable as far as I can see.

using System;
using System.Diagnostics;
using System.ComponentModel;

namespace EventMonitor2
{
    class Program
    {
        static void Main(string[] args)
        {
            var eventRaiser = new EventRaiser();
            var eventAsserter = new EventAsserter<PropertyChangedEventArgs>(
                asserter => eventRaiser.PropertyChanged += asserter.HandleEvent);

            eventRaiser.RaiseEvent();

            eventAsserter.AssertEventOccurred();
        }

        class EventRaiser : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            public void RaiseEvent()
            {
                PropertyChanged(this, new PropertyChangedEventArgs(""));
            }
        }

        class EventAsserter<TEventArgs> where TEventArgs : EventArgs
        {
            bool _eventOccurred;

            public EventAsserter(Action<EventAsserter<TEventArgs>> attacher)
            {
                attacher(this);
            }

            public void HandleEvent(object sender, TEventArgs e)
            {
                _eventOccurred = true;
            }

            public void AssertEventOccurred()
            {
                Trace.Assert(_eventOccurred, "Event did not occurr");
            }
        }
    }
}

In this case the EventAsserter has to make its handler public. Then in its constructor it passes itself to the attacher delegate. The calling code then needs to know that it has to hook the HandleEvent method to the event that it is interested in. Not pretty, I know; but pragmatic at least. It just goes to show the benefit of using EventHandler<T> over defining your own custom delegates.

What do you think? Can you see yourself making use of this? Any refinements that you can suggest?

Footnotes

1. If you try assigning a delegate to an event within a lambda expression that is converted to an Expression tree you get the error "An expression tree may not contain an assignment operator".

0 comments:

Post a Comment