Comparing .Net IoC containers, part zero: Groundwork

Tags: IocContainer, IocComparison, comparison, groundwork, overview, DependencyInjection, DI, IoC, code

I started doing some compare and contrast code on well-known .Net Inversion Of Control containers some time in 2009, in order to better convince my colleagues that they should be using a good one. I haven't succeeded yet, but I have reworked the code into a simple and practical survey that may be of use to anyone interested in picking an Inversion of Control Container for .Net code.

In general I'm only looking at basic features, and the syntax used to access them, but I hope it's enough to get a feel for each one's strengths and weaknesses.

This article isn't aimed at teaching what an IoC container is or why you would want one; but in brief, Dependency Injection is the pattern of supplying an object's dependencies to it, usually via the constructor.  An IoC container automates this process.  Dependency Injection and Inversion of control are discussed by Martin Fowler if you want to read more. Or, you can look for answers on StackOverflow or view Mike Hadlow on Castle Windsor and a Tekpub video on Dependency Injection and Inversion of Control using NInject.

Ioc works on the Hollywood principle: don't call us, we'll call you. I had the "aha!" moment and "got" IoC after I had been on a team that was using it for about a week. I was working with a class in the middle of the whole application. It needed data from a new service, so I added this service to the class's constructor,  called the service at the right point, tested that the class worked, then asked myself: what else do I need to do in order to ripple this change to the rest of the system. The answer was absolutely nothing. The change was completely isolated by the IoC container. It's worth trying IoC, you may not fully get it until you do.

Using the dependency injection pattern is a way to more testable, loosely coupled architectures with separated concerns.  An IoC container is not needed to accomplish the DI, but it does make the pattern a lot easier to use. With an IoC container, it is trivial to swap in a mock or fake service for a test. In fact, the demo that I made was initially focused on this scenario. It has strayed a bit, but still demonstrates swapping out one lower-level service in a hierarchy for another.
I'll assume that you have some familiarity with Ioc ideas and tools, but that you don't know all of these IoC containers

I will use the following IoC containers for this demo:
Microsoft Unity version 2.0
Ninject version 2.0
Castle Windsor  version  2.0
StructureMap version 2.6.1
Autofac version 2.1
Spring.net version  1.3
I will also later aim to look at MEF 1.0 in.Net 4.0
Later I also looked at Funq and LightCore

Tool Vendor Version Automatic resolution Transient objects by default Mutable container
Unity Microsoft Patterns and Practices Group 2.0 Yes Yes Yes
Ninject Nate Kohari 2.0 Yes Yes Yes
Castle Windsor Castle Project 2.0 No No Yes
StructureMap Jeremy D. Miller 2.6.1 Yes Yes No
Autofac Nicholas Blumhardt 2.1 No Yes No
Spring.Net SpringSource 1.3 No No No (depends)
LightCore Peter Bucher 1.4 Yes No No



In this list there's everything from Microsoft P&P group releases, collaborative open-source projects to one person's code. In fact, a motivated and knowledgeable person can make a good IoC container on their own. I can see how one would go about making one, not that I am under any illusion that I can do a better job of re-inventing this wheel.

The completed blog posts are:

Part One: Microsoft Unity
Part Two: Castle Windsor
Part Three: Ninject
Part Four: StructureMap
Part Five: AutoFac
Part Six: Spring.Net
Part Seven: MEF
Funq
LightCore
Spring.Net CodeConfig
An introduction to Autoregistration
Autoregistration in Castle Windsor
Autoregistration in Microsoft Unity
Autoregistration in Ninject
Autoregistration in StructureMap

Autoregistration in Autofac

If there's an IoC container that you feel that I should include, let me know.

The most notable common variations among the containers are:

Automatic resolution

Does the container Automatically resolve. i.e. if you ask it to make an instance of class Foo of which it has no prior knowledge, will it inspect it for a public constructor and attempt to call that, recursing onto all the needed parameters, or will it simply stop and say "Class Foo is not registered". This less of technical limitiation than an ideological difference. Some containers are designed believing that autoregistering is a convenience, some believing that it is a danger and that you should be more rigourous.

With Automatic resolution, you may need to add fewer type mappings to your container, but without it you can never be surprised by the container working on a type that you did not intend it to work on. I have a slight preference for  Automatic registration, but both work well.

 Of course, no container will automatically resolve an interface type - the concrete class that implements this will have to be specified. However some containers can be instructed to scan assemblies and register all types as implementations of the interfaces that they have.

Castle Windsor notably does not believe in automatic resolution, but it does do automatic registration - you can ask it to scan assemblies and register everything in them, or just the types that meet certain criteria. It's quite flexible, and I am starting to look it at it in later posts.

Singleton or transient instances

Do you get a new instance (sometimes called a "transient" instance) each time by default, or a singleton. All containers can be configured to dispense singletons or transient objects (and generally other options, like one instance per resolution, one instance per thread or to allow you to plug in custom lifetime schemas), but they differ on which lifespan is the default.  I feel that a new instance is usually a more appropriate default, but this also depends on the exact usage scenario.

Mutable containers

Is the container mutable. Some containers make you go through two different phases - first setting up the container by registering types, then locking before using it to resolve object instances. Others let you interleave these as you see fit, though in practice you will generally register types upfront. Again it's about convenience versus rigour.

The Code

First I must set up the groundwork - what the demo tries to do, and how this is done without any IoC. The first version of the demo made a three-layer object hierarchy, which  pretended to be services and mocks. That was boring in such a small demo, so for the reworked version, I modelled a sweet shop that dispenses jellybeans.

It is worth remembering that any sample small enough to demo IoC is too small to need IoC. IoC is extremely useful. However, it tends to look overcomplex in toy problems. I assure you, in larger systems, it cuts down on the overall complexity by decoupling classes.

The full code is maintained in a repository on Github. With that in mind, here is the sample code:

Definitions.cs

namespace IoCComparison
{
    public enum Jellybean
    {
        Vanilla,
        Strawberry,
        Lemon,
        Orange,
        Aniseed,
        Cocoa
    };

    public interface IJellybeanDispenser
    {
        Jellybean DispenseJellybean();
    }
}

Definitions.cs contains an enum that defines the type of Jellybean, and an IJellybeanDispenser interface for a service that dispenses Jellybeans. If you need an example that applies to your day job, think it as a data or service layer - Web or Database-backed service that retrieves results from a remote source.

Classes.cs

namespace IoCComparison
{
    public class VanillaJellybeanDispenser : IJellybeanDispenser
    {
        public Jellybean DispenseJellybean()
        {
            return Jellybean.Vanilla;
        }
    }

    public class StrawberryJellybeanDispenser : IJellybeanDispenser
    {
        public Jellybean DispenseJellybean()
        {
            return Jellybean.Strawberry;
        }
    }

    public class SweetVendingMachine
    {
        public IJellybeanDispenser JellybeanDispenser { get; private set; }

        public SweetVendingMachine(IJellybeanDispenser jellybeanDispenser)
        {
            this.JellybeanDispenser = jellybeanDispenser;
        }
    }

    public class SweetShop
    {
        public SweetVendingMachine SweetVendingMachine { get; private set; }

        public SweetShop(SweetVendingMachine sweetVendingMachine)
        {
            this.SweetVendingMachine = sweetVendingMachine;
        }

        public virtual Jellybean DispenseJellyBean()
        {
            return this.SweetVendingMachine.JellybeanDispenser.DispenseJellybean();
        }
    }
}

Classes.cs contains two implementations of IJellybeanDispenser - one that gives vanilla beans and one that gives strawberry beans. In the original sample, these were named a "real" and "mock" service, but since this is just a demo, both are pretty much mock.

In order to get some levels of hierarchy, the Jellybean dispenser is owned by a SweetVendingMachine, which is owned by a SweetShop. These two classes do not have interfaces, so the IoC container will have to handle types with and without interfaces.

Advanced.cs

namespace IoCComparison
{
    ///  /// This root object has no contained objects /// Used to test container mappings on the root object /// 
    public class AniseedSweetShop : SweetShop
    {
        public AniseedSweetShop()
            : base(null)
        { }

        public override Jellybean DispenseJellyBean()
        {
            return Jellybean.Aniseed;
        }
    }

    ///  /// Used to test constructor params that are a simple type - e.g. int, string, enum /// 
    public class AnyJellybeanDispenser : IJellybeanDispenser
    {
        private readonly Jellybean jellybean;

        public AnyJellybeanDispenser(Jellybean jellybean)
        {
            this.jellybean = jellybean;
        }

        public Jellybean DispenseJellybean()
        {
            return jellybean;
        }
    }

}

The file Advanced.cs contains a few alternate scenarios that the IoC container will be asked to handle. There is an Aniseed sweet shop that does not have the other objects inside it, and a parameterised AnyJellybeanDispenser, which can dispense any kind of Jellybean, but must be created with an enum value given to its constructor. This will test the IoC container's ability to inject simple types like strings, ints and enums. You can imagine this being useful in your day job, for instance, to pass a connection string or url into a service layer which is backed by a database or web service. It will be used for prebuilt instances and factory methods.

Baseline tests

NoIocTest.cs sets a baseline - of how to do the tests using these classes and constructor injection, but no IoC container at all. This shows another good design side-effect. The classes are Plain Old C# Objects or POCOs. they do not inherit from a special base class, implement a special service, and are not marked with special attributes. They don't need to use anything from the IoC container. They are DI-friendly but container-agnostic. They are designed for constructor injection, but using an IoC container on top of that is optional.


The tests are in summary:
•   Verify that a sweet shop can be made that dispenses Vanilla jelly beans, using the contained objects.
•    Verify that a sweet shop can be made that dispenses Strawberry jelly beans, using the contained objects.
•   Verify that a sweet shops can be constructed, configured with a new instance of all the contained objects each time
•    Verify that a sweet shops can be constructed, configured with the same instance of the jellybean dispenser used between them.
•    Verify that a sweet shop can be made that dispenses Aniseed jelly beans, using the AniseedSweetShop root object.
•   Verify that a sweet shop can be made that dispenses Lemon jelly beans, using the AnyJellybeanDispenser contained object configured to dispense Lemon jellybeans
•   Verify that a pre-built object can be used during construction. This is trivial in the no-ioc case, but becomes more interesting when passing the object into the IoC container for later use as a singleton.
•    Verify that a factory method can be called during construction. This is trivial in the no-ioc case, but becomes more interesting when passing the function into the IoC container for later use in constructing instances.

namespace IoCComparison
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using NUnit.Framework;

    [TestFixture]
    public class NoIoCTest
    {
        [Test]
        public void CanMakeSweetShopWithVanillaJellybeans()
        {
            SweetShop sweetShop = new SweetShop(new SweetVendingMachine(new VanillaJellybeanDispenser()));

            Assert.AreEqual(Jellybean.Vanilla, sweetShop.DispenseJellyBean());
        }

        [Test]
        public void CanMakeSweetShopWithStrawberryJellybeans()
        {
            SweetShop sweetShop = new SweetShop(new SweetVendingMachine(new StrawberryJellybeanDispenser()));

            Assert.AreEqual(Jellybean.Strawberry, sweetShop.DispenseJellyBean());
        }

        [Test]
        public void JellybeanDispenserHasNewInstanceEachTime()
        {
            SweetShop sweetShop = new SweetShop(new SweetVendingMachine(new StrawberryJellybeanDispenser()));
            SweetShop sweetShop2 = new SweetShop(new SweetVendingMachine(new StrawberryJellybeanDispenser()));

            Assert.IsFalse(ReferenceEquals(sweetShop, sweetShop2), "Root objects are equal");
            Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine, sweetShop2.SweetVendingMachine), "Contained objects are equal");
            Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine.JellybeanDispenser, sweetShop2.SweetVendingMachine.JellybeanDispenser), "services are equal");
        }

        [Test]
        public void CanMakeSingletonJellybeanDispenser()
        {
            IJellybeanDispenser jellybeanDispenser = new StrawberryJellybeanDispenser();
            SweetShop sweetShop = new SweetShop(new SweetVendingMachine(jellybeanDispenser));
            SweetShop sweetShop2 = new SweetShop(new SweetVendingMachine(jellybeanDispenser));

            Assert.IsFalse(ReferenceEquals(sweetShop, sweetShop2), "Root objects are equal");
            Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine, sweetShop2.SweetVendingMachine), "Contained objects are equal");

            // should be same service
            Assert.IsTrue(ReferenceEquals(sweetShop.SweetVendingMachine.JellybeanDispenser, sweetShop2.SweetVendingMachine.JellybeanDispenser), "services are not equal");
        }

        [Test]
        public void CanMakeAniseedRootObject()
        {
            SweetShop sweetShop = new AniseedSweetShop();

            Assert.AreEqual(Jellybean.Aniseed, sweetShop.DispenseJellyBean());
        }

        [Test]
        public void CanUseAnyJellybeanDispenser()
        {
            SweetShop sweetShop = new SweetShop(new SweetVendingMachine(new AnyJellybeanDispenser(Jellybean.Lemon)));

            Assert.AreEqual(Jellybean.Lemon, sweetShop.DispenseJellyBean());
        }

        [Test]
        public void CanUseConstructedObject()
        {
            IJellybeanDispenser dispenser = new AnyJellybeanDispenser(Jellybean.Cocoa);
            SweetShop sweetShop = new SweetShop(new SweetVendingMachine(dispenser));

            Assert.AreEqual(Jellybean.Cocoa, sweetShop.DispenseJellyBean());
        }

        [Test]
        public void CanUseFactoryMethod()
        {
            Func<IJellybeanDispenser> factoryFunc = () => new AnyJellybeanDispenser(Jellybean.Orange);
            SweetShop sweetShop = new SweetShop(new SweetVendingMachine(factoryFunc()));

            Assert.AreEqual(Jellybean.Orange, sweetShop.DispenseJellyBean());
        }

        [Test]
        public void CanRegisterMultipleDispensers()
        {
            IEnumerable<IJellybeanDispenser> dispensers = new List<IJellybeanDispenser>
                {
                    new VanillaJellybeanDispenser(),
                    new StrawberryJellybeanDispenser()
                };

            Assert.IsNotNull(dispensers);
            Assert.AreEqual(2, dispensers.Count());
        }
    }
}

2 Comments

Add a Comment