Open generics
Register a generic class once and let the container close it for every type argument.
A generic class is registered as an open generic — one descriptor covering every closed
construction. MSDI closes it on demand, so IRepository<Todo> and IRepository<User> both
resolve from a single registration.
Registering against an open-generic interface
Use ServiceType with an unbound typeof:
using Immediate.Injections.Shared;
public interface IRepository<T>
{
Type ElementType { get; }
}
[RegisterTransient(ServiceType = typeof(IRepository<>))]
public sealed class Repository<T> : IRepository<T>
{
public Type ElementType => typeof(T);
}ServiceDescriptor.Transient(typeof(IRepository<>), typeof(Repository<>));Every closed construction now resolves without further registration:
var todos = provider.GetRequiredService<IRepository<Todo>>(); // Repository<Todo>
var users = provider.GetRequiredService<IRepository<User>>(); // Repository<User> There is no generic-attribute equivalent of this: [RegisterTransient<IRepository<>>] is not
legal C#, which is exactly why ServiceType still exists on the non-generic form.
The arities must match. typeof(IRepository<>) on a class with two type parameters, or a
service type the class does not implement, is INJ0004.
Registering the class as itself
A bare attribute on a generic class registers the open generic against itself:
using Immediate.Injections.Shared;
[RegisterSingleton]
public sealed class Cache<T>;ServiceDescriptor.Singleton(typeof(Cache<>), typeof(Cache<>));Closed constructions
If you want just one closed construction rather than the open generic, name it explicitly with
either the two-type-argument attribute form or a bound ServiceType:
using Immediate.Injections.Shared;
// Registers IRepository<Todo> -> Repository<Todo>
[RegisterScoped<IRepository<Todo>, Repository<Todo>>]
public sealed class Repository<T> : IRepository<T>;// Also registers IRepository<Todo> -> Repository<Todo>
[RegisterScoped(ServiceType = typeof(IRepository<Todo>))]
public sealed class Repository<T> : IRepository<T>;Both forms may be repeated to register several closed constructions of the same class.
Strategies on a generic class
ImplementedInterfaces and SelfAndImplementedInterfaces work on generic classes, but they
filter the interface list hard. An interface is included only if it is generic, has the same
arity as the class, and is closed over the class’s own type parameters.
using Immediate.Injections.Shared;
public interface IMarker;
public interface IOne<T>;
public interface ITwo<T1, T2>;
[RegisterScoped(RegistrationStrategy = RegistrationStrategy.SelfAndImplementedInterfaces)]
public sealed class Service<T1, T2> : IMarker, IOne<T1>, ITwo<T1, T2>;Only two registrations are emitted:
ServiceDescriptor.Scoped(typeof(Service<,>), typeof(Service<,>));
ServiceDescriptor.Scoped(typeof(ITwo<,>), typeof(Service<,>));IMarker is skipped because it is not generic. IOne<T1> is skipped because its arity does not
match the class’s. The same rule drops an interface closed over a concrete type — a Service<T> : IOther<int> does not get an IOther<> registration, because int is not one of
the class’s type parameters.
If you need those, add explicit attributes for them alongside the strategy.
What is not supported
MSDI cannot build an open-generic service from a factory delegate, so neither Factory nor UseProxyFactory works on a generic target class.
// INJ0012 — Factory with an open-generic ServiceType
[RegisterScoped(ServiceType = typeof(IRepository<>), Factory = nameof(Create))]
// INJ0008 — UseProxyFactory with an open-generic ServiceType
[RegisterScoped(ServiceType = typeof(IRepository<>), UseProxyFactory = true)]Closed constructions are unaffected: [RegisterScoped<IRepository<Todo>>] on Repository<T> may
use a factory, because the resulting descriptor is not open.