Skip to content

Factory & Instance Registration

Factory Method

Register a service using a factory method:

csharp
[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
csharp
// <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:

csharp
[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
csharp
// <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 KindExampleResolution
IServiceProviderIServiceProvider spPassed directly as sp
[ServiceKey][ServiceKey] string keyPassed as the registration key value
Required serviceILogger loggersp.GetRequiredService<ILogger>()
Optional service (nullable)ILogger? loggersp.GetService<ILogger>() ?? default
Default valueILogger logger = nullsp.GetService<ILogger>() ?? default
Keyed service[FromKeyedServices("key")] IConfig cfgsp.GetRequiredKeyedService<IConfig>("key")
CollectionIEnumerable<IHandler> handlerssp.GetServices<IHandler>()

Mixed Parameters Example

A factory method can combine all parameter kinds:

csharp
[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
csharp
// <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):

csharp
[IocRegister<IMyService>(ServiceLifetime.Singleton, Instance = nameof(Default))]
internal class MyService : IMyService
{
    public static readonly MyService Default = new();
}
Generated Code
csharp
// <auto-generated/>
services.AddSingleton<global::MyNamespace.MyService>(global::MyNamespace.MyService.Default);
services.AddSingleton<global::MyNamespace.IMyService>(global::MyNamespace.MyService.Default);

Instance with Keyed Service

csharp
[IocRegister<IMyService>(ServiceLifetime.Singleton, Instance = nameof(Default), Key = "myKey")]
internal class MyService : IMyService
{
    public static readonly MyService Default = new();
}
Generated Code
csharp
// <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.

csharp
[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
csharp
// <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:

csharp
[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
csharp
// <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:

  1. [IocGenericFactory] on the factory method — use when you own the factory method.
  2. GenericFactoryTypeMapping on 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:

csharp
[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
csharp
// <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):

csharp
// 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>>):

  1. The generator compares it with the service type template (IRequestHandler<Task<int>>)
  2. Extracts actual types from positions matching placeholder types (intEntity)
  3. Substitutes them into the factory method call: Create<Entity>()

Multiple Type Parameters

For services with multiple type parameters, use different placeholder types for each:

csharp
[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
csharp
// <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:

csharp
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> matches Task<int>int maps to T2T2 = Entity
  • List<Dto> matches List<decimal>decimal maps to T1T1 = 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:

csharp
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
csharp
// <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

IDSeverityDescription
SGIOC008ErrorFactory or Instance uses nameof(), but the referenced field/property/method is not static or is inaccessible.
SGIOC009ErrorInstance is specified but Lifetime is not Singleton.
SGIOC010ErrorBoth Factory and Instance are specified on the same attribute. Factory takes precedence.
SGIOC016ErrorGeneric 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.
SGIOC017ErrorPlaceholder types in [IocGenericFactory] or GenericFactoryTypeMapping are duplicated. Each placeholder type must be unique within its mapping.

← Back to Overview

Released under the MIT License.