Konstruktor

Konstruktor is a small constructor dependency injector for .NET. It is hosted on google code.

Konstruktor: new DefaultImplementation Attribute

I’ve added a new attribute to Konstruktor: The attribute [DefaultImplementation] declares a class to be the default implementation for the immediate interfaces it is implementing. To use this feature, Builder.scanAssembly(Assembly) needs to be called. It scans all types of the given assembly and registers the appropriate interfaces.

If you are interested, the unit test shows some more details.

Idea: Automatic Lifetime Scopes for Constructor Dependency Injection

I just had this idea to derive the lifetime scopes of instances automatically from their dependencies.

Given that

  1. Only constructor injection is used (which is the kind of purity I prefer).
  2. Nested lifetime scopes and new instances are created only by constructors having the argument types of Func<Instance> or Func<Argument, Instance>.

… the “intended” lifetime scope of all resolved instances can be derived by one simple rule:

  • When an instance is resolved, it is bound in the current or first ancestor scope that fulfills the closure of all its dependencies.

Of course there is one exception: Func<> returned instances and their arguments “need” to be created in a new nested scope.

An example (simplified to constructors):

  • PersonName(string)
  • PersonsFriends(PersonName)
  • Person(PersonName, PersonsFriends)

Whenever a Func<PersonName, Person> generator is used, PersonName is bound to the new lifetime scope and implicitly forces PersonsFriends also to be bound to the same scope, because the dependency PersonName can be fulfilled.

This solves one essential problem with dependency injectors with nested lifetime scoping:

If an instance of PersonsFriends already exists in an ancestor scope, it would be used instead of a new instance. In other words, it would just silently “shadow” an instance that is parameterized differently.

Now, by tracking down the closure of all dependencies, the lifetime scope chosen for the instance would be just right.

This problem was bothering me for some time and was indeed the most confusing behavior of my constructor dependency injector.

    I doubt hope that the implementation will be simple.

About Konstruktor lifetime scopes

Konstruktor supports just only one single lifetime concept. To clarify the implications and why I think it covers most use cases, I’ll try to explain what a lifetime scope is:

Lifetime Scopes

In Konstruktor, all lifetime scopes together form a tree, a hierarchical structure with one single root.

When an instance needs to be resolved, either by explicitly calling the IScope.resolve() method or by a constructor argument of a dependent object, the scope uses the following resolution algorithm:

  • If an instance of the requested type is existing in the scope, it is immediately returned. This behavior is similar to Autofac’s InstancePerLifetimeScope()
  • If an instance of the requested type is not existing, the parent scope is asked for an instance.
  • If there is an instance in an ancestor scope, this instance is returned.
  • If there is no instance found, a new instance is created by the Builder and is stored in the scope that initially requested the instance.

Effectively, a newly created instance’s lifetime extends over the scope in which it was first resolved and all its nested branches.

In Konstruktor, there can be only one instance of the same type in one scope.

The implications are:

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.

Konstruktor update: constructor precedence selection

Konstruktor is growing up a little.

In revision 18, Konstruktor, supports precedence selection of constructors in the Builder:

Use the new Builder method “preferConstructor<ImplementationType>(params Type[])” to prefer a specific constructor of an implementation type.

Take a look out at these unit tests to find out more about how to configure constructor precendence by using attributes or the Builder.

Syndicate content