The PerResolve lifetime ensures that there will be one instance of the dependency for each composition root instance.
using Shouldly;
using Pure.DI;
using static Pure.DI.Lifetime;
DI.Setup(nameof(Composition))
// PerResolve = one "planning session" per root access.
// Imagine: each time you ask for a plan, you get a fresh context.
.Bind().As(PerResolve).To<RoutePlanningSession>()
// Singleton = created once per Composition instance.
// Here it intentionally captures session when it's created the first time
// (this is a realistic pitfall: singleton accidentally holds request-scoped state).
.Bind().As(Singleton).To<(IRoutePlanningSession s3, IRoutePlanningSession s4)>()
// Composition root
.Root<TrainTripPlanner>("Planner");
var composition = new Composition();
// First "user request": plan a trip now
var plan1 = composition.Planner;
// In the same request, PerResolve dependencies are the same instance:
plan1.SessionForOutbound.ShouldBe(plan1.SessionForReturn);
// Tuple is Singleton, so both entries are the same captured instance:
plan1.CapturedSessionA.ShouldBe(plan1.CapturedSessionB);
// Because the singleton tuple was created during the first request,
// it captured THAT request's PerResolve session:
plan1.SessionForOutbound.ShouldBe(plan1.CapturedSessionA);
// Second "user request": plan another trip (new root access)
var plan2 = composition.Planner;
// New request => new PerResolve session:
plan2.SessionForOutbound.ShouldNotBe(plan1.SessionForOutbound);
// But the singleton still holds the old captured session from the first request:
plan2.CapturedSessionA.ShouldBe(plan1.CapturedSessionA);
plan2.SessionForOutbound.ShouldNotBe(plan2.CapturedSessionA);
// A request-scoped context: e.g., contains "now", locale, pricing rules version,
// feature flags, etc. You typically want a new one per route planning request.
interface IRoutePlanningSession;
class RoutePlanningSession : IRoutePlanningSession;
// A service that plans a train trip.
// It asks for two session instances to demonstrate PerResolve:
// both should be the same within a single request.
class TrainTripPlanner(
IRoutePlanningSession sessionForOutbound,
IRoutePlanningSession sessionForReturn,
(IRoutePlanningSession capturedA, IRoutePlanningSession capturedB) capturedSessions)
{
public IRoutePlanningSession SessionForOutbound { get; } = sessionForOutbound;
public IRoutePlanningSession SessionForReturn { get; } = sessionForReturn;
// These come from a singleton tuple - effectively "global cached" instances.
public IRoutePlanningSession CapturedSessionA { get; } = capturedSessions.capturedA;
public IRoutePlanningSession CapturedSessionB { get; } = capturedSessions.capturedB;
}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 runNote
PerResolve lifetime is useful when you want to share a dependency instance within a single composition root resolution.
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 (IRoutePlanningSession s3, IRoutePlanningSession s4) _singletonValueTuple65;
private bool _singletonValueTuple65Created;
public TrainTripPlanner Planner
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var perResolveRoutePlanningSession624 = default(RoutePlanningSession);
if (!_singletonValueTuple65Created)
lock (_lock)
if (!_singletonValueTuple65Created)
{
EnsureRoutePlanningSessionExists();
_singletonValueTuple65 = (perResolveRoutePlanningSession624, perResolveRoutePlanningSession624);
Thread.MemoryBarrier();
_singletonValueTuple65Created = true;
}
EnsureRoutePlanningSessionExists();
return new TrainTripPlanner(perResolveRoutePlanningSession624, perResolveRoutePlanningSession624, _singletonValueTuple65);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void EnsureRoutePlanningSessionExists()
{
if (perResolveRoutePlanningSession624 is null)
lock (_lock)
if (perResolveRoutePlanningSession624 is null)
{
perResolveRoutePlanningSession624 = new RoutePlanningSession();
}
}
}
}
}Class diagram:
---
config:
maxTextSize: 2147483647
maxEdges: 2147483647
class:
hideEmptyMembersBox: true
---
classDiagram
RoutePlanningSession --|> IRoutePlanningSession
ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ --|> IStructuralComparable
ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ --|> IStructuralEquatable
ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ --|> IComparable
ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ --|> IComparableᐸValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳᐳ
ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ --|> IEquatableᐸValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳᐳ
ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ --|> ITuple
Composition ..> TrainTripPlanner : TrainTripPlanner Planner
TrainTripPlanner o-- "2 PerResolve instances" RoutePlanningSession : IRoutePlanningSession
TrainTripPlanner o-- "Singleton" ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ : ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ
ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ o-- "2 PerResolve instances" RoutePlanningSession : IRoutePlanningSession
namespace Pure.DI.UsageTests.Lifetimes.PerResolveScenario {
class Composition {
<<partial>>
+TrainTripPlanner Planner
}
class IRoutePlanningSession {
<<interface>>
}
class RoutePlanningSession {
<<class>>
+RoutePlanningSession()
}
class TrainTripPlanner {
<<class>>
+TrainTripPlanner(IRoutePlanningSession sessionForOutbound, IRoutePlanningSession sessionForReturn, ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ capturedSessions)
}
}
namespace System {
class IComparable {
<<interface>>
}
class IComparableᐸValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳᐳ {
<<interface>>
}
class IEquatableᐸValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳᐳ {
<<interface>>
}
class ValueTupleᐸIRoutePlanningSessionˏIRoutePlanningSessionᐳ {
<<struct>>
+ValueTuple(IRoutePlanningSession item1, IRoutePlanningSession item2)
}
}
namespace System.Collections {
class IStructuralComparable {
<<interface>>
}
class IStructuralEquatable {
<<interface>>
}
}
namespace System.Runtime.CompilerServices {
class ITuple {
<<interface>>
}
}