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
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 default tag 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: