This example shows several ways to define composition roots as explicit entry points into the graph.
Tip
There is no hard limit on roots, but prefer a small number. Ideally, an application has a single composition root.
In classic DI containers, the composition is resolved dynamically via calls like T Resolve<T>() or object GetService(Type type). In Pure.DI, each root generates a property or method at compile time, so roots are explicit and discoverable.
using Pure.DI;
DI.Setup(nameof(Composition))
.Bind<IInvoiceGenerator>().To<PdfInvoiceGenerator>()
.Bind<IInvoiceGenerator>("Online").To<HtmlInvoiceGenerator>()
.Bind<ILogger>().To<FileLogger>()
// Specifies to create a regular composition root
// of type "IInvoiceGenerator" with the name "InvoiceGenerator".
// This will be the main entry point for invoice generation.
.Root<IInvoiceGenerator>("InvoiceGenerator")
// Specifies to create an anonymous composition root
// that is only accessible from "Resolve()" methods.
// This is useful for auxiliary types or testing.
.Root<ILogger>()
// Specifies to create a regular composition root
// of type "IInvoiceGenerator" with the name "OnlineInvoiceGenerator"
// using the "Online" tag to differentiate implementations.
.Root<IInvoiceGenerator>("OnlineInvoiceGenerator", "Online");
var composition = new Composition();
// Resolves the default invoice generator (PDF) with all its dependencies
// invoiceGenerator = new PdfInvoiceGenerator(new FileLogger());
var invoiceGenerator = composition.InvoiceGenerator;
// Resolves the online invoice generator (HTML)
// onlineInvoiceGenerator = new HtmlInvoiceGenerator();
var onlineInvoiceGenerator = composition.OnlineInvoiceGenerator;
// All and only the roots of the composition
// can be obtained by Resolve method.
// Here we resolve the private root 'ILogger'.
var logger = composition.Resolve<ILogger>();
// We can also resolve tagged roots dynamically if needed
var tagged = composition.Resolve<IInvoiceGenerator>("Online");
// Common logger interface used across the system
interface ILogger;
// Concrete implementation of a logger that writes to a file
class FileLogger : ILogger;
// Abstract definition of an invoice generator
interface IInvoiceGenerator;
// Implementation for generating PDF invoices, dependent on ILogger
class PdfInvoiceGenerator(ILogger logger) : IInvoiceGenerator;
// Implementation for generating HTML invoices for online viewing
class HtmlInvoiceGenerator : IInvoiceGenerator;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- Add a reference to the NuGet package
dotnet add package Pure.DI- Copy the example code into the Program.cs file
You are ready to run the example 🚀
dotnet runThe name of the composition root is arbitrarily chosen depending on its purpose but should be restricted by the property naming conventions in C# since it is the same name as a property in the composition class. In reality, the Root property has the form:
public IService Root
{
get
{
return new Service(new Dependency());
}
}To avoid generating Resolve methods just add a comment // Resolve = Off before a Setup method:
// Resolve = Off
DI.Setup("Composition")
.Bind<IDependency>().To<Dependency>()
...This can be done if these methods are not needed, in case only certain composition roots are used. It's not significant then, but it will help save resources during compilation. Limitations: too many public roots increase composition API surface and make architecture boundaries harder to track. Common pitfalls:
- Exposing internal services as roots instead of keeping them private.
- Depending on
Resolveeverywhere instead of explicit root members. See also: Resolve methods, Root arguments.
The following partial class will be generated:
partial class Composition
{
public IInvoiceGenerator InvoiceGenerator
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return new PdfInvoiceGenerator(new FileLogger());
}
}
public IInvoiceGenerator OnlineInvoiceGenerator
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return new HtmlInvoiceGenerator();
}
}
private LightweightRoot LightRoot
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Func<ILogger> perBlockFunc279 = new Func<ILogger>(
[MethodImpl(MethodImplOptions.AggressiveInlining)]
() =>
{
return new FileLogger();
});
return new LightweightRoot()
{
ILogger2 = perBlockFunc279
};
}
}
private ILogger Root1
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return LightRoot.ILogger2();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Resolve<T>()
{
return Resolver<T>.Value.Resolve(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Resolve<T>(object? tag)
{
return Resolver<T>.Value.ResolveByTag(this, tag);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public object Resolve(Type type)
{
#if NETCOREAPP3_0_OR_GREATER
var index = (int)(_bucketSize * (((uint)type.TypeHandle.GetHashCode()) % 4));
#else
var index = (int)(_bucketSize * (((uint)RuntimeHelpers.GetHashCode(type)) % 4));
#endif
ref var pair = ref _buckets[index];
return Object.ReferenceEquals(pair.Key, type) ? pair.Value.Resolve(this) : Resolve(type, index);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private object Resolve(Type type, int index)
{
var finish = index + _bucketSize;
while (++index < finish)
{
ref var pair = ref _buckets[index];
if (Object.ReferenceEquals(pair.Key, type))
{
return pair.Value.Resolve(this);
}
}
throw new CannotResolveException($"{CannotResolveMessage} {OfTypeMessage} {type}.", type, null);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public object Resolve(Type type, object? tag)
{
#if NETCOREAPP3_0_OR_GREATER
var index = (int)(_bucketSize * (((uint)type.TypeHandle.GetHashCode()) % 4));
#else
var index = (int)(_bucketSize * (((uint)RuntimeHelpers.GetHashCode(type)) % 4));
#endif
ref var pair = ref _buckets[index];
return Object.ReferenceEquals(pair.Key, type) ? pair.Value.ResolveByTag(this, tag) : Resolve(type, tag, index);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private object Resolve(Type type, object? tag, int index)
{
var finish = index + _bucketSize;
while (++index < finish)
{
ref var pair = ref _buckets[index];
if (Object.ReferenceEquals(pair.Key, type))
{
return pair.Value.ResolveByTag(this, tag);
}
}
throw new CannotResolveException($"{CannotResolveMessage} \"{tag}\" {OfTypeMessage} {type}.", type, tag);
}
private readonly static uint _bucketSize;
private readonly static Pair<IResolver<Composition, object>>[] _buckets;
static Composition()
{
var valResolver_0000 = new Resolver_0000();
Resolver<IInvoiceGenerator>.Value = valResolver_0000;
var valResolver_0001 = new Resolver_0001();
Resolver<ILogger>.Value = valResolver_0001;
_buckets = Buckets<IResolver<Composition, object>>.Create(
4,
out _bucketSize,
new Pair<IResolver<Composition, object>>[2]
{
new Pair<IResolver<Composition, object>>(typeof(IInvoiceGenerator), valResolver_0000)
,new Pair<IResolver<Composition, object>>(typeof(ILogger), valResolver_0001)
});
}
private const string CannotResolveMessage = "Cannot resolve composition root ";
private const string OfTypeMessage = "of type ";
private class Resolver<T>: IResolver<Composition, T>
{
public static IResolver<Composition, T> Value = new Resolver<T>();
public virtual T Resolve(Composition composite)
{
throw new CannotResolveException($"{CannotResolveMessage}{OfTypeMessage}{typeof(T)}.", typeof(T), null);
}
public virtual T ResolveByTag(Composition composite, object tag)
{
throw new CannotResolveException($"{CannotResolveMessage}\"{tag}\" {OfTypeMessage}{typeof(T)}.", typeof(T), tag);
}
}
private sealed class Resolver_0000: Resolver<IInvoiceGenerator>
{
public override IInvoiceGenerator Resolve(Composition composition)
{
return composition.InvoiceGenerator;
}
public override IInvoiceGenerator ResolveByTag(Composition composition, object tag)
{
switch (tag)
{
case "Online":
return composition.OnlineInvoiceGenerator;
case null:
return composition.InvoiceGenerator;
default:
return base.ResolveByTag(composition, tag);
}
}
}
private sealed class Resolver_0001: Resolver<ILogger>
{
public override ILogger Resolve(Composition composition)
{
return composition.Root1;
}
public override ILogger ResolveByTag(Composition composition, object tag)
{
switch (tag)
{
case null:
return composition.Root1;
default:
return base.ResolveByTag(composition, tag);
}
}
}
#pragma warning disable CS0649
private sealed class LightweightRoot: LightweightRoot
{
[OrdinalAttribute()] public Func<ILogger> ILogger2;
}
}Class diagram:
---
config:
maxTextSize: 2147483647
maxEdges: 2147483647
class:
hideEmptyMembersBox: true
---
classDiagram
PdfInvoiceGenerator --|> IInvoiceGenerator
HtmlInvoiceGenerator --|> IInvoiceGenerator : "Online"
FileLogger --|> ILogger
Composition ..> LightweightRoot : LightweightRoot LightRoot120d
Composition ..> HtmlInvoiceGenerator : IInvoiceGenerator OnlineInvoiceGenerator
Composition ..> FileLogger : ILogger _
Composition ..> PdfInvoiceGenerator : IInvoiceGenerator InvoiceGenerator
PdfInvoiceGenerator *-- FileLogger : ILogger
LightweightRoot o-- "PerBlock" FuncᐸILoggerᐳ : FuncᐸILoggerᐳ
FuncᐸILoggerᐳ *-- FileLogger : ILogger
namespace Pure.DI {
class LightweightRoot {
<<class>>
+LightweightRoot()
+FuncᐸILoggerᐳ ILogger2
}
}
namespace Pure.DI.UsageTests.Basics.CompositionRootsScenario {
class Composition {
<<partial>>
+IInvoiceGenerator InvoiceGenerator
-LightweightRoot LightRoot120d
+IInvoiceGenerator OnlineInvoiceGenerator
-ILogger _
+ T ResolveᐸTᐳ()
+ T ResolveᐸTᐳ(object? tag)
+ object Resolve(Type type)
+ object Resolve(Type type, object? tag)
}
class FileLogger {
<<class>>
+FileLogger()
}
class HtmlInvoiceGenerator {
<<class>>
+HtmlInvoiceGenerator()
}
class IInvoiceGenerator {
<<interface>>
}
class ILogger {
<<interface>>
}
class PdfInvoiceGenerator {
<<class>>
+PdfInvoiceGenerator(ILogger logger)
}
}
namespace System {
class FuncᐸILoggerᐳ {
<<delegate>>
}
}