Overriding generic component’s resolution in Castle Windsor

Few months ago, a user asked the following question on the Castle users discussion group. A friend asked me about the same thing today, so I thought I’d blog this so that’s easier to find than the discussion group thread. Anyway, here’s the question:

say I have the following types

public interface ISometype<T> {} 

public class SomeTypeImpl<T>:ISometype<T> {} 

public class SomeSpecificTypeImpl<T>:ISometype<T> where T: ISomeSpecificSpecifier {} 

public interface ISomeSpecificSpecifier { } 

Is there a way to register ISomeType<> in the container so that for the general case it uses SomeTypeImpl<> but for cases where T is ISomeSpecificSpecifier it could use SomeSpecificTypeImpl<>?

Solution take one – the explicit version

The solution may be similar to the following sketchy code.

class Program

{

    static void Main(string[] args)

    {

        var container = new WindsorContainer();

        container.Register(Component.For(typeof(ISometype<>)).ImplementedBy(typeof(SomeTypeImpl<>)),

                           Component.For(typeof(ISometype<>)).ImplementedBy(typeof(SomeSpecificTypeImpl<>))

                               .PreferredFor<ISomeSpecificSpecifier>());

        var selector = new Selector();

        container.Kernel.AddHandlerSelector(selector);

 

        var sometype = container.Resolve<ISometype<string>>();

        Debug.Assert(sometype.GetType() == typeof(SomeTypeImpl<string>));

 

        var sometype2 = container.Resolve<ISometype<Specifier>>();

        Debug.Assert(sometype2.GetType() == typeof(SomeSpecificTypeImpl<Specifier>));

    }

}

 

public static class Preferred

{

    public static ComponentRegistration<object> PreferredFor<TClosingGenericType>(this ComponentRegistration<object> registration)

    {

        return registration.ExtendedProperties(new Preference(typeof(TClosingGenericType)));

    }

}

 

internal class Preference

{

    public static readonly string Name = "PreferredForClosingType";

    public Type PreferredForClosingType { get; set; }

 

    public Preference(Type type)

    {

        this.PreferredForClosingType = type;

    }

}

 

public class Selector : IHandlerSelector

{

    public bool HasOpinionAbout(string key, Type service)

    {

        // that's about as much as we can say at this point...

        return service.IsGenericType && service.GetGenericArguments().Length == 1;

    }

 

    public IHandler SelectHandler(string key, Type service, IHandler[] handlers)

    {

        var @default = handlers.FirstOrDefault(h => MatchHandler(service, h));

        return @default ?? handlers.First();

    }

 

    private bool MatchHandler(Type service, IHandler handler)

    {

        if (handler.ComponentModel.ExtendedProperties.Contains(Preference.Name) == false)

            return false;

        var closingTypeRequired = (Type)handler.ComponentModel.ExtendedProperties[Preference.Name];

        var closingTypeActual = service.GetGenericArguments().Single();

        return closingTypeRequired.IsAssignableFrom(closingTypeActual);

    }

}

This version contains an explicit extension to the component registration API, to specify which component you prefer in which case. It also uses and IHandlerSelector implementation to do the actual work during resolution. It is possible however to not extend the API, and use the information we already have

Solution take two – the implicit version

class Program

{

    static void Main(string[] args)

    {

        var container = new WindsorContainer();

        container.Register(Component.For(typeof(ISometype<>)).ImplementedBy(typeof(SomeTypeImpl<>)),

                           Component.For(typeof(ISometype<>)).ImplementedBy(typeof(SomeSpecificTypeImpl<>)));

        var selector = new Selector();

        container.Kernel.AddHandlerSelector(selector);

 

        var sometype = container.Resolve<ISometype<string>>();

        Debug.Assert(sometype.GetType() == typeof(SomeTypeImpl<string>));

 

        var sometype2 = container.Resolve<ISometype<Specifier>>();

        Debug.Assert(sometype2.GetType() == typeof(SomeSpecificTypeImpl<Specifier>));

    }

}

 

public class Selector : IHandlerSelector

{

    public bool HasOpinionAbout(string key, Type service)

    {

        // that's about as much as we can say at this point...

        return service.IsGenericType && service.GetGenericArguments().Length == 1;

    }

 

    public IHandler SelectHandler(string key, Type service, IHandler[] handlers)

    {

        return handlers.FirstOrDefault(h => MatchHandler(service, h));

 

    }

 

    private bool MatchHandler(Type service, IHandler handler)

    {

        var closingTypeRequired = handler.ComponentModel.Implementation.GetGenericArguments()

            .Single().GetGenericParameterConstraints().SingleOrDefault();

        if (closingTypeRequired == null)

            return false;

        var closingTypeActual = service.GetGenericArguments().Single();

        return closingTypeRequired.IsAssignableFrom(closingTypeActual);

    }

}

This version relies on the information provided in component’s generic type constraints. The Registration API is not extended, and we just do a little bit more work in the selector. Is this version better? It depends. The explicit version is… well – explicit about what it does, and for which components. The implicit one works on its own and if you’re not careful you may end up chasing very strange bugs in your code. That said, if you do go for the second option, be sure to at least add some diagnostics logging to it, so that you can see what it does when you’re not watching.

Technorati Tags: