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) => global::MyNamespace.MyServiceFactory.Create());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) =>
{
var f_p0 = sp.GetRequiredService<global::MyNamespace.ILogger>();
return global::MyNamespace.MyServiceFactory.Create(sp, f_p0);
});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 |
| Required service | ILogger logger | sp.GetRequiredService<ILogger>() |
| Optional service (nullable) | ILogger? logger | sp.GetService<ILogger>() ?? default |
| Default value | ILogger logger = null | sp.GetService<ILogger>() ?? default |
| 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) =>
{
var f_p0 = sp.GetRequiredService<global::MyNamespace.ILogger>();
var f_p1 = sp.GetRequiredKeyedService<global::MyNamespace.IConfiguration>("config");
return global::MyNamespace.MyServiceFactory.Create(sp, "myKey", f_p0, f_p1);
});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.AddSingleton<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) => (global::TestNamespace.IRepository<global::TestNamespace.Customer>)global::TestNamespace.RepositoryFactory.Create(sp));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) => global::TestNamespace.DefaultFactory.Create(sp));
// 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) => global::TestNamespace.SpecialFactory.Create(sp));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.
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.AddSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>>((global::System.IServiceProvider sp) => (global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>)global::TestNamespace.FactoryContainer.Create<global::TestNamespace.Entity>());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.AddSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>, global::System.Collections.Generic.List<global::TestNamespace.Dto>>>((global::System.IServiceProvider sp) => (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>());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.AddSingleton<global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>>((global::System.IServiceProvider sp) => (global::TestNamespace.IRequestHandler<global::System.Threading.Tasks.Task<global::TestNamespace.Entity>>)global::TestNamespace.FactoryContainer.Create<global::TestNamespace.Entity>(sp));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 does not have [IocGenericFactory] attribute and GenericFactoryTypeMapping is not specified on the registration attribute, or the placeholder count does not match the factory method's type parameters. |
| SGIOC017 | Error | Placeholder types in [IocGenericFactory] or GenericFactoryTypeMapping are duplicated. Each placeholder type must be unique within its mapping. |