Decorator
Namespace: DesignPatterns.Structural
Overview
Stack cross-cutting behaviors around a core service without subclass explosion.
Runtime
IDecorator<T>— decorator contractIAsyncDecorator<T>— async decorator contract (DecorateAsyncwithCancellationToken)DecoratorStackBuilder<T>— ordered composition; optionalAdd(..., Func<bool>)skips a decorator when the predicate is false at build time
Conditional registration
var enableMetrics = configuration.GetValue<bool>("Metrics:Enabled");
var service = new DecoratorStackBuilder<IPaymentService>()
.Add<LoggingPaymentDecorator>()
.Add<MetricsPaymentDecorator>(() => enableMetrics)
.Build(new PaymentService());Generated {Contract}DecoratorStack.Build(core) always includes every [Decorator] type; use the manual builder for runtime switches.
Source generator
Mark each decorator with [Decorator<TContract>(order)] (or non-generic on netstandard2.0). The generator emits:
{Contract}DecoratorStack.Build(core)— ordered stack{Contract}DecoratorOrder—public const intfields named after each decorator type
[Decorator<IPaymentService>(10)]
public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator<IPaymentService> { ... }
// Elsewhere (after generation):
Console.WriteLine(PaymentServiceDecoratorOrder.LoggingPaymentDecorator);Lower order wraps closer to the core; outer decorators run first on the way in. Duplicate orders are reported as DP016.
Diagnostics
DP016–DP019. DP042 for async signature validation. DP043 for DI resolvability.
DI integration
When the DesignPatterns.Extensions.DependencyInjection package is referenced, the source generator emits a RegisterDi(IServiceCollection, ServiceLifetime) method for each service contract. This registers all decorators with the DI container and enables Build(IServiceProvider, core) to resolve decorators from the container.
PaymentServiceDecoratorStack.RegisterDi(services);
var provider = services.BuildServiceProvider();
var core = new PaymentService();
var service = PaymentServiceDecoratorStack.Build(provider, core);Decorators must be registered in the container before calling Build(IServiceProvider, core). The core service itself is not registered automatically — you provide it explicitly to Build.
Async variant
For asynchronous decoration scenarios, implement IAsyncDecorator<T> instead of IDecorator<T>. The async decorator uses DecorateAsync instead of Decorate:
public interface IAsyncDecorator<TService>
{
ValueTask<TService> DecorateAsync(TService inner, CancellationToken cancellationToken = default);
}
[Decorator<IPaymentService>(10)]
public sealed class AsyncCachingPaymentDecorator : IPaymentService, IAsyncDecorator<IPaymentService>
{
public async ValueTask<IPaymentService> DecorateAsync(IPaymentService inner, CancellationToken ct = default)
{
await InitializeCacheAsync(ct);
return this;
}
}The source generator validates async decorator signatures (DP042–DP043). Use IAsyncDecorator<T> when decorator initialization requires async work (loading configuration, warming caches, establishing connections). For simple synchronous wrapping, IDecorator<T> is sufficient.
Sample
DesignPatterns.Samples.Decorator — generated stack + order constants, conditional Add, and core comparison.
Maintainer doc: docs/Decorator.md (中文).