6

I have the following test

[TestFixture]
public class Test
{
    public interface IMy { }

    class MyClass : IMy { }

    class MyClass2 : IMy { }

    [Test]
    public static void Go()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<MyClass>().AsImplementedInterfaces();
        builder.RegisterType<MyClass2>().AsImplementedInterfaces();
        var container = builder.Build();
        var resolved = container.Resolve<IMy>();
        Console.WriteLine(resolved);
    }
}

Why it does not throw exception when implementations are obviously in conflict ? And how to make it throw exception if such a conflict found ?

UPDATE Solution with registration checking is almost Ok, but there is simple situation when it fails:

[TestFixture]
public class Test
{
    public interface IPlugin
    {
    }

    public interface IMy
    {

    }

    class MyClass : IMy, IPlugin
    {
        public void Dispose()
        {
        }
    }

    class MyClass2 : IPlugin
    {
        public void Dispose()
        {
        }
    }

    public class SingleRegistrationModule : Module
    {
        protected override void AttachToComponentRegistration(
            IComponentRegistry componentRegistry, 
            IComponentRegistration registration)
        {
            foreach (var service in registration.Services)
            {
                var registrations = componentRegistry.RegistrationsFor(service);
                if (registrations.Count() > 1)
                {
                    throw new Exception(
                        "Can't register '{registration.Activator.LimitType}' as '{service}'" + 
                        " because '{registrations.First().Activator.LimitType}' is already registered");
                }
            }
        }
    }

    [Test]
    public static void Go()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<MyClass>().AsImplementedInterfaces();
        builder.RegisterType<MyClass2>().AsImplementedInterfaces();
        builder.RegisterModule<SingleRegistrationModule>();
        var container = builder.Build();
        var resolved = container.Resolve<IMy>();
        Console.WriteLine(resolved);
    }
}

In this case nobody resolves IInitializable so it is acceptable to have multiple implementations. Moreover there are scenarios when mulltiple implementation is OK, for example IPluginToSomething

Alex Ilyin
  • 1,294
  • 1
  • 12
  • 22

2 Answers2

7

The reason that Autofac doesn't throw an exception is because Autofac considers multiple registrations for the same interface to be part of a collection. Example:

builder.RegisterType<MyClass>().As<IMy>();
builder.RegisterType<MyClass2>().As<IMy>();
var container = builder.Build();
var collection = container.Resolve<IEnumerable<IMy>>();
Console.WriteLine(collection.Count()); // prints "2"

In case multiple registrations are made, a call to Resolve<IMy>() will resolve only one of them (either the first or the last, but I always forget which one it is). I personally consider this a design flaw in Autofac (and the other DI containers), because this causes your application to fail silently, instead of failing fast. In Simple Injector the choice has been made to strictly separate the registrations of collections (as explained here) to prevent these types of configuration mistakes.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • I fully agree with you and I also tried to resolve the collection, but how to override this flawed default behaviour ? – Alex Ilyin May 10 '16 at 09:19
  • @AlexIlyin: I don't think you can easily override this, but perhaps the Autofac guru's can answer that. – Steven May 10 '16 at 09:20
  • I checked the revolving sequence, Autofac will use the last one when inject. – hsc Jun 25 '19 at 09:34
2

As Steven said, Autofac will consider multiple registration of a same service to be part of a collection.

If you don't want this behavior, you can add a check using an Autofac module :

public class SingleRegistrationModule : Module
{
    protected override void AttachToComponentRegistration(
        IComponentRegistry componentRegistry, 
        IComponentRegistration registration)
    {
        foreach (var service in registration.Services)
        {
            var registrations = componentRegistry.RegistrationsFor(service);
            if (registrations.Count() > 1)
            {
                throw new Exception(
                    $"Can't register '{registration.Activator.LimitType}' as '{service}'" + 
                    $" because '{registrations.First().Activator.LimitType}' is already registered");
            }
        }
    }
}

Then you can register the module using :

builder.RegisterModule<SingleRegistrationModule>();

The exception will be thrown when the container is being built.

Community
  • 1
  • 1
Cyril Durand
  • 15,834
  • 5
  • 54
  • 62
  • Thanks for the answer but there seems to be a problem. This solution throws too many exceptions... Let's suppose types are registered using .RegisterType().AsImplementedInterfaces(); And some of them support IDisposable, IEnumerable or any other interface nobody going to resolve. This solution would report error for these interfaces. – Alex Ilyin May 10 '16 at 10:27
  • Of course I can support a list of exception interfaces, but it is not a scalable solution – Alex Ilyin May 10 '16 at 10:30
  • I've even tred to listen evens like ResolveOperationBeginning and InstanceLookupBeginning, but these events do not seem to expose the requested interface – Alex Ilyin May 10 '16 at 10:31
  • It is so strange that there is no some simple solution, from my point of view 100% of Autofac users should face the problem – Alex Ilyin May 10 '16 at 10:34
  • You are the first one I heard with this problem. Could you describe tout problem ? Maybe a [X Y problem](http://xyproblem.info) – Cyril Durand May 10 '16 at 12:13
  • I've updated the initial question because large code fragments are not allowed in comments. The point is that sometimes it is a valid situation when one interface has multiple implementations. It is a problem only when one tries to resolve such an interface – Alex Ilyin May 10 '16 at 14:05
  • Thanks you for trying to help me. I am new to Autofac so I have to ask stupid questions :) – Alex Ilyin May 10 '16 at 14:08
  • 1
    IMO you should not use AsImplementedInterfaces. – Steven May 10 '16 at 15:41
  • And do note that this `SingleRegistrationModule` completely disables registration of collections altogether, which might not be desirable. – Steven May 10 '16 at 20:31
  • If I do not use AsImplementedInterfaces. then how should I register my types ? – Alex Ilyin May 20 '16 at 06:18
  • With the as method – Cyril Durand May 20 '16 at 09:53