composition-arguments.md
July 3, 2026 ยท View on GitHub
Composition arguments
Use composition arguments when you need to pass state into the composition. Define them with Arg<T>(string argName) (optionally with tags) and use them like any other dependency. Only arguments that are used in the object graph become constructor parameters.
This is a clean way to inject external runtime state without global static variables.
Note
Actually, composition arguments work like normal bindings. The difference is that they bind to the values of the arguments. These values will be injected wherever they are required.
using Shouldly;
using Pure.DI;
DI.Setup(nameof(Composition))
.Bind<IBankGateway>().To<BankGateway>()
.Bind<IPaymentProcessor>().To<PaymentProcessor>()
// Composition root "PaymentService"
.Root<IPaymentProcessor>("PaymentService")
// Composition argument: Connection timeout (e.g., from config)
.Arg<int>("timeoutSeconds")
// Composition argument: API Token (using a tag to distinguish from other strings)
.Arg<string>("authToken", "api token")
// Composition argument: Bank gateway address
.Arg<string>("gatewayUrl");
// Create the composition, passing real settings from outside
var composition = new Composition(
timeoutSeconds: 30,
authToken: "secret_token_123",
gatewayUrl: "https://api.bank.com/v1");
var paymentService = composition.PaymentService;
paymentService.Token.ShouldBe("secret_token_123");
paymentService.Gateway.Timeout.ShouldBe(30);
paymentService.Gateway.Url.ShouldBe("https://api.bank.com/v1");
interface IBankGateway
{
int Timeout { get; }
string Url { get; }
}
// Simulation of a bank gateway client
class BankGateway(int timeoutSeconds, string gatewayUrl) : IBankGateway
{
public int Timeout { get; } = timeoutSeconds;
public string Url { get; } = gatewayUrl;
}
interface IPaymentProcessor
{
string Token { get; }
IBankGateway Gateway { get; }
}
// Payment processing service
class PaymentProcessor(
// The tag allows specifying exactly which string to inject here
[Tag("api token")] string token,
IBankGateway gateway) : IPaymentProcessor
{
public string Token { get; } = token;
public IBankGateway Gateway { get; } = gateway;
}
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
Note
Composition arguments provide a way to inject runtime values into the composition, making your DI configuration more flexible. Limitations: too many composition arguments can bloat composition constructors and blur configuration boundaries. Common pitfalls:
- Using untagged primitive arguments where several values of the same type exist.
- Treating composition arguments as mutable runtime state holders. See also: Root arguments, Tags.
The following partial class will be generated
partial class Composition
{
private readonly int _argTimeoutSeconds;
private readonly string _argAuthToken;
private readonly string _argGatewayUrl;
[OrdinalAttribute(128)]
public Composition(int timeoutSeconds, string authToken, string gatewayUrl)
{
_argTimeoutSeconds = timeoutSeconds;
_argAuthToken = authToken ?? throw new ArgumentNullException(nameof(authToken));
_argGatewayUrl = gatewayUrl ?? throw new ArgumentNullException(nameof(gatewayUrl));
}
public IPaymentProcessor PaymentService
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return new PaymentProcessor(_argAuthToken, new BankGateway(_argTimeoutSeconds, _argGatewayUrl));
}
}
}
Class diagram:
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
BankGateway --|> IBankGateway
PaymentProcessor --|> IPaymentProcessor
Composition ..> PaymentProcessor : IPaymentProcessor PaymentService
BankGateway o-- Int32 : Argument "timeoutSeconds"
BankGateway o-- String : Argument "gatewayUrl"
PaymentProcessor *-- BankGateway : IBankGateway
PaymentProcessor o-- String : "api token" Argument "authToken"
namespace Pure.DI.UsageTests.Basics.CompositionArgumentsScenario {
class BankGateway {
<<class>>
+BankGateway(Int32 timeoutSeconds, String gatewayUrl)
}
class Composition {
<<partial>>
+IPaymentProcessor PaymentService
}
class IBankGateway {
<<interface>>
}
class IPaymentProcessor {
<<interface>>
}
class PaymentProcessor {
<<class>>
+PaymentProcessor(String token, IBankGateway gateway)
}
}
namespace System {
class Int32 {
<<struct>>
}
class String {
<<class>>
}
}
See also: