Comparing .Net IoC containers, part two: Castle Windsor

Tags: IocContainer, IocComparison, DependencyInjection, DI, IoC, code, castle, windsor

Castle Windsor is perhaps the most well-known .Net IoC container. It is well-established and flexible.

I would call it "the open source leader", but they're all open source - even Microsoft Unity source is up on Codeplex too. Windsor and Unity are quite similar to each other. Windsor uses the syntax AddComponent<T, U> where unity uses RegisterType<T,U> to add a type mapping. Both use a method called Resolve() to create an instance.

Windsor has not one but two syntaxes for adding types to the container. There's the simple syntax, container.AddComponent<T, U> and a fluent interface for registering a type that goes

container.Register(
    Component.For<SweetShop>(),
    Component.For<SweetVendingMachine>(),
    Component.For<IJellybeanDispenser>().
        ImplementedBy<StrawberryJellybeanDispenser>());

Generally you need to use the fluent syntax to access any of the advanced options.
Windsor certainly has more syntax than Unity. It's an open-source project with lots of versions and contributors.

Other differences immediately apparent are that Windsor insists that all types to be created must be registered, even if the mapping is trival This is not an oversight or missing feature but a deliberate design decision. You can plug in code to work around this (see coments on StackOverflow), but it's just as easy to work with it.  One feature that Windsor users love is to "Auto  register" all types in a given assembly in one statement.
The default lifestyle in Windsor is Singleton, but this can be controlled with the LifeStyleType enum, e.g.

   container.AddComponentLifeStyle<SweetShop>(LifestyleType.Transient);
or
   container.AddComponentLifeStyle<IJellybeanDispenser, VanillaJellybeanDispenser>(LifestyleType.Singleton);

The syntax for adding a constructor parameter in Windsor is quite straightforward in the 2.5.3 version:

container.Register(Component.For<IJellybeanDispenser>()
   .ImplementedBy<AnyJellybeanDispenser>()
   .DependsOn(new {Jellybean = Jellybean.Lemon}));

Factory methods in Windsor are a bit more verbose, but easy enough once you find the syntax:

WindsorContainer container = new WindsorContainer();
container.AddFacility<FactorySupportFacility>();
container.Register(
  Component.For<IJellybeanDispenser>()
  .UsingFactoryMethod((c, t) => new AnyJellybeanDispenser(Jellybean.Orange)));

The FactorySupportFacility has to be added to the container, or else it just fails. The Container.Register(Component.For syntax must be used instead of AddComponent. Nevertheless, it can be done in one line of code and a few minutes googling.

Another issue for IoC containers is lifetime and garbage collection of contained objects. When you ask an IoC container to have a singleton instance if IJellyBeanDispenser, this means that multiple calls to container.Resolve will all be supplied the same instance of IJellyBeanDispenser. Typically it will create an instance of the singleton type the first time that it needs one, and keep the instance around for later use. The container can’t let go of this instance in case it’s needed again, so it will stay in memory as long as the container does. So if the container is long-lived, it may carry a fair amount of memory with it.  You may or may not see this as a problem, and some containers address it in various ways.

Windsor will apparently even hold on to Transient objects unless you do the following:

IKernel kernel = new DefaultKernel();
// Set release policy.
kernel.ReleasePolicy = new NoTrackingReleasePolicy();

Notice that because the policy is an object; you can supply your own custom policy by making a class the subclasses the relevant base class.
Windsor can do lots of advanced things, see Mike Hadlow's "10 advanced Windsor tricks" for more. I have also blogged further about autoregistration in Castle Windsor.

Here is the complete set of tests on Windsor:

Windsor: The code

The code is maintained in a repository in GitHub. Here's the complete code for the tests:

namespace IoCComparison
{
    using System.Collections.Generic;
    using System.Linq;
    using Castle.Core;
    using Castle.Facilities.FactorySupport;
    using Castle.MicroKernel.Registration;
    using Castle.Windsor;
    using NUnit.Framework;

    [TestFixture]
    public class WindsorTest
    {
        [Test]
        public void CanMakeSweetShopWithVanillaJellybeansWithOldSyntax()
        {
            WindsorContainer container = new WindsorContainer();
            // disable the deprecated! unclean! warning that we know the "AddComponent" method will emit 
            #pragma warning disable 612,618
            container.AddComponent<SweetShop>();
            container.AddComponent<SweetVendingMachine>();
            container.AddComponent<IJellybeanDispenser, VanillaJellybeanDispenser>();
            #pragma warning restore 612,618

            SweetShop sweetShop = container.Resolve<SweetShop>();

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

        [Test]
        public void CanMakeSweetShopWithVanillaJellybeans()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(
                Component.For<SweetShop>(),
                Component.For<SweetVendingMachine>(),
                Component.For<IJellybeanDispenser>().ImplementedBy<VanillaJellybeanDispenser>());

            SweetShop sweetShop = container.Resolve<SweetShop>();

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

        [Test]
        public void CanMakeSweetShopWithStrawberryJellybeans()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(
                Component.For<SweetShop>(),
                Component.For<SweetVendingMachine>(),
                Component.For<IJellybeanDispenser>().ImplementedBy<StrawberryJellybeanDispenser>());

            SweetShop sweetShop = container.Resolve<SweetShop>();

            Assert.AreEqual(Jellybean.Strawberry, sweetShop.DispenseJellyBean());
        }
        
        [Test]
        public void JellybeanDispenserHasNewInstanceEachTime()
        {
            WindsorContainer container = new WindsorContainer();
            // windsor seems to default to retained singletons, 
            // so we add LifestyleType.Transient to make them new each time
            container.Register(
                Component.For<SweetShop>().LifeStyle.Is(LifestyleType.Transient),
                Component.For<SweetVendingMachine>().LifeStyle.Is(LifestyleType.Transient),
                Component.For<IJellybeanDispenser>().ImplementedBy<VanillaJellybeanDispenser>()
                    .LifeStyle.Is(LifestyleType.Transient));


            SweetShop sweetShop = container.Resolve<SweetShop>();
            SweetShop sweetShop2 = container.Resolve<SweetShop>();

            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()
        {
            WindsorContainer container = new WindsorContainer();

            container.Register(
                Component.For<SweetShop>().LifeStyle.Is(LifestyleType.Transient),
                Component.For<SweetVendingMachine>().LifeStyle.Is(LifestyleType.Transient),
                Component.For<IJellybeanDispenser>().ImplementedBy<VanillaJellybeanDispenser>()
                    .LifeStyle.Is(LifestyleType.Singleton));

            SweetShop sweetShop = container.Resolve<SweetShop>();
            SweetShop sweetShop2 = container.Resolve<SweetShop>();

            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()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(Component.For<SweetShop>().ImplementedBy<AniseedSweetShop>());

            SweetShop sweetShop = container.Resolve<SweetShop>();

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

        [Test]
        public void CanUseAnyJellybeanDispenser()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(
                Component.For<SweetShop>(),
                Component.For<SweetVendingMachine>(),
                Component.For<IJellybeanDispenser>()
                    .ImplementedBy<AnyJellybeanDispenser>()
                    .DependsOn(new {Jellybean = Jellybean.Lemon}));
                
            SweetShop sweetShop = container.Resolve<SweetShop>();

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

        [Test]
        public void CanUseConstructedObject()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(
                Component.For<SweetShop>(),
                Component.For<SweetVendingMachine>(),
                Component.For<IJellybeanDispenser>().Instance(new AnyJellybeanDispenser(Jellybean.Cocoa)));

            SweetShop sweetShop = container.Resolve<SweetShop>();

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

        [Test]
        public void CanUseObjectFactory()
        {
            WindsorContainer container = new WindsorContainer();
            container.AddFacility<FactorySupportFacility>();
            container.Register(
                Component.For<IJellybeanDispenser>()
                    .UsingFactoryMethod((c, t) => new AnyJellybeanDispenser(Jellybean.Orange)),
                Component.For<SweetShop>(),
                Component.For<SweetVendingMachine>());

            SweetShop sweetShop = container.Resolve<SweetShop>();

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

        [Test]
        public void CanRegisterMultipleDispensers()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(
                Component.For<IJellybeanDispenser>().ImplementedBy<VanillaJellybeanDispenser>(),
                Component.For<IJellybeanDispenser>().ImplementedBy<StrawberryJellybeanDispenser>());

            IEnumerable<IJellybeanDispenser> dispensers = container.ResolveAll<IJellybeanDispenser>();

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