Dynamically binding event handlers is a common source of bugs, and memory leaks.
The traditional approach is to use a finally block:
EventHandler eventHandler = (s, e) => fooService.Bar();
eventSource.AnEvent += eventHandler;
try
{
// do something that raises the event
}
finally
{
eventSource.AnEvent -= eventHandler;
}
An alternative way to ensure that your handler is always unbound correctly is to (ab)use IDisposable (again!) and use the using sugar:
using (_eventHandlerFactory.Attach(_eventSource, _fooService))
{
// do something that raises the event
}
public class EventHandlerFactory
{
public IDisposable Attach(IEventSource eventSource, IFooService fooService)
{
EventHandler eventHandler = (s, e) => fooService.Bar();
eventSource.AnEvent += eventHandler;
return new EventHandlerCookie(eventSource, eventHandler);
}
private class EventHandlerCookie : IDisposable
{
private readonly IEventSource _eventSource;
private readonly EventHandler _eventHandler;
public EventHandlerCookie(IEventSource eventSource, EventHandler eventHandler)
{
_eventSource = eventSource;
_eventHandler = eventHandler;
}
public void Dispose()
{
_eventSource.AnEvent -= _eventHandler;
}
}
}
public interface IFooService
{
void Bar();
}
public interface IEventSource
{
event EventHandler AnEvent;
}
[TestFixture]
public class EventHandlerFactoryTests
{
private IEventSource _eventSource = MockRepository.GenerateStub<IEventSource>();
private IFooService _fooService = MockRepository.GenerateStub<IFooService>();
private EventHandlerFactory _eventHandlerFactory;
[SetUp]
public void SetUp()
{
_eventHandlerFactory = new EventHandlerFactory();
}
[Test]
public void Event_handler_is_attached()
{
_eventHandlerFactory.Attach(_eventSource, _fooService);
_eventSource.Raise(es => es.AnEvent += null, _eventSource, EventArgs.Empty);
_fooService.AssertWasCalled(fs => fs.Bar());
}
[Test]
public void Event_handler_is_detached_when_disposed()
{
using (_eventHandlerFactory.Attach(_eventSource, _fooService))
{
_eventSource.Raise(es => es.AnEvent += null, _eventSource, EventArgs.Empty);
}
_eventSource.Raise(es => es.AnEvent += null, _eventSource, EventArgs.Empty);
_fooService.AssertWasCalled(fs => fs.Bar(), o => o.Repeat.Once());
}
}