如何实现可正确等待反射获取的可等待类型的通用C#装饰器?
如何在C#中实现适配异步方法的通用装饰器?
我正在研究如何在C#中实现适配异步方法的通用装饰器。多数示例使用仅提供同步Invoke方法的DispatchProxy。除非.NET对此进行改进并添加异步支持(相关议题已关闭),否则我们只能采用与普通特定装饰器相同的调用方式。问题似乎在于,由于可等待类型是基于模式实现的,目前没有简洁的方式检测该场景并编写正确代码,希望熟悉反射深层原理的人士能提供思路。
特定场景装饰器示例
手动编写特定场景的装饰器很简单,示例如下:
interface IMyWork { Task DoStuff(); } class MyWork : IMyWork { public async Task DoStuff() { await Something(); } } // 普通装饰器实现 class MyDecoratedWork : IMyWork { private readonly MyWork _real; public async Task DoStuff() { using var scope = Decorate(); await _real.DoStuff(); // 释放操作与Decorate处于同一上下文 - AsyncLocal等状态会被恢复 } private IDisposable Decorate() { /* 返回作用域装饰对象 */ } }
通用DispatchProxy装饰器框架
如果需要大量装饰器,通用实现会更具吸引力,典型的DispatchProxy框架如下:
class DispatchProxyScopedDecorator<TDecorated> : DispatchProxy { private TDecorated? _decorated; private IScopeProvider? _scopeProvider; private void SetParameters(TDecorated decorated, IScopeProvider scopeProvider) { _decorated = decorated; _scopeProvider = scopeProvider; } protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) { // 核心逻辑待实现 } public static TDecorated Create(TDecorated decorated, IScopeProvider scopeProvider) { object created = Create<TDecorated, DispatchProxyScopedDecorator<TDecorated>>()!; ((DispatchProxyScopedDecorator<TDecorated>)created).SetParameters(decorated, scopeProvider); return (TDecorated)created; } }
同步场景的简单实现
同步方法的装饰逻辑很直接:
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) { using var scope = _scopeProvider.Decorate(); return targetMethod?.Invoke(_decorated, args); }
异步场景的实现困境
但Invoke是同步方法,要正确装饰返回可等待类型的方法调用,必须用新的可等待类型拦截返回值。
根据文档,可等待类型的唯一要求是实现GetAwaiter方法(AsyncStateMachineAttribute等属性不一定适用):
var scope = _scopeProvider.Decorate(); var result = targetMethod.Invoke(_decorated, args); if (result is null) { return null; } // 注意:这段代码不完整,还需要查找程序集中的扩展方法! var awaiterMethod = result.GetType().GetMethod("GetAwaiter");
如果awaiterMethod不为null,说明返回值是可等待类型。我理想中易维护的实现是将逻辑封装到异步方法,让编译器处理复杂的等待逻辑:
async Task InterceptAsync(??? awaitable, MyScope scope) { try { await awaitable; } finally { scope.Dispose(); } } if (awaiterMethod is not null) { return InterceptAsync(result, scope); }
但这段代码无法编译,原因很明显——可等待类型并非单一类型,除了已弃用的dynamic外,没有能让编译器接受的类型填入???位置。
像所有装饰器一样,这种方式存在明显缺点(性能损耗、返回类型可能变更),但这种实现方式是否可行?我是否遗漏了其他方法?我的第一反应是,如果原方法返回的不是Task,上述实现会违反预期的方法签名——但这是否意味着每种不同的可等待类型都需要单独实现?
期待您的想法。
内容的提问来源于stack exchange,提问作者naviwhack
相关产品推荐
相关产品推荐

