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 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.