tags.md
July 5, 2026 ยท View on GitHub
Tags
Tags let you control dependency selection when multiple implementations exist: This is practical for scenarios like public/internal API clients, multiple payment providers, or environment-specific integrations.
using Shouldly;
using Pure.DI;
DI.Setup(nameof(Composition))
// The `default` tag is used when the consumer does not specify a tag
.Bind<IApiClient>("Public", default).To<RestApiClient>()
.Bind<IApiClient>("Internal").As(Lifetime.Singleton).To<InternalApiClient>()
.Bind<IApiFacade>().To<ApiFacade>()
// "InternalRoot" is a root name, "Internal" is a tag
.Root<IApiClient>("InternalRoot", "Internal")
// Specifies to create the composition root named "Root"
.Root<IApiFacade>("Api");
var composition = new Composition();
var api = composition.Api;
api.PublicClient.ShouldBeOfType<RestApiClient>();
api.InternalClient.ShouldBeOfType<InternalApiClient>();
api.InternalClient.ShouldBe(composition.InternalRoot);
api.DefaultClient.ShouldBeOfType<RestApiClient>();
interface IApiClient;
class RestApiClient : IApiClient;
class InternalApiClient : IApiClient;
interface IApiFacade
{
IApiClient PublicClient { get; }
IApiClient InternalClient { get; }
IApiClient DefaultClient { get; }
}
class ApiFacade(
[Tag("Public")] IApiClient publicClient,
[Tag("Internal")] IApiClient internalClient,
IApiClient defaultClient)
: IApiFacade
{
public IApiClient PublicClient { get; } = publicClient;
public IApiClient InternalClient { get; } = internalClient;
public IApiClient DefaultClient { get; } = defaultClient;
}
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
The example shows how to:
- Define multiple bindings for the same interface
- Use tags to differentiate between implementations
- Control lifetime management
- Inject tagged dependencies into constructors
The tag can be a constant, a type, a smart tag, or a value of an Enum type. The default and null tags are also supported.
Limitations: extensive tag usage can become hard to navigate if naming conventions are inconsistent.
Common pitfalls:
- Using many ad-hoc string tags without central conventions.
- Forgetting to define a
defaulttag path for untagged consumers. See also: Smart tags, Composition roots.
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 InternalApiClient? _singletonCompositionWithGenericRootsAndArgsInOtherProject;
public IApiClient InternalRoot
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (_singletonCompositionWithGenericRootsAndArgsInOtherProject is null)
lock (_lock)
if (_singletonCompositionWithGenericRootsAndArgsInOtherProject is null)
{
_singletonCompositionWithGenericRootsAndArgsInOtherProject = new InternalApiClient();
}
return _singletonCompositionWithGenericRootsAndArgsInOtherProject;
}
}
public IApiFacade Api
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (_singletonCompositionWithGenericRootsAndArgsInOtherProject is null)
lock (_lock)
if (_singletonCompositionWithGenericRootsAndArgsInOtherProject is null)
{
_singletonCompositionWithGenericRootsAndArgsInOtherProject = new InternalApiClient();
}
return new ApiFacade(new RestApiClient(), _singletonCompositionWithGenericRootsAndArgsInOtherProject, new RestApiClient());
}
}
}
Class diagram:
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
RestApiClient --|> IApiClient : "Public", default
InternalApiClient --|> IApiClient : "Internal"
ApiFacade --|> IApiFacade
Composition ..> ApiFacade : IApiFacade Api
Composition ..> InternalApiClient : IApiClient InternalRoot
ApiFacade *-- RestApiClient : "Public" IApiClient
ApiFacade *-- RestApiClient : IApiClient
ApiFacade o-- "Singleton" InternalApiClient : "Internal" IApiClient
namespace Pure.DI.UsageTests.Basics.TagsScenario {
class ApiFacade {
<<class>>
+ApiFacade(IApiClient publicClient, IApiClient internalClient, IApiClient defaultClient)
}
class Composition {
<<partial>>
+IApiFacade Api
+IApiClient InternalRoot
}
class IApiClient {
<<interface>>
}
class IApiFacade {
<<interface>>
}
class InternalApiClient {
<<class>>
+InternalApiClient()
}
class RestApiClient {
<<class>>
+RestApiClient()
}
}
See also: