Convention based event wiring

Update: this article is obsolete. I now consider event wiring a very bad practice and removed it from Konstruktor.

Event wiring may lead to a maintenance problem. For example, it is not possible to find out if a particular event is wired somewhere else in the object graph. And when an event is not be referenced anymore, it may be tempting for the developer to remove it, which may break the application.

There is almost always another, cleaner method to wire up events up to the object graph, for example by providing specific service interfaces downwards the object graph.


One important feature of dependency injection is that it does not require you to modify your classes. It works behind the curtain and wires up your object graph.

A not so quite obvious feature is that factories can be injected. Like referring to simple classes or interfaces, functions may be referred from within constructors. When called, these functions resolve objects and use all the magic of the dependency injector. Just by using conventions, the spirit and use of the dependency injector extends over the application lifetime.

In my implementation of a dependency injector, I focused strongly on constructor injection. Konstruktor only supports persistent objects per lifetime scope. When more objects of the same type need to be created, a constructor parameter of Func<T> automatically gets a factory injected that creates instances of type T (and their associated lifetime scope).

In a object graph, wiring events of dependent objects is simple. In the constructor the event source is referred, then a method is added to the event, and in IDisposable.Dispose() the method is removed from the event. The events flow from the root of the object graph down to the leaves.

But wiring events in the other direction is not so simple. There are no references available to the instances you need to receive events from. These event sources may not even exist yet. And often – architecturally required – there should be no direct dependency from the event source to the receivers.

A good example is logging.

So for Konstruktor, I experimentally implemented “convention based event wiring”. For every new object instantiated, all public events that have one delegate parameter type, can be automatically wired to a listening method that follows a very specific convention:

A method that is be wired to an event

  • must be named “actOn”
  • its parameter type must match the event’s delegate type
  • its instance must be in the same or ancestor’s lifetimes scopes

The reduction to one parameter type was a conscious one. Of course it makes the implementation simpler, but I’d liked the idea that for auto-wired events, new event types had to be created.

For example, to wire up logging events:

sealed class LogEvent
{
    public string Message;
}
 
sealed class Logger
{
    public void actOn(LogEvent logEvent)
    {
        Console.WriteLine(logEvent.Message);
    }
}
 
sealed class LoggingEventSource
{
    public event Action<LogEvent> Log;
 
    public void log()
    {
        Log(new LogEvent{ Message = "Hello World!"});
    }
}

Whenever an instance of LoggingEventSource is created and a Logger instance is in the “reachable” object construction graph, the Log event is automatically wired to the actOn method.

Convention based event wiring is a fairly uncommon concept in the dependency injection community. I just implemented this feature in Konstruktor because I found it very useful for one specific situation. Time will tell if this approach can be used in more complex scenarios.

Konstruktor is available on Google Code. Additionally, you may want to take a look at the tests for the automatic event wiring.