Factory & Instance Registration
Factory Method
Register a service using a factory method:
[IocRegister<IMyService>(Factory = nameof(MyServiceFactory.Create))]
internal class MyService : IMyService;
public static class MyServiceFactory
{
// Must be static
public static IMyService Create()
{
return new MyService();
}
}Generated Code
// <auto-generated/>
services.AddTransient<global::MyNamespace.MyService>((global::System.IServiceProvider sp) => (global::MyNamespace.MyService)global::MyNamespace.MyServiceFactory.Create());
services.AddTransient<global::MyNamespace.IMyService>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::MyNamespace.MyService>());The factory is called only by the implementation type's registration. Service types forward to it, so resolving MyService and IMyService gives the same instance for a singleton or scoped service. Decorators wrap that instance, as they do for constructor-created services. Generic factories (see Generic Factory) are called separately for each service type.
Factory Returning Different Implementations
WARNING
The implementation type's registration casts the factory result to the implementation type, and service types resolve through it. A factory that returns another type, for example new OtherService() in the factory of MyService, throws InvalidCastException at resolution.
When the factory decides which implementation to create, register the factory for the service type itself. IocRegisterFor<IMyService> makes IMyService both the registered type and the target type, so the result is not cast to any implementation:
[IocRegisterFor<IMyService>(ServiceLifetime.Singleton, Factory = nameof(MyServiceFactory.Create))]
public static class MyServiceFactory
{
public static IMyService Create(IServiceProvider sp) =>
Environment.ProcessorCount > 1 ? new FastService() : new SafeService();
}Generated Code
// <auto-generated/>
services.AddSingleton<global::MyNamespace.IMyService>((global::System.IServiceProvider sp) => global::MyNamespace.MyServiceFactory.Create(sp));FastService and SafeService are not registered, so they cannot be resolved by their own types. Register them separately if consumers need them.
Factory with Service Injection
Factory methods support service injection just like constructors. Any parameter that is not IServiceProvider or [ServiceKey] is automatically resolved from the DI container:
[IocRegister<IMyService>(Factory = nameof(MyServiceFactory.Create))]
internal class MyService : IMyService;
public static class MyServiceFactory
{
public static IMyService Create(IServiceProvider sp, ILogger logger)
{
return new MyService(logger);
}
}Generated Code
// <auto-generated/>
services.AddTransient<global::MyNamespace.MyService>((global::System.IServiceProvider sp) =>
{
var f_p0 = sp.GetRequiredService<global::MyNamespace.ILogger>();
return (global::MyNamespace.MyService)global::MyNamespace.MyServiceFactory.Create(sp, f_p0);
});
services.AddTransient<global::MyNamespace.IMyService>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::MyNamespace.MyService>());The following parameter kinds are supported:
| Parameter Kind | Example | Resolution |
|---|---|---|
IServiceProvider | IServiceProvider sp | Passed directly as sp |
[ServiceKey] | [ServiceKey] string key | Passed as the registration key value, or default! when the registration has no key |
| Required service | ILogger logger | sp.GetRequiredService<ILogger>() |
| Optional service (nullable) | ILogger? logger | sp.GetService<ILogger>() ?? default |
| Default value | int retries = 3 | sp.GetService(typeof(int)) switch { int service => service, _ => 3 } (the parameter's own default; also compiles for value types) |
Default null on a non-nullable type | ILogger logger = null | sp.GetService(typeof(ILogger)) switch { ILogger service => service, _ => default(ILogger)! } |
| Keyed service | [FromKeyedServices("key")] IConfig cfg | sp.GetRequiredKeyedService<IConfig>("key") |
| Collection | IEnumerable<IHandler> handlers | sp.GetServices<IHandler>() |
Mixed Parameters Example
A factory method can combine all parameter kinds:
[IocRegister<IMyService>(
ServiceLifetime.Scoped,
Factory = nameof(MyServiceFactory.Create),
Key = "myKey")]
internal class MyService : IMyService;
public static class MyServiceFactory
{
public static IMyService Create(
IServiceProvider sp,
[ServiceKey] string key,
ILogger logger,
[FromKeyedServices("config")] IConfiguration config)
=> new MyService();
}Generated Code
// <auto-generated/>
services.AddKeyedScoped<global::MyNamespace.MyService>("myKey", (global::System.IServiceProvider sp, object? key) =>
{
var f_p0 = sp.GetRequiredService<global::MyNamespace.ILogger>();
var f_p1 = sp.GetRequiredKeyedService<global::MyNamespace.IConfiguration>("config");
return (global::MyNamespace.MyService)global::MyNamespace.MyServiceFactory.Create(sp, "myKey", f_p0, f_p1);
});
services.AddKeyedScoped<global::MyNamespace.IMyService>("myKey", (global::System.IServiceProvider sp, object? key) => sp.GetRequiredKeyedService<global::MyNamespace.MyService>(key));Static Instance
Register a static instance (only allows Singleton lifetime):
[IocRegister<IMyService>(ServiceLifetime.Singleton, Instance = nameof(Default))]
internal class MyService : IMyService
{
public static readonly MyService Default = new();
}Generated Code
// <auto-generated/>
services.AddSingleton<global::MyNamespace.MyService>(global::MyNamespace.MyService.Default);
services.AddSingleton<global::MyNamespace.IMyService>(global::MyNamespace.MyService.Default);Instance with Keyed Service
[IocRegister<IMyService>(ServiceLifetime.Singleton, Instance = nameof(Default), Key = "myKey")]
internal class MyService : IMyService
{
public static readonly MyService Default = new();
}Generated Code
// <auto-generated/>
services.AddKeyedSingleton<global::MyNamespace.MyService>("myKey", global::MyNamespace.MyService.Default);
services.AddKeyedSingleton<global::MyNamespace.IMyService>("myKey", global::MyNamespace.MyService.Default);NOTE
When using Instance, only Singleton lifetime is allowed. Using Scoped or Transient will not generate any registration code.
Factory in Default Settings
You can specify a Factory in IocRegisterDefaults to apply it as the default factory for all services implementing the target type. Explicit Factory specified in IocRegister takes precedence over the default.
[assembly: IocRegisterDefaults(
typeof(IRepository<>),
ServiceLifetime.Scoped,
Factory = nameof(RepositoryFactory.Create))]
public interface IRepository<T> { T Get(int id); }
public static class RepositoryFactory
{
public static object Create(IServiceProvider sp)
{
// Custom creation logic for all repositories
return sp.GetRequiredService<object>();
}
}
// Uses Factory from DefaultSettings
[IocRegister(ServiceTypes = [typeof(IRepository<>)])]
public class Repository<T> : IRepository<T>
{
public T Get(int id) => default!;
}
// Entity for closed generic discovery
public class Customer { }
// This triggers closed generic registration with Factory
[IocRegister]
public class CustomerService(IRepository<Customer> repository);Generated Code
// <auto-generated/>
// Open generic uses typeof() syntax (Factory not applicable for open generics)
services.AddScoped(typeof(global::TestNamespace.Repository<>), typeof(global::TestNamespace.Repository<>));
services.AddScoped(typeof(global::TestNamespace.IRepository<>), typeof(global::TestNamespace.Repository<>));
// CustomerService registration
services.AddTransient<global::TestNamespace.CustomerService, global::TestNamespace.CustomerService>();
// Closed generic uses Factory from DefaultSettings
services.AddScoped<global::TestNamespace.Repository<global::TestNamespace.Customer>>((global::System.IServiceProvider sp) => (global::TestNamespace.Repository<global::TestNamespace.Customer>)global::TestNamespace.RepositoryFactory.Create(sp));
services.AddScoped<global::TestNamespace.IRepository<global::TestNamespace.Customer>>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::TestNamespace.Repository<global::TestNamespace.Customer>>());Explicit Factory Overrides Default
When a registration specifies its own Factory, it overrides the default from IocRegisterDefaults:
[assembly: IocRegisterDefaults(
typeof(IHandler),
ServiceLifetime.Scoped,
Factory = nameof(DefaultFactory.Create))]
public interface IHandler { void Handle(); }
public static class DefaultFactory
{
public static IHandler Create(IServiceProvider sp) => sp.GetRequiredService<DefaultHandler>();
}
public static class SpecialFactory
{
public static IHandler Create(IServiceProvider sp) => new SpecialHandler();
}
// Uses Factory from DefaultSettings
[IocRegister(ServiceTypes = [typeof(IHandler)])]
public class DefaultHandler : IHandler
{
public void Handle() { }
}
// Uses explicit Factory, overriding DefaultSettings
[IocRegister(ServiceTypes = [typeof(IHandler)], Factory = nameof(SpecialFactory.Create))]
public class SpecialHandler : IHandler
{
public void Handle() { }
}Generated Code
// <auto-generated/>
// DefaultHandler uses Factory from DefaultSettings
services.AddScoped<global::TestNamespace.DefaultHandler>((global::System.IServiceProvider sp) => (global::TestNamespace.DefaultHandler)global::TestNamespace.DefaultFactory.Create(sp));
services.AddScoped<global::TestNamespace.IHandler>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::TestNamespace.DefaultHandler>());
// SpecialHandler uses explicit Factory
services.AddScoped<global::TestNamespace.SpecialHandler>((global::System.IServiceProvider sp) => (global::TestNamespace.SpecialHandler)global::TestNamespace.SpecialFactory.Create(sp));
services.AddScoped<global::TestNamespace.IHandler>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::TestNamespace.SpecialHandler>());Generic Factory
When working with open generic services, you can use a generic factory method to create instances. The generator needs to know how to map the service type's type parameters to the factory method's type parameters. There are two ways to specify this mapping:
[IocGenericFactory]on the factory method — use when you own the factory method.GenericFactoryTypeMappingon the registration attribute — use when you cannot modify the factory (e.g., a third-party library).
Both use the same format:
- First type: The service type template with concrete placeholder types (e.g.,
typeof(IRequestHandler<Task<int>>)) - Remaining types: The placeholder types that map to factory method type parameters in order
WARNING
If Factory points to a generic method but neither [IocGenericFactory] nor a valid GenericFactoryTypeMapping is specified, analyzer SGIOC016 is reported and registration will not be generated. The number of placeholder types must match the factory method's type parameter count, and each placeholder type must appear in the service type template.
NOTE
When both GenericFactoryTypeMapping on the attribute and [IocGenericFactory] on the method are present, [IocGenericFactory] takes precedence.
Using [IocGenericFactory]
Place the attribute on the factory method:
[assembly: IocRegisterDefaults(
typeof(IRequestHandler<>),
ServiceLifetime.Singleton,
Factory = nameof(FactoryContainer.Create))]
public interface IRequestHandler<TResponse> { }
public static class FactoryContainer
{
// Map: IRequestHandler<Task<int>> placeholder 'int' -> type parameter T
[IocGenericFactory(typeof(IRequestHandler<Task<int>>), typeof(int))]
public static IRequestHandler<Task<T>> Create<T>()
=> throw new NotImplementedException();
}
[IocRegister]
public class Handler<T> : IRequestHandler<Task<T>> { }
public class Entity { }
// Discover IRequestHandler<Task<Entity>>
[IocDiscover<IRequestHandler<Task<Entity>>>]
public sealed class App { }Generated Code
// <auto-generated/>
// Open generic registration
services.AddSingleton(typeof(global::TestNamespace.Handler<>), typeof(global::TestNamespace.Handler<>));
// Closed generic uses generic factory with Entity substituted for T
services.AddSingleton<global::TestNamespace.Handler<global::TestNamespace.Entity>, global::TestNamespace.Handler<global::TestNamespace.Entity>>();
services.AddKeyedSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>>("SourceGen.Ioc.Decorated:TestNamespace.IRequestHandler<System.Threading.Tasks.Task<TestNamespace.Entity>>|TestNamespace.Handler<TestNamespace.Entity>", (global::System.IServiceProvider sp, object? key) =>
{
var s0 = (global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>)global::TestNamespace.FactoryContainer.Create<global::TestNamespace.Entity>();
return s0;
});
services.AddSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>>((global::System.IServiceProvider sp) => sp.GetRequiredKeyedService<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>>("SourceGen.Ioc.Decorated:TestNamespace.IRequestHandler<System.Threading.Tasks.Task<TestNamespace.Entity>>|TestNamespace.Handler<TestNamespace.Entity>"));NOTE
A generic factory creates the instance of the service type it is closed for, so the factory result is not cast to the implementation type. Resolving the implementation type (Handler<Entity>) gives its own constructed instance. Decorators wrap the factory result, and Lazy<T> / Func<T> of the service type resolve the same instance through the hidden key.
Using GenericFactoryTypeMapping
Specify the mapping directly on the registration attribute. This is useful when you cannot modify the factory method (e.g., third-party code):
// No [IocGenericFactory] attribute needed on the factory method
[assembly: IocRegisterDefaults(
typeof(IRequestHandler<>),
ServiceLifetime.Singleton,
Factory = nameof(ExternalFactory.Create),
GenericFactoryTypeMapping = [typeof(IRequestHandler<Task<int>>), typeof(int)])]
public static class ExternalFactory
{
// Third-party or shared factory — cannot be modified
public static IRequestHandler<Task<T>> Create<T>()
=> throw new NotImplementedException();
}GenericFactoryTypeMapping is available on [IocRegisterDefaults] and [IocRegisterFor].
How the Mapping Works
When a closed generic type is discovered (e.g., IRequestHandler<Task<Entity>>):
- The generator compares it with the service type template (
IRequestHandler<Task<int>>) - Extracts actual types from positions matching placeholder types (
int→Entity) - Substitutes them into the factory method call:
Create<Entity>()
Multiple Type Parameters
For services with multiple type parameters, use different placeholder types for each:
[assembly: IocRegisterDefaults(
typeof(IRequestHandler<,>),
ServiceLifetime.Singleton,
Factory = nameof(FactoryContainer.Create))]
public interface IRequestHandler<TRequest, TResponse> { }
public static class FactoryContainer
{
// Map: 'int' -> T1, 'decimal' -> T2
[IocGenericFactory(
typeof(IRequestHandler<Task<int>, List<decimal>>),
typeof(int),
typeof(decimal))]
public static IRequestHandler<Task<T1>, List<T2>> Create<T1, T2>()
=> throw new NotImplementedException();
}
[IocRegister]
public class Handler<T1, T2> : IRequestHandler<Task<T1>, List<T2>> { }
public class Entity { }
public class Dto { }
// Discover IRequestHandler<Task<Entity>, List<Dto>>
[IocDiscover(typeof(IRequestHandler<Task<Entity>, List<Dto>>))]
public sealed class App { }Generated Code
// <auto-generated/>
// Open generic registration
services.AddSingleton(typeof(global::TestNamespace.Handler<,>), typeof(global::TestNamespace.Handler<,>));
// Closed generic with T1=Entity, T2=Dto
services.AddSingleton<global::TestNamespace.Handler<global::TestNamespace.Entity, global::TestNamespace.Dto>, global::TestNamespace.Handler<global::TestNamespace.Entity, global::TestNamespace.Dto>>();
services.AddKeyedSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>, global::System.Collections.Generic.List<global::TestNamespace.Dto>>>("SourceGen.Ioc.Decorated:TestNamespace.IRequestHandler<System.Threading.Tasks.Task<TestNamespace.Entity>, System.Collections.Generic.List<TestNamespace.Dto>>|TestNamespace.Handler<TestNamespace.Entity, TestNamespace.Dto>", (global::System.IServiceProvider sp, object? key) =>
{
var s0 = (global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>, global::System.Collections.Generic.List<global::TestNamespace.Dto>>)global::TestNamespace.FactoryContainer.Create<global::TestNamespace.Entity, global::TestNamespace.Dto>();
return s0;
});
services.AddSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>, global::System.Collections.Generic.List<global::TestNamespace.Dto>>>((global::System.IServiceProvider sp) => sp.GetRequiredKeyedService<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>, global::System.Collections.Generic.List<global::TestNamespace.Dto>>>("SourceGen.Ioc.Decorated:TestNamespace.IRequestHandler<System.Threading.Tasks.Task<TestNamespace.Entity>, System.Collections.Generic.List<TestNamespace.Dto>>|TestNamespace.Handler<TestNamespace.Entity, TestNamespace.Dto>"));Reversed Type Parameter Mapping
The placeholder order determines the mapping. You can map placeholders to type parameters in any order:
public static class FactoryContainer
{
// First placeholder (decimal) -> T1, second placeholder (int) -> T2
// This reverses the mapping!
[IocGenericFactory(
typeof(IRequestHandler<Task<int>, List<decimal>>),
typeof(decimal), // -> T1
typeof(int))] // -> T2
public static IRequestHandler<Task<T2>, List<T1>> Create<T1, T2>()
=> throw new NotImplementedException();
}When discovering IRequestHandler<Task<Entity>, List<Dto>>:
Task<Entity>matchesTask<int>→intmaps toT2→T2 = EntityList<Dto>matchesList<decimal>→decimalmaps toT1→T1 = Dto- Result:
Create<Dto, Entity>()
WARNING
Placeholder types must be unique! Using the same type for multiple placeholders (e.g., typeof(int), typeof(int)) will cause SGIOC017 error and no registration will be generated, because the generator cannot determine which actual type maps to which type parameter.
Generic Factory with IServiceProvider
Generic factories can also receive IServiceProvider:
public static class FactoryContainer
{
[IocGenericFactory(typeof(IRequestHandler<Task<int>>), typeof(int))]
public static IRequestHandler<Task<T>> Create<T>(IServiceProvider sp)
{
var logger = sp.GetRequiredService<ILogger>();
return new Handler<T>(logger);
}
}Generated Code
// <auto-generated/>
services.AddKeyedSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>>("SourceGen.Ioc.Decorated:TestNamespace.IRequestHandler<System.Threading.Tasks.Task<TestNamespace.Entity>>|TestNamespace.Handler<TestNamespace.Entity>", (global::System.IServiceProvider sp, object? key) =>
{
var s0 = (global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>)global::TestNamespace.FactoryContainer.Create<global::TestNamespace.Entity>(sp);
return s0;
});
services.AddSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>>((global::System.IServiceProvider sp) => sp.GetRequiredKeyedService<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>>("SourceGen.Ioc.Decorated:TestNamespace.IRequestHandler<System.Threading.Tasks.Task<TestNamespace.Entity>>|TestNamespace.Handler<TestNamespace.Entity>"));Diagnostics
| ID | Severity | Description |
|---|---|---|
| SGIOC008 | Error | Factory or Instance uses nameof(), but the referenced field/property/method is not static or is inaccessible. |
| SGIOC009 | Error | Instance is specified but Lifetime is not Singleton. |
| SGIOC010 | Error | Both Factory and Instance are specified on the same attribute. Factory takes precedence. |
| SGIOC016 | Error | Generic factory method has no valid type parameter mapping: neither [IocGenericFactory] nor GenericFactoryTypeMapping is specified, the placeholder count does not match the factory method's type parameters, or a placeholder type does not appear in the service type template. |
| SGIOC017 | Error | Placeholder types in [IocGenericFactory] or GenericFactoryTypeMapping are duplicated. Each placeholder type must be unique within its mapping. |