Maximilian Csuk
Maximilian Csuk

Reputation: 594

Castle Windsor (or other DI) - creating object based on parameter

I am pretty new to the whole DI/IoC thing, so bear with me...

I have this kind of setting:

interface IA
interface IB
interface IC
abstract class A : IA
class B : A, IB
class C : A, IC

interface IX
interface IY
interface IZ
abstract class X : IX
class Y : X, IY
class Z : X, IZ

B and C's constructors look like this:

public B(IY y);
public C(IZ z);

Now I want either B or C to be constructed, based on an already created instance of either Y or Z. Like this:

IX x = new ...; // either Y or Z, determined at runtime
// lots of code
IA a = fancyfuncoftruth<IA>(x); // creates an instance of either B or C, depending on x

Is something like this possible?

To give you a bit of background: I am trying to combine WPF's treeview, the MVVM pattern and DI.

Thanks for your time.

Upvotes: 1

Views: 1254

Answers (2)

Maximilian Csuk
Maximilian Csuk

Reputation: 594

Pfeh, I found an answer. Probably not the best, but at least something to start.

Have a look at the following complete example: (I used NInject for this):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject.Core;
using Ninject.Core.Parameters;
using Ninject.Conditions;

namespace IoCTest01
{
    interface IA { }
    interface IB : IA { }
    interface IC : IA { }

    abstract class A : IA { }

    class B : A, IB
    {
        public B(IY x)
        {
            Console.WriteLine("Constructor for B called!");
        }
    }

    class C : A, IC
    {
        public C(IZ x)
        {
            Console.WriteLine("Constructor for C called!");
        }
    }

    interface IX { }
    interface IY : IX { }
    interface IZ : IX { }

    abstract class X : IX { }

    class Y : X, IY
    {
    }
    class Z : X, IZ
    {
    }

    class TestModule : StandardModule
    {
        public override void Load()
        {
            Bind<IY>().To<Y>();
            Bind<IZ>().To<Z>();

            Bind<IA>().To<B>().Only(When.Context.Parameter<ConstructorArgumentParameter>("x").Matches(
                e =>
                {
                    return e.Value.GetType().Equals(typeof(Y));
                }));
            Bind<IA>().To<C>().Only(When.Context.Parameter<ConstructorArgumentParameter>("x").Matches(
                e =>
                {
                    return e.Value.GetType().Equals(typeof(Z));
                }));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IKernel kernel = new StandardKernel(new TestModule());

            IX x1 = kernel.Get<IY>();
            IX x2 = kernel.Get<IZ>();

            kernel.Dispose();

            // lots of code

            kernel = new StandardKernel(new TestModule());

            var parameters = new ParameterCollection();
            parameters.Add<ConstructorArgumentParameter>(new ConstructorArgumentParameter("x", x1));
            kernel.Get<IA>(parameters);

            parameters = new ParameterCollection();
            parameters.Add<ConstructorArgumentParameter>(new ConstructorArgumentParameter("x", x2));
            kernel.Get<IA>(parameters);
        }
    }
}

When it is run, it displays:

Constructor for B called!
Constructor for C called!

First, I had to manually inject the already instantiated objects x1 and x2 into the kernel.Get-calls. This looked like it was enough to let NInject resolve the right entity, but as soon as I added a second Binding for IA, it complained about multiple default bindings for IA. So I had to do some contextual binding:

Bind<IA>().To<B>().Only(When.Context.Parameter<ConstructorArgumentParameter>("x").Matches(
                    e =>
                    {
                        return e.Value.GetType().Equals(typeof(Y));
                    }));

This checks if the parameter x is of type Y. If yes, this binding is used.

Again, while this is a solution, it's probably far from being optimal. I wished NInject could resolve the right type (B or C) to instantiate from the dynamic type (Y or Z) of the parameter given (x).

Ah well. :-)

Does anybody have a better solution?

To Mark: does the code explain the problem better? The Main-method should give you an overview.

Upvotes: 0

Mark Seemann
Mark Seemann

Reputation: 233317

I'm not quite sure if I understand what you are looking for, but it seems to me that you are asking whether there's any functionality that can correctly resolve IA based on a specific value of IX (x).

You would be best off implementing this using an Abstract Factory that maps instances of IX to IA.

I'd personally implement this as a custom Abstract Factory, but you can also use UsingFactory or UsingFactoryMethod of Castle Windsor:

IX x = new ...;

var container = new WindsorContainer();
container.AddFacility<FactorySupportFacility>();
container.Register(Component.For<IA>().UsingFactoryMethod(k =>
    {
        // Do fancy stuff with x here
        // This example just shows that x can be referenced
        // in the closure, but I'm not using it...
        if (x == null)
        {
        }
        return k.Resolve<B>();
    }));
container.Register(Component.For<B>());
container.Register(Component.For<IY>().ImplementedBy<Y>());

var result = container.Resolve<IA>();

Upvotes: 2

Related Questions