Attributes API Reference¶
DecoWeaver provides attributes for marking classes to be decorated. These attributes are compile-time only and have zero runtime footprint.
DecoratedByAttribute¶
Generic attribute for specifying a decorator type.
Syntax¶
Type Parameters¶
TDecorator: The type of decorator to apply. Must implement the same interface as the decorated class.
Properties¶
Order¶
Controls the order in which multiple decorators are applied. Lower values are applied first (closer to the implementation).
Default: 0
Example:
[DecoratedBy<LoggingDecorator>(Order = 1)] // Applied first (innermost)
[DecoratedBy<CachingDecorator>(Order = 2)] // Applied second
[DecoratedBy<MetricsDecorator>(Order = 3)] // Applied third (outermost)
public class UserRepository : IUserRepository { }
Examples¶
Basic Usage¶
using DecoWeaver.Attributes;
[DecoratedBy<LoggingRepository>]
public class UserRepository : IUserRepository
{
// Implementation
}
Multiple Decorators¶
[DecoratedBy<LoggingRepository>]
[DecoratedBy<CachingRepository>]
public class UserRepository : IUserRepository
{
// Implementation
}
With Order¶
[DecoratedBy<LoggingRepository>(Order = 1)]
[DecoratedBy<CachingRepository>(Order = 2)]
public class UserRepository : IUserRepository
{
// Implementation
}
Open Generics¶
[DecoratedBy<CachingRepository<>>]
public class Repository<T> : IRepository<T> where T : class
{
// Implementation
}
Requirements¶
- TDecorator must implement the same interface as the decorated class
- TDecorator must have a constructor accepting the interface as first parameter
- Target class must be concrete (not abstract or interface)
- Project must target C# 11+ for generic attributes
Compile-Time Behavior¶
This attribute is marked with [Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")], meaning:
- The attribute does not exist in the compiled assembly
- No runtime reflection is possible on this attribute
- Zero metadata footprint
- Only affects compile-time code generation
Diagnostics¶
SCULPT002: Decorator must implement service interface
// Error: CachingRepository doesn't implement IUserRepository
[DecoratedBy<CachingRepository>]
public class UserRepository : IUserRepository { }
SCULPT003: Decorator must accept service interface in constructor
// Error: Constructor doesn't accept IUserRepository
public class LoggingRepository : IUserRepository
{
public LoggingRepository(ILogger logger) { } // Missing IUserRepository parameter
}
DecoratedByAttribute (Non-Generic)¶
Non-generic attribute for specifying a decorator type using typeof().
Syntax¶
Constructor¶
Parameters¶
decoratorType: TheTypeof the decorator to apply. Must implement the same interface as the decorated class.
Properties¶
Order¶
Controls the order in which multiple decorators are applied. Lower values are applied first (closer to the implementation).
Default: 0
Examples¶
Basic Usage¶
using DecoWeaver.Attributes;
[DecoratedBy(typeof(LoggingRepository))]
public class UserRepository : IUserRepository
{
// Implementation
}
Multiple Decorators¶
[DecoratedBy(typeof(LoggingRepository), Order = 1)]
[DecoratedBy(typeof(CachingRepository), Order = 2)]
public class UserRepository : IUserRepository
{
// Implementation
}
Open Generics¶
[DecoratedBy(typeof(CachingRepository<>))]
public class Repository<T> : IRepository<T> where T : class
{
// Implementation
}
Generic vs Non-Generic¶
Both forms are equivalent. Choose based on your preference:
// Generic - type-safe at compile time
[DecoratedBy<LoggingRepository>]
// Non-generic - same behavior
[DecoratedBy(typeof(LoggingRepository))]
Recommendation: Use the generic form (DecoratedBy<T>) for better type safety and IntelliSense support.
AttributeUsage¶
Both attribute forms can be applied multiple times to a class:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class DecoratedByAttribute<T> : Attribute { }
Properties¶
- Target:
AttributeTargets.Class- Can only be applied to classes - AllowMultiple:
true- Multiple decorators can be applied - Inherited:
false- Not inherited by derived classes
Implications¶
AllowMultiple = true¶
You can stack multiple decorators:
[DecoratedBy<Decorator1>]
[DecoratedBy<Decorator2>]
[DecoratedBy<Decorator3>]
public class MyService : IMyService { }
Inherited = false¶
Decorators are not inherited:
[DecoratedBy<LoggingDecorator>]
public class BaseRepository : IRepository { }
// This is NOT decorated - must apply attribute explicitly
public class UserRepository : BaseRepository { }
// This IS decorated
[DecoratedBy<LoggingDecorator>]
public class UserRepository : BaseRepository { }
Best Practices¶
Use Explicit Ordering¶
// ✅ Good: Clear order
[DecoratedBy<LoggingDecorator>(Order = 10)]
[DecoratedBy<CachingDecorator>(Order = 20)]
[DecoratedBy<MetricsDecorator>(Order = 30)]
// ❌ Confusing: What's the order?
[DecoratedBy<LoggingDecorator>]
[DecoratedBy<CachingDecorator>]
[DecoratedBy<MetricsDecorator>]
Document Ordering Strategy¶
// Order strategy:
// 1-9: Core infrastructure (transactions, connections)
// 10-19: Performance (caching)
// 20-29: Observability (logging, metrics)
// 30-39: Security (auth, validation)
[DecoratedBy<TransactionDecorator>(Order = 1)]
[DecoratedBy<CachingDecorator>(Order = 10)]
[DecoratedBy<LoggingDecorator>(Order = 20)]
[DecoratedBy<AuthorizationDecorator>(Order = 30)]
public class OrderService : IOrderService { }
Prefer Generic Form¶
// ✅ Preferred: Better IntelliSense and type safety
[DecoratedBy<LoggingRepository>]
// ⚠️ Works but less ideal
[DecoratedBy(typeof(LoggingRepository))]
DecorateServiceAttribute¶
Assembly-level attribute for applying decorators to all implementations of a service interface.
Syntax¶
[assembly: DecorateService(typeof(TService), typeof(TDecorator))]
[assembly: DecorateService(typeof(TService), typeof(TDecorator), Order = int)]
Constructor¶
Parameters¶
serviceType: The service interface type (can be open generic likeIRepository<>)decoratorType: The decorator type to apply (can be open generic likeCachingRepository<>)
Properties¶
Order¶
Controls the order when multiple assembly-level decorators are applied. Lower values are applied first (closer to the implementation).
Default: 0
Target¶
- Target:
AttributeTargets.Assembly- Applied to assemblies - AllowMultiple:
true- Multiple decorators can be defined - Inherited:
false- Not inherited
Examples¶
Basic Usage¶
using DecoWeaver.Attributes;
// Apply logging to all IRepository<T> implementations
[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
Multiple Decorators¶
// Apply logging, caching, and metrics to all repositories
[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 1)]
[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>), Order = 2)]
[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>), Order = 3)]
Non-Generic Services¶
// Apply to non-generic service
[assembly: DecorateService(typeof(IUserService), typeof(LoggingUserService))]
Mixed Generic/Non-Generic¶
// Open generic service, closed generic decorator
[assembly: DecorateService(typeof(IRepository<>), typeof(CachingUserRepository))]
Behavior¶
At compile time, DecoWeaver:
- Discovers all
[assembly: DecorateService(...)]attributes - Finds DI registrations matching the service type
- Merges with class-level
[DecoratedBy]attributes - Generates interceptor code applying all decorators in order
Type Matching¶
Assembly-level decorators match registrations based on:
- Open generic
IRepository<>matches all closed registrations (IRepository<User>,IRepository<Order>, etc.) - Closed generic
IRepository<User>matches onlyIRepository<User> - Non-generic
IUserServicematches exactly
Combining with Class-Level¶
Assembly-level and class-level decorators are merged:
// GlobalUsings.cs
[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 10)]
// UserRepository.cs
[DecoratedBy<ValidationRepository<User>>(Order = 5)]
public class UserRepository : IRepository<User> { }
Resulting chain: LoggingRepository<User> → ValidationRepository<User> → UserRepository
Compile-Time Behavior¶
This attribute is marked with [Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")], meaning:
- The attribute does not exist in the compiled assembly
- No runtime reflection is possible
- Zero metadata footprint
- Only affects compile-time code generation
Requirements¶
- Decorator must implement service interface
- Decorator must have constructor accepting interface as first parameter
- Only intercepts closed generic registrations (
AddScoped<IRepo<T>, Impl<T>>()) - Does not intercept open generic registrations (
AddScoped(typeof(IRepo<>), typeof(Impl<>)))
Best Practices¶
- Group by concern - Keep related decorators together
- Use gaps in Order - Reserve ranges (10-19 for logging, 20-29 for caching, etc.)
- Document your strategy - Comment why decorators are applied assembly-wide
- Centralize location - Keep all assembly attributes in
GlobalUsings.csor similar
SkipAssemblyDecorationAttribute¶
Class-level attribute for opting out of all assembly-level decorators.
Syntax¶
Constructor¶
No parameters required.
Target¶
- Target:
AttributeTargets.Class- Applied to classes - AllowMultiple:
false- Can only be applied once per class - Inherited:
false- Not inherited
Examples¶
Skip All Assembly Decorators¶
// GlobalUsings.cs
[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>))]
// UserRepository.cs - gets all assembly decorators
public class UserRepository : IRepository<User> { }
// OrderRepository.cs - skips ALL assembly decorators
[SkipAssemblyDecoration]
public class OrderRepository : IRepository<Order> { }
Result: - UserRepository: Decorated with Logging, Caching, and Metrics - OrderRepository: No decorators applied
Combined with Class-Level Decorators¶
// Skip assembly decorators, use class-level instead
[SkipAssemblyDecoration]
[DecoratedBy<ValidationRepository<Product>>]
public class ProductRepository : IRepository<Product>
{
// Only ValidationRepository is applied (class-level)
}
Behavior¶
At compile time, DecoWeaver:
- Discovers all
[SkipAssemblyDecoration]attributes on classes - Excludes ALL assembly-level decorators for that implementation
- Still applies any class-level
[DecoratedBy]decorators - Generates interceptor code with only class-level decorators
Scope¶
- Affects: Only assembly-level
[DecorateService]decorators - Does NOT affect: Class-level
[DecoratedBy]decorators - Isolation: Only affects the specific class it's applied to
Use Cases¶
- Performance-critical implementations - Zero decorator overhead
- Completely different decoration strategy - Use class-level decorators instead
- Clean slate needed - Start fresh without assembly defaults
- Legacy code - Gradual migration to new decoration patterns
Compile-Time Behavior¶
This attribute is marked with [Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")], meaning:
- The attribute does not exist in the compiled assembly
- No runtime reflection is possible
- Zero metadata footprint
- Only affects compile-time code generation
Comparison with DoNotDecorate¶
| Attribute | Scope | Use When |
|---|---|---|
[SkipAssemblyDecoration] | Removes ALL assembly decorators | Need clean slate or most decorators excluded |
[DoNotDecorate(typeof(...))] | Removes specific decorator(s) | Need to exclude 1-2 decorators |
Best Practices¶
- Use for clean slate - When you want zero assembly decorators
- Combine with class-level - Apply implementation-specific decorators
- Document why - Add comments explaining the opt-out reason
- Prefer DoNotDecorate for surgical exclusions - If only removing 1-2 decorators
DoNotDecorateAttribute¶
Class-level attribute for excluding specific decorators from an implementation.
Syntax¶
Constructor¶
Parameters¶
decoratorType: The decorator type to exclude (can be open generic likeCachingRepository<>)
Target¶
- Target:
AttributeTargets.Class- Applied to classes - AllowMultiple:
true- Multiple decorators can be excluded - Inherited:
false- Not inherited
Examples¶
Opt Out of Assembly-Level Decorator¶
// GlobalUsings.cs
[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
// UserRepository.cs - gets caching
public class UserRepository : IRepository<User> { }
// OrderRepository.cs - opts out of caching
[DoNotDecorate(typeof(CachingRepository<>))]
public class OrderRepository : IRepository<Order> { }
Multiple Opt-Outs¶
// Opt out of caching and metrics
[DoNotDecorate(typeof(CachingRepository<>))]
[DoNotDecorate(typeof(MetricsRepository<>))]
public class OrderRepository : IRepository<Order> { }
Open Generic Matching¶
// Open generic in DoNotDecorate matches all closed generics
[DoNotDecorate(typeof(CachingRepository<>))] // Matches CachingRepository<User>
public class UserRepository : IRepository<User> { }
Behavior¶
At compile time, DecoWeaver:
- Discovers all
[DoNotDecorate]attributes on classes - Collects decorators from class-level and assembly-level sources
- Filters out decorators matching the
DoNotDecoratedirectives - Generates interceptor code with only remaining decorators
Type Matching Rules¶
- Open generic
typeof(Decorator<>)matches all closed variants - Closed generic
typeof(Decorator<User>)matches only that specific type - Non-generic types match exactly
- Type matching is by definition (ignoring type arguments)
Isolation¶
[DoNotDecorate] only affects the specific class it's applied to:
[DoNotDecorate(typeof(CachingRepository<>))]
public class OrderRepository : IRepository<Order> { }
// UserRepository still gets caching (not affected)
public class UserRepository : IRepository<User> { }
Use Cases¶
- Excluding assembly-level decorators - Primary use case
- Performance-critical code - Remove observability overhead
- Implementation-specific constraints - Special requirements
- Testing implementations - Simplify test setup
Compile-Time Behavior¶
This attribute is marked with [Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")], meaning:
- The attribute does not exist in the compiled assembly
- No runtime reflection is possible
- Zero metadata footprint
- Only affects compile-time code generation
Best Practices¶
- Use sparingly - If many implementations opt out, reconsider assembly-level decorator
- Document why - Comment explaining the opt-out reason
- Exact type match - Ensure decorator type exactly matches what's being applied
- Prefer class-level for specific needs - Use assembly-level for cross-cutting concerns
See Also¶
- Class-Level Decorators - Usage guide
- Assembly-Level Decorators - Assembly-wide decoration
- Opt-Out - Excluding decorators
- Multiple Decorators - Stacking decorators
- Order and Nesting - Understanding order