I need to apologise up-front for the verbosity of this post. I have tried 
to include a minimal but complete example but it is difficult due to the 
number of components required to illustrate the problem.

Some background. I am using a composite pattern inside my application. For 
this example, I am illustrating that composite as an `ILogger` which is 
merely a wrapper for other types of `ILogger` instances. In this case, 
a`CompositeLogger`which is provided with an 
`ILogger[]` comprising of a `ConsoleLogger` and a `TraceLogger`. When using 
the `ArrayResolver` this pattern works beautifully, resulting in three 
`ILogger` instances being constructed.

In my example, I have a `StartableThing` class which requires an `ILogger`in 
its constructor. The 
`StartableThing` class has `Start` and `Stop` methods. If I use the 
`StartableFacility` to call these methods (via `StartUsingMethod(x => ...)`and 
`StopUsingMethod(x 
=> ...)`) then something strange happens; the `CompositeLogger` class is 
instantiated twice. The second of these instances is passed to the first 
instance along with the expected `ConsoleLogger` and `TraceLogger`. Both 
`CompositeLogger` instances reference the same `ConsoleLogger` and 
`TraceLogger`.

All components in this example are intended to have the Singletonlifestyle. 
This behaviour is also observed with the 
`CollectionResolver`.

The code below is a complete example. You'll need to create a new class 
library project for it and run the `Install-Package` comments in the 
Package Manager Console, of course, but then it should execute in your 
favourite test runner.

-- 
You received this message because you are subscribed to the Google Groups 
"Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/groups/opt_out.
?using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Castle.Facilities.Startable;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.Resolvers.SpecializedResolvers;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using NUnit.Framework;

/*
Install-Package -Id Castle.Core -Version 3.2.2
Install-Package -Id Castle.Windsor -Version 3.2.1
Install-Package -Id NUnit -Version 2.6.3
*/

namespace Spikes.WindsorCollectionTests
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void CORRECT_BEHAVIOUR__When_startable_component_IS_NOT_STARTED_only_one_composite_logger_is_created()
        {
            var container = new WindsorContainer();
            container.Install(new MainInstaller(), new NonStartableInstaller());
            var startable = container.Resolve<StartableThing>();
            startable.Start();

            var logger = container.Resolve<ILogger>();
            var tree = buildTree(logger);
            Console.WriteLine(tree);

            Assert.That(tree, Is.EqualTo(@"
CompositeLogger
    ConsoleLogger
    TraceLogger
"));
        }

        [Test]
        public void INCORRECT_BEHAVIOUR__When_startable_component_IS_STARTED_the_singleton_composite_logger_is_created_twice()
        {
            var container = new WindsorContainer();
            container.Install(new MainInstaller(), new StartableInstaller());

            var logger = (CompositeLogger)container.Resolve<ILogger>();
            var tree = buildTree(logger);
            Console.WriteLine(tree);

            Assert.That(tree, Is.EqualTo(@"
CompositeLogger
    CompositeLogger
        ConsoleLogger
        TraceLogger
    ConsoleLogger
    TraceLogger
"));

            // The other loggers in the second composite logger are the exact same
            // objects that are held by the first composite logger - i.e., they
            // are only created once.
            var secondCompositeLogger = (CompositeLogger)logger[0];
            Assert.That(logger[1], Is.SameAs(secondCompositeLogger[0]));
            Assert.That(logger[2], Is.SameAs(secondCompositeLogger[1]));
        }

        private string buildTree(object o, string indent = "")
        {
            var sb = new StringBuilder();
            if (string.IsNullOrEmpty(indent)) sb.AppendLine(); else sb.Append(indent);
            sb.AppendLine(o.GetType().Name);
            var oAsEnumerable = o as IEnumerable;
            if (null != oAsEnumerable)
            {
                var newIndent = indent + "    ";
                foreach (var item in oAsEnumerable)
                {
                    sb.Append(buildTree(item, newIndent));
                }
            }
            return sb.ToString();
        }
    }

    public class StartableThing
    {
        private readonly ILogger _logger;

        public StartableThing(ILogger logger)
        {
            if (logger == null) throw new ArgumentNullException("logger");
            _logger = logger;
        }

        public void Start()
        {
            _logger.Log("The StartableThing is starting...");
        }

        public void Stop()
        {
            _logger.Log("The StartableThing is stopping...");
        }
    }

    public interface ILogger
    {
        void Log(string message);
    }

    public class CompositeLogger : List<ILogger>, ILogger
    {
        public CompositeLogger(ILogger[] loggers) : base(loggers)
        { }

        public void Log(string message)
        {
            ForEach(x => x.Log(message));
        }
    }

    public class ConsoleLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine("CONSOLE : " + message);
        }
    }

    public class TraceLogger : ILogger
    {
        public void Log(string message)
        {
            Trace.WriteLine("TRACE   : " + message);
        }
    }

    public class MainInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Kernel.Resolver.AddSubResolver(new ArrayResolver(container.Kernel));
            container.AddFacility<StartableFacility>(f => f.DeferredStart());
            container.Register(
                Component.For<ILogger>().ImplementedBy< CompositeLogger >(),
                Component.For<ILogger>().ImplementedBy< ConsoleLogger   >(),
                Component.For<ILogger>().ImplementedBy< TraceLogger     >());
        }
    }

    public class NonStartableInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For<StartableThing>());
        }
    }

    public class StartableInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For<StartableThing>()
                .StartUsingMethod(x => x.Start)
                .StopUsingMethod(x => x.Stop));
        }
    }
}

Reply via email to