Search code examples
c#autofac

In Autofac, what is the difference between RegisterAssemblyOpenGenericTypes and RegisterAssemblyTypes with AsClosedTypesOf?


I have a generic interface that is implemented by a more than one concrete class. For example:

public interface IMyInterface<T> {}
internal class Impl1 : IMyInterface<int> {}
internal class Impl2 : IMyInterface<float> {}

I want to automatically register all implementations of IMyInterface<T> so that I can resolve a specific implementation supported for type T (in this case, either int or float). For example:

class TheConsumer
{
  // `impl` should resolve to `Impl1`
  public TheConsumer(IMyInterface<int> impl) {}
}

From what I can gather from this github issue and the documentation, there seems to be two mechanisms to do this:

var builder = new ContainerBuilder();

// Mechanism 1
builder.RegisterAssemblyOpenGenericTypes(typeof(Startup).Assembly)
    .AssignableTo(typeof(IMyInterface<>))
    .AsImplementedInterfaces();

// Mechanism 2
builder.RegisterAssemblyTypes(typeof(Startup).Assembly)
    .AsClosedTypesOf(typeof(IMyInterface<>))
    .AsImplementedInterfaces();

I've only managed to test the 2nd mechanism shown above. The first one I am speculating is the same; but it may not be. Overall I'm just really confused about how "generics" are handled in Autofac. The docs are also missing any mention of RegisterAssemblyOpenGenericTypes, which doesn't help. So here are my questions:

  • What is the functional difference between the two versions above?
  • In what case would you choose one or the other?
  • What is an "open" and "closed" generic (terminology from the Autofac documentation)?

Solution

  • What is an "open" and "closed" generic (terminology from the Autofac documentation)?

    An open generic has an undefined type argument (e.g. List<T>). Whereas, a closed generic has a defined type argument (e.g. List<int>).

    • What is the functional difference between the two versions above?

    Currently, both of your implementations are "closed" generic implementations of IMyInterface<T>:

    internal class Impl1 : IMyInterface<int> { }
    internal class Impl2 : IMyInterface<float> { }
    

    When you use RegisterAssemblyOpenGenericTypes ("Mechanism 1") with these, you get an exception that IMyInterface<int> is unknown when resolving TheConsumer. When you use RegisterAssemblyTypes with these closed generic implementations, Impl1 is passed as expected when resolving TheConsumer.

    The usage of RegisterAssemblyOpenGenericTypes can then be illustrated by extending your example a bit. If you add another implementation of IMyInterface<T> such as:

    internal class Impl3<T> : IMyInterface<T> { }
    

    You know have an "open" generic implementation of IMyInterface<T>. So when you go back to RegisterAssemblyOpenGenericTypes ("Mechanism 1") and try to resolve TheConsumer, it works and Impl3<int> is passed in.

    In what case would you choose one or the other?

    Using RegisterAssemblyTypes with the "closed" generic types as you currently have, users of IMyInterface<T> will only be able to resolve the interface if it's specifically implemented with that type (e.g. IMyInterface<int> is implemented by Impl1). This can be ideal when you only want a certain set of type arguments that you want used in IMyInterface<T>.

    Using RegisterAssemblyOpenGenericTypes can be useful when you don't actually care what the type arguments used in IMyInterface<T> are. This essentially just allows more flexible usage of IMyInterface<T>, which is something you may or may not want.