Konstruktor

I don’t actually need field and property injection from a dependency injection framework, do you?

And are XML configurations really required? Isn’t a small, tiny constructor injector sufficient?

For me, it must support

  • Constructor Injection
  • Automatic generation and injection of Func<> or Func<,> generators.
  • Open Generics, … sometimes you need that.
  • Lifetime management through IDisposable (at least for the root scope)

and it should be thread-safe.

Yesterday I’ve taken a look at StructureMap and Autofac, but both frameworks confused me a lot and I had a hard time finding out how they actually behave and if they support my needs. So, I decided to write my own, and here is it on google code:

In Konstruktor, there are two concepts, the Builder and the Scope:

  • The Builder contains all your rules
  • A Scope stores all the objects that the Builder instantiates

To get started, a builder needs to instantiated, and then rules are added. When the set up of the builder is finished, a scope is generated and objects can be resolved (all the Examples are also available in the Konstructor.Test project):

    sealed class A
    {
      public void printHelloWorld()
      {
        Console.WriteLine("Hello World");
      }
    }
    sealed class B
    {
      public readonly A A;
      public B(A a)
      {
        A = a;
      }
    }
    [Test]
    public static void myFirstKonstrukt()
    {
      var b = new Builder();
      using (var s = b.beginScope())
      {
        var bInstance = s.resolve<B>();
        bInstance.A.printHelloWorld();
      }
    }

Here, an instance of A is automatically created and injected in B.

Of course, Konstruktor supports resolving instances by interfaces. To register an interface mapping (a concrete class that should be instantiated when the interface is requested), use Builder.iface<Interface, ConcreteType>():

    interface IInterface
    {
    }
    
    sealed class Concrete : IInterface
    {
    }
    [Test]
    public static void mapInterface()
    {
      var b = new Builder();
      b.iface<IInterface, Concrete>();
      using (var s = b.beginScope())
      {
        var concrete = s.resolve<IInterface>();
        Assert.AreEqual(typeof(Concrete), concrete.GetType());
      }
    }

Builder.iface() also supports open generics, just specify the open types, for example to map all interfaces of IThreadSafe<T> to the concrete class ThreadSafe<T>, use

b.iface(typeof(IThreadSafe<>), typeof(ThreadSafe<>));

This works with arbitrary number of type arguments.

Konstruktor supports generating and injecting factory methods with optional parameterization, for example:

    sealed class ClientInstance
    {
      public readonly Server Server;
      public ClientInstance(Server server)
      {
        Server = server;
      }
    }
    sealed class Server
    {
      readonly Func<ClientInstance> _instanceCreator;
      public Server(Func<ClientInstance> instanceCreator)
      {
        _instanceCreator = instanceCreator;
      }
      public ClientInstance createClient()
      {
        return _instanceCreator();
      }
    }
    [Test]
    public static void serverFunc()
    {
      var b = new Builder();
      using (var s = b.beginScope())
      {
        var server = s.resolve<Server>();
        var client1 = server.createClient();
        var client2 = server.createClient();
        Assert.AreNotSame(client1, client2);
        Assert.AreSame(client1.Server, server);
        Assert.AreSame(client2.Server, server);
      }
    }

The interesting application here is that Server is now able to create any number of client instances but the original Server instance can also injected into the clients.

For the generated Clients (for each Func<> call), Konstruktor creates a new scope.

Func-injection also works with one parameter. The parameter argument will be stored in the nested scope.

Some notable specialities and implementation details:

  • Konstruktor does not see Interfaces that are not specified using in a Build.iface() call, so a Scope.resolve() to an interface that is implemented by a registered concrete type, which was not explicitly specified, will fail.
  • After an instance is created that implements IDisposable, the scope “owns” it. So if the Dispose() method of IScope is called, the scope will dispose all these instances.
  • Implicit Scopes (scopes that are created by calling injected Func<> generators), don’t dispose any objects. It is assumed that Lifetime is maintained by the caller.
  • Instantiation and constructor resolvement is implemented by using Reflection, so it may not be that fast.

And some more features not mentioned:

  • Scopes are thread-safe, builders are not, but builders are frozen after a scope has been created.
  • Constructor arguments can be injected explicitly by parameter index: Builder.constructorArgument<ConcreteType, ParameterType>(index, generator function). The Unit test is here.
  • The constructor with the most arguments is chosen for injection. The PreferredConstructor attribute may be used to override this behavior.
  • Explicit interface registration overrides open generic mappings, see here for an example (openGenericAndExplicit()).
  • Builder.generators() allows the registration of arbitrary generator methods, for an example take a look at openGenericGenerator() here.

Check out the source, or just browse it, license is the New BSD license, have fun.