Customizing Autofac's component resolution / Issue with generic co-/contravariance

后端 未结 3 848
小蘑菇
小蘑菇 2020-12-25 14:17

First, sorry for the vague question title. I couldn\'t come up with a more precise one.

Given these types:

                                                  


        
相关标签:
3条回答
  • 2020-12-25 14:46

    What you are asking is not possible without own coding. Basically, you are asking the following: If the type I tried to resolve isn't found, return another type that can be converted to it, e.g. if you try to resolve IEnumerable return a type that is registered for ICollection. This is not supported. One simple solution would be the following: Register FooCommandHandler as a handler for ICommandHandler<SpecialFooCommand>. For this to work, ICommandHandler needs to be contravariant:

    interface ICommand { }
    
    class FooCommand : ICommand { }
    
    class SpecialFooCommand : FooCommand { }
    
    interface ICommandHandler<in T> where T : ICommand
    {
        void Handle(T command);
    }
    
    class FooCommandHandler : ICommandHandler<FooCommand>
    {
        public void Handle(FooCommand command)
        {
            // ...
        }
    }
    
    var builder = new ContainerBuilder();
    builder.RegisterType<FooCommandHandler>()
           .As<ICommandHandler<SpecialFooCommand>>()
           .As<ICommandHandler<FooCommand>>();
    var container = builder.Build();
    var fooCommand = new FooCommand();
    var specialCommand = new SpecialFooCommand();
    container.Resolve<ICommandHandler<FooCommand>>().Handle(fooCommand);
    container.Resolve<ICommandHandler<FooCommand>>().Handle(specialCommand);
    container.Resolve<ICommandHandler<SpecialFooCommand>>().Handle(specialCommand);
    

    BTW: The way you are using the container, you apply the Service locator anti-pattern. This should be avoided.

    0 讨论(0)
  • 2020-12-25 14:50

    Not really a fair answer, as I've extended Autofac since you posted the question... :)

    As per Daniel's answer, you'll need to add the in modifier to the TCommand parameter of ICommandHandler:

    interface ICommandHandler<in TCommand>
    {
        void Handle(TCommand command);
    }
    

    Autofac 2.5.2 now includes an IRegistrationSource to enable contravariant Resolve() operations:

    using Autofac.Features.Variance;
    
    var builder = new ContainerBuilder();
    builder.RegisterSource(new ContravariantRegistrationSource());
    

    With this source registered, services represented by a generic interface with a single in parameter will be looked up taking variant implementations into account:

    builder.RegisterType<FooCommandHandler>()
       .As<ICommandHandler<FooCommand>>();
    
    var container = builder.Build();
    container.Resolve<ICommandHandler<FooCommand>>();
    container.Resolve<ICommandHandler<SpecialFooCommand>>();
    

    Both calls to Resolve() will successfully retrieve the FooCommandHandler.

    If you can't upgrade to the latest Autofac package, grab the ContravariantRegistrationSource from http://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs - it should compile against any recent Autofac build.

    0 讨论(0)
  • 2020-12-25 14:59

    I like to add an alternative approach, which also works without C# 4.0 variance support.

    You can create a special decorator / wrapper that allows executing a command as its base type:

    public class VarianceHandler<TSubCommand, TBaseCommand> 
        : ICommandHandler<TSubCommand>
        where TSubCommand : TBaseCommand
    {
        private readonly ICommandHandler<TBaseCommand> handler;
    
        public VarianceHandler(ICommandHandler<TBaseCommand> handler)
        {
            this.handler = handler;
        }
    
        public void Handle(TSubCommand command)
        {
            this.handler.Handle(command);
        }
    }
    

    With this in place, the following line of code would allow you to handle SpecialFooCommand as its base type:

    builder.Register<FooCommandHandler>()
        .As<ICommandHandler<FooCommand>>();
    
    builder.Register<VarianceHandler<SpecialFooCommand, FooCommand>>()
        .As<ICommandHandler<SpecialFooCommand>>();
    

    Note that the use of such VarianceHandler works for most DI containers.

    0 讨论(0)
提交回复
热议问题