Open Generic Types
In Microsoft.Extensions.DependencyInjection, open generic types can be registered and resolved at runtime.
However, this involves runtime reflection which can be costly in terms of performance, and does not support nested generic types.SourceGen.Ioc can automatically discover closed generic types used in constructor/property/method injection or IServiceProvider resolution, generating registration code at compile time to eliminate runtime reflection overhead and support nested generic types.
Basic Open Generic
public interface IRepository<T>;
[IocRegister(ServiceLifetime.Scoped, typeof(IRepository<>))]
internal class Repository<T> : IRepository<T>;Generated Code
// <auto-generated/>
services.AddScoped(typeof(global::MyNamespace.Repository<>), typeof(global::MyNamespace.Repository<>));
services.AddScoped(typeof(global::MyNamespace.IRepository<>), typeof(global::MyNamespace.Repository<>));Auto-Discovery
The generator automatically discovers closed generic types used in:
- Constructor parameters
- Property/field types marked with
[IocInject]/[Inject] - Method parameters that method are marked with
[IocInject] IServiceProviderinvocations:GetService(typeof(T))GetService<T>()GetRequiredService(typeof(T))GetRequiredService<T>()GetKeyedService(typeof(T), Key)GetKeyedService<T>(Key)GetRequiredKeyedService(typeof(T), Key)GetRequiredKeyedService<T>(Key)GetServices(typeof(T))GetServices<T>()GetKeyedServices(typeof(T), Key)GetKeyedServices<T>(Key)
public interface ILogger<T>;
[IocRegister(ServiceTypes = [typeof(ILogger<>)])]
internal class Logger<T> : ILogger<T>;
public class User;
[IocRegister]
internal class UserService(ILogger<UserService> logger)
{
// ILogger<UserService> is automatically discovered and registered
}Generated Code
// <auto-generated/>
// Open generic registration
services.AddSingleton(typeof(global::MyNamespace.Logger<>), typeof(global::MyNamespace.Logger<>));
services.AddSingleton(typeof(global::MyNamespace.ILogger<>), typeof(global::MyNamespace.Logger<>));
// Closed generic discovered from UserService constructor
services.AddSingleton<global::MyNamespace.UserService>((global::System.IServiceProvider sp) =>
{
var p0 = sp.GetRequiredService<global::MyNamespace.ILogger<global::MyNamespace.UserService>>();
var s0 = new global::MyNamespace.UserService(p0);
return s0;
});Nested Generic Types
SourceGen.Ioc supports nested open generic service interfaces that MS.E.DI cannot resolve at runtime. When the service interface itself contains nested generics (e.g., IRequestHandler<GenericRequest<T>, List<T>>), MS.E.DI cannot properly map from the open generic registration to the correct closed generic type.
public interface IRequest<TSelf, TResponse> where TSelf : IRequest<TSelf, TResponse>;
public interface IRequestHandler<TRequest, TResponse> where TRequest : IRequest<TRequest, TResponse>;
public record GenericRequest<T> : IRequest<GenericRequest<T>, List<T>>;
[IocRegister]
internal class GenericRequestHandler<T> : IRequestHandler<GenericRequest<T>, List<T>>;
public class Entity;
internal class ViewModel(IRequestHandler<GenericRequest<Entity>, List<Entity>> handler)
{
// ...
}Generated Code
// <auto-generated/>
// Open generic registration
services.AddSingleton(typeof(global::MyNamespace.GenericRequestHandler<>), typeof(global::MyNamespace.GenericRequestHandler<>));
// Discovered nested generic service interface
services.AddSingleton<global::MyNamespace.GenericRequestHandler<global::MyNamespace.Entity>, global::MyNamespace.GenericRequestHandler<global::MyNamespace.Entity>>();
services.AddSingleton<global::MyNamespace.IRequestHandler<global::MyNamespace.GenericRequest<global::MyNamespace.Entity>, global::System.Collections.Generic.List<global::MyNamespace.Entity>>>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::MyNamespace.GenericRequestHandler<global::MyNamespace.Entity>>());NOTE
The service interface IRequestHandler<GenericRequest<Entity>, List<Entity>> contains nested generic types. MS.E.DI cannot resolve this at runtime because it cannot determine how to substitute T = Entity into IRequestHandler<GenericRequest<T>, List<T>>. SourceGen.Ioc handles this through compile-time code generation.
Manual Discovery with [IocDiscover]
For types not directly referenced in code, use [IocDiscover] attribute:
On Method
public interface IRequest<TSelf, TResponse> where TSelf : IRequest<TSelf, TResponse>;
public interface IRequestHandler<TRequest, TResponse> where TRequest : IRequest<TRequest, TResponse>;
public record TestRequest<T> : IRequest<TestRequest<T>, List<T>>;
[IocRegister]
internal class TestRequestHandler<T> : IRequestHandler<TestRequest<T>, List<T>>;
internal class Consumer
{
// Discover closed generic type for registration
[IocDiscover<IRequestHandler<TestRequest<string>, List<string>>>]
public void Process() { }
}Generated Code
// <auto-generated/>
// Open generic registration
services.AddSingleton(typeof(global::MyNamespace.TestRequestHandler<>), typeof(global::MyNamespace.TestRequestHandler<>));
// Discovered from [IocDiscover] attribute
services.AddSingleton<global::MyNamespace.TestRequestHandler<string>, global::MyNamespace.TestRequestHandler<string>>();
services.AddSingleton<global::MyNamespace.IRequestHandler<global::MyNamespace.TestRequest<string>, global::System.Collections.Generic.List<string>>>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::MyNamespace.TestRequestHandler<string>>());On Class
[IocDiscover(typeof(IRequestHandler<TestRequest<CustomType>, List<CustomType>>))]
internal class MyClass;On Assembly
[assembly: IocDiscover(typeof(IRequestHandler<TestRequest<Entity>, List<Entity>>))]With Decorators
Open generic types with decorators also support automatic discovery:
[IocRegisterDefaults(
typeof(IRequestHandler<,>),
ServiceLifetime.Singleton,
Decorators = [typeof(LoggingDecorator<,>)])]
public interface IRequestHandler<TRequest, TResponse>;
[IocRegister]
internal class TestHandler<T> : IRequestHandler<TestRequest<T>, List<T>>;
[IocRegister]
internal class ViewModel(IRequestHandler<TestRequest<Entity>, List<Entity>> handler);Generated Code
// <auto-generated/>
// Open generic registration
services.AddSingleton(typeof(global::MyNamespace.TestHandler<>), typeof(global::MyNamespace.TestHandler<>));
// ViewModel with discovered dependency
services.AddSingleton<global::MyNamespace.ViewModel>((global::System.IServiceProvider sp) =>
{
var p0 = sp.GetRequiredService<global::MyNamespace.IRequestHandler<global::MyNamespace.TestRequest<global::MyNamespace.Entity>, global::System.Collections.Generic.List<global::MyNamespace.Entity>>>();
var s0 = new global::MyNamespace.ViewModel(p0);
return s0;
});
// Closed generic with decorator chain
services.AddSingleton<global::MyNamespace.IRequestHandler<global::MyNamespace.TestRequest<global::MyNamespace.Entity>, global::System.Collections.Generic.List<global::MyNamespace.Entity>>>((global::System.IServiceProvider sp) =>
{
var s0 = sp.GetRequiredService<global::MyNamespace.TestHandler<global::MyNamespace.Entity>>();
var s1 = new global::MyNamespace.LoggingDecorator<global::MyNamespace.TestRequest<global::MyNamespace.Entity>, global::System.Collections.Generic.List<global::MyNamespace.Entity>>(s0);
return s1;
});