Demonstrates how to create thread-safe overrides in compositions, ensuring that override operations work correctly in multi-threaded scenarios.
using Shouldly;
using Pure.DI;
using System.Collections.Immutable;
DI.Setup(nameof(Composition))
.Bind("Global").To(() => new ProcessingToken("TOKEN-123"))
.Bind().As(Lifetime.Singleton).To<TimeProvider>()
.Bind().To<Func<int, int, IOrderHandler>>(ctx =>
(orderId, customerId) => {
// Retrieves a global processing token to be passed to the handler
ctx.Inject("Global", out ProcessingToken token);
// The factory is invoked in parallel, so we must lock
// the context to safely perform overrides for the specific graph
lock (ctx.Lock)
{
// Overrides the 'int' dependency (OrderId)
ctx.Override(orderId);
// Overrides the tagged 'int' dependency (CustomerId)
ctx.Override(customerId, "customer");
// Overrides the 'string' dependency (TraceId)
ctx.Override($"Order:{orderId}-Cust:{customerId}");
// Overrides the 'ProcessingToken' dependency with the injected value
ctx.Override(token);
// Creates the handler with the overridden dependencies
ctx.Inject<OrderHandler>(out var handler);
return handler;
}
})
.Bind().To<OrderBatchProcessor>()
// Composition root
.Root<IOrderBatchProcessor>("OrderProcessor");
var composition = new Composition();
var orderProcessor = composition.OrderProcessor;
orderProcessor.Handlers.Length.ShouldBe(100);
for (var i = 0; i < 100; i++)
{
orderProcessor.Handlers.Count(h => h.OrderId == i).ShouldBe(1);
}
record ProcessingToken(string Value);
interface ITimeProvider
{
DateTimeOffset Now { get; }
}
class TimeProvider : ITimeProvider
{
public DateTimeOffset Now => DateTimeOffset.Now;
}
interface IOrderHandler
{
string TraceId { get; }
int OrderId { get; }
int CustomerId { get; }
}
class OrderHandler(
string traceId,
ITimeProvider timeProvider,
int orderId,
[Tag("customer")] int customerId,
ProcessingToken token)
: IOrderHandler
{
public string TraceId => traceId;
public int OrderId => orderId;
public int CustomerId => customerId;
}
interface IOrderBatchProcessor
{
ImmutableArray<IOrderHandler> Handlers { get; }
}
class OrderBatchProcessor(Func<int, int, IOrderHandler> orderHandlerFactory)
: IOrderBatchProcessor
{
public ImmutableArray<IOrderHandler> Handlers { get; } =
[
// Simulates parallel processing of orders
..Enumerable.Range(0, 100)
.AsParallel()
.Select(i => orderHandlerFactory(i, 99))
];
}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 Sampledotnet 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 runImportant
Thread-safe overrides are essential when composition instances are shared across multiple threads or when parallel resolution is required.
The following partial class will be generated:
partial class Composition
{
#if NET9_0_OR_GREATER
private readonly Lock _lock = new Lock();
#else
private readonly Object _lock = new Object();
#endif
private TimeProvider? _singletonTimeProvider63;
public IOrderBatchProcessor OrderProcessor
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Func<int, int, IOrderHandler> transientFunc141 =
[MethodImpl(MethodImplOptions.AggressiveInlining)]
(localOrderId, localCustomerId) =>
{
// Retrieves a global processing token to be passed to the handler
ProcessingToken transientProcessingToken142 = new ProcessingToken("TOKEN-123");
ProcessingToken localToken = transientProcessingToken142;
// The factory is invoked in parallel, so we must lock
// the context to safely perform overrides for the specific graph
lock (_lock)
{
// Overrides the 'int' dependency (OrderId)
// Overrides the tagged 'int' dependency (CustomerId)
// Overrides the 'string' dependency (TraceId)
// Overrides the 'ProcessingToken' dependency with the injected value
// Creates the handler with the overridden dependencies
int overriddenInt32 = localOrderId;
int overriddenInt321 = localCustomerId;
string overriddenString2 = $"Order:{localOrderId}-Cust:{localCustomerId}";
ProcessingToken overriddenProcessingToken3 = localToken;
if (_singletonTimeProvider63 is null)
lock (_lock)
if (_singletonTimeProvider63 is null)
{
_singletonTimeProvider63 = new TimeProvider();
}
return new OrderHandler(overriddenString2, _singletonTimeProvider63, overriddenInt32, overriddenInt321, overriddenProcessingToken3);
}
};
return new OrderBatchProcessor(transientFunc141);
}
}
}Class diagram:
---
config:
maxTextSize: 2147483647
maxEdges: 2147483647
class:
hideEmptyMembersBox: true
---
classDiagram
OrderBatchProcessor --|> IOrderBatchProcessor
Composition ..> OrderBatchProcessor : IOrderBatchProcessor OrderProcessor
FuncᐸInt32ˏInt32ˏIOrderHandlerᐳ *-- ProcessingToken : "Global" ProcessingToken
FuncᐸInt32ˏInt32ˏIOrderHandlerᐳ *-- OrderHandler : OrderHandler
OrderBatchProcessor *-- FuncᐸInt32ˏInt32ˏIOrderHandlerᐳ : FuncᐸInt32ˏInt32ˏIOrderHandlerᐳ
OrderHandler o-- "Singleton" TimeProvider : ITimeProvider
OrderHandler *-- Int32 : Int32
OrderHandler *-- Int32 : "customer" Int32
OrderHandler *-- String : String
OrderHandler *-- ProcessingToken : ProcessingToken
namespace Pure.DI.UsageTests.Advanced.ThreadsafeOverridesScenario {
class Composition {
<<partial>>
+IOrderBatchProcessor OrderProcessor
}
class IOrderBatchProcessor {
<<interface>>
}
class OrderBatchProcessor {
<<class>>
+OrderBatchProcessor(FuncᐸInt32ˏInt32ˏIOrderHandlerᐳ orderHandlerFactory)
}
class OrderHandler {
<<class>>
+OrderHandler(String traceId, ITimeProvider timeProvider, Int32 orderId, Int32 customerId, ProcessingToken token)
}
class ProcessingToken {
<<record>>
}
class TimeProvider {
<<class>>
}
}
namespace System {
class FuncᐸInt32ˏInt32ˏIOrderHandlerᐳ {
<<delegate>>
}
class Int32 {
<<struct>>
}
class String {
<<class>>
}
}