-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Add a source generator that produces boilerplate-free, GoF-consistent Proxy pattern implementations for interfaces and abstract classes.
The generator lives in PatternKit.Generators and emits self-contained C# with no runtime PatternKit dependency.
Primary goals:
- Generate typed proxies without runtime reflection/dynamic dispatch.
- Support common proxy use cases (logging, timing, retries, caching, auth, circuit breaker).
- Provide deterministic interceptor ordering and clear semantics.
- Support sync + async methods (favoring
ValueTaskwhere possible).
Motivation / Problem
Proxies are commonly implemented via:
- runtime dynamic proxies/reflection emit
- hand-written wrapper classes that drift and break
- inconsistent async handling and exception semantics
We want a generator that:
- works with trimming/AOT constraints
- keeps proxies debuggable (readable output)
- is deterministic and testable
Supported Targets (must-have)
The generator must support proxying:
interfaceabstract class(virtual/abstract members only)
Proxy generation should be opt-in via attribute on the contract type.
Proposed User Experience
A) Simple proxy generation
[GenerateProxy]
public partial interface IUserService
{
User Get(Guid id);
ValueTask<User> GetAsync(Guid id, CancellationToken ct = default);
}Generated (representative shape):
public sealed partial class UserServiceProxy : IUserService
{
public UserServiceProxy(IUserService inner, IUserServiceInterceptor? interceptor = null);
public User Get(Guid id);
public ValueTask<User> GetAsync(Guid id, CancellationToken ct = default);
}
public interface IUserServiceInterceptor
{
// Sync
void Before(MethodContext context);
void After(MethodContext context);
void OnException(MethodContext context, Exception ex);
// Async
ValueTask BeforeAsync(MethodContext context, CancellationToken ct);
ValueTask AfterAsync(MethodContext context, CancellationToken ct);
ValueTask OnExceptionAsync(MethodContext context, Exception ex, CancellationToken ct);
}B) Multiple interceptors with deterministic ordering
[GenerateProxy(InterceptorMode = ProxyInterceptorMode.Pipeline)]
public partial interface IUserService { /* ... */ }Generated supports:
IReadOnlyList<IUserServiceInterceptor>- deterministic pipeline order (documented)
Attributes / Surface Area
Namespace: PatternKit.Generators.Proxy
Core
-
[GenerateProxy]on contract typestring? ProxyTypeName(default:<ContractName>Proxy)ProxyInterceptorMode InterceptorMode(default:Single)bool GenerateAsync(default: inferred)bool ForceAsync(default: false)ProxyExceptionPolicy Exceptions(default:Rethrow)
Enums:
ProxyInterceptorMode:None,Single,PipelineProxyExceptionPolicy:Rethrow,Swallow(discouraged; require explicit opt-in)
Optional:
[ProxyIgnore]for members that must not be proxied.
Semantics (must-have)
Member coverage
- Interfaces: proxy all methods + properties.
- Abstract classes: proxy only virtual/abstract members.
- Events are v2 (unless trivial).
Ordering
For pipeline mode:
-
interceptors[0]is outermost by default. -
Before/After semantics must be deterministic:
- Before called in ascending order.
- After called in descending order.
Exceptions
- Default: rethrow.
OnExceptioninvoked deterministically.
Async
- If any member returns
Task/ValueTaskOR hasCancellationToken, generator emits async-capable interceptor path. - Prefer
ValueTaskin generated interceptors and wrappers. - Do not “rewrite” consumer signatures (proxy implements exact contract).
Context
Generated MethodContext must include:
- method identifier (stable string or enum)
- arguments (typed accessors preferred; object[] fallback allowed)
- optional return value
V1 recommendation:
- generate a strongly-typed context per method to avoid object[] allocations in hot paths.
Diagnostics (must-have)
Stable IDs, actionable:
PKPRX001Type marked[GenerateProxy]must bepartial.PKPRX002Unsupported member kind (e.g., event) for v1.PKPRX003Member not accessible for proxy generation.PKPRX004Proxy type name conflicts with existing type.PKPRX005Async member detected but async interception disabled (enable GenerateAsync/ForceAsync).
Generated Code Layout
ContractName.Proxy.g.csContractName.Proxy.Interceptor.g.cs(if generating interceptor interfaces/types)
Determinism:
- stable emission order by fully-qualified member name.
Testing Expectations
-
Proxy delegates calls to inner.
-
Interceptor ordering is correct (before asc, after desc).
-
Exception behavior:
- OnException called
- exception rethrown by default
-
Async proxy:
- awaits properly
- cancellation token flows
-
Diagnostics:
- unsupported members
- conflicts
Acceptance Criteria
-
[GenerateProxy]works for interfaces and abstract classes. - Proxy implements exact contract signatures.
- Optional interceptor support with deterministic ordering.
- Async path supported, favoring
ValueTaskfor generated hooks. - Reflection-free implementation.
- Diagnostics cover unsupported/invalid usage.
- Tests cover delegation, ordering, exceptions, and async.