factory.md
July 3, 2026 ยท View on GitHub
Factory
Constructor injection covers most cases, but sometimes an instance needs extra work before it is ready to use โ like the Connect() call here that opens a database connection.
A factory binding To<T>(ctx => ...) puts that creation logic under your control: call ctx.Inject(out var dependency) to have the container provide dependencies, run any setup code, then return the finished instance.
using Shouldly;
using Pure.DI;
DI.Setup(nameof(Composition))
.Bind<IDatabaseService>().To<DatabaseService>(ctx => {
// Some logic for creating an instance.
// For example, we need to manually initialize the connection.
ctx.Inject(out DatabaseService service);
service.Connect();
return service;
})
.Bind<IUserRegistry>().To<UserRegistry>()
// Composition root
.Root<IUserRegistry>("Registry");
var composition = new Composition();
var registry = composition.Registry;
registry.Database.IsConnected.ShouldBeTrue();
interface IDatabaseService
{
bool IsConnected { get; }
}
class DatabaseService : IDatabaseService
{
public bool IsConnected { get; private set; }
// Simulates a connection establishment that must be called explicitly
public void Connect() => IsConnected = true;
}
interface IUserRegistry
{
IDatabaseService Database { get; }
}
class UserRegistry(IDatabaseService database) : IUserRegistry
{
public IDatabaseService Database { get; } = database;
}
Running this code sample locally
- Make sure you have the .NET SDK 10.0 or later installed
dotnet --list-sdk
- Create a net10.0 (or later) console application
dotnet new console -n Sample
dotnet add package Pure.DI
dotnet add package Shouldly
- Copy the example code into the Program.cs file
You are ready to run the example ๐
dotnet run
There are scenarios where manual control over the creation process is required, such as
- When additional initialization logic is needed
- When complex construction steps are required
- When specific object states need to be set during creation
Important
The method Inject() cannot be used outside of the binding setup.
Limitations: factory bindings introduce custom construction logic that must be maintained and tested.
Common pitfalls:
- Moving business decisions into DI factory code.
- Overusing
Inject()where normal constructor binding is enough. See also: Simplified factory, Injection on demand.
The following partial class will be generated
partial class Composition
{
public IUserRegistry Registry
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
DatabaseService transientDatabaseService;
// Some logic for creating an instance.
// For example, we need to manually initialize the connection.
DatabaseService localService = new DatabaseService();
localService.Connect();
transientDatabaseService = localService;
return new UserRegistry(transientDatabaseService);
}
}
}
Class diagram:
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
DatabaseService --|> IDatabaseService
UserRegistry --|> IUserRegistry
Composition ..> UserRegistry : IUserRegistry Registry
UserRegistry *-- DatabaseService : IDatabaseService
namespace Pure.DI.UsageTests.Basics.FactoryScenario {
class Composition {
<<partial>>
+IUserRegistry Registry
}
class DatabaseService {
<<class>>
}
class IDatabaseService {
<<interface>>
}
class IUserRegistry {
<<interface>>
}
class UserRegistry {
<<class>>
+UserRegistry(IDatabaseService database)
}
}
See also: