如何判断.NET中的对象是否为可等待(Awaitable)对象?
判断对象是否可等待的最佳实践
我有一个返回任意类型对象的方法,需要判断该对象是否为可等待(Awaitable)类型,若是则等待并获取最终结果。仅检查Task/ValueTask及其泛型版本并不足够,因为还存在其他符合可等待约定的类型。我想实现类似如下逻辑,但C#中并没有现成的Awaitable类型:
object result = GetResult(); object actualResult = result is Awaitable awaitable // "Awaitable" 并不存在 ? await awaitable : result;
根据C#语言规范,一个表达式t是可等待的,需满足以下条件之一:
t的编译时类型为dynamict拥有可访问的实例或扩展方法GetAwaiter(无参数、无类型参数),且该方法的返回类型A需同时满足:- 实现
System.Runtime.CompilerServices.INotifyCompletion接口 - 包含可访问的、可读的
bool类型实例属性IsCompleted - 包含可访问的无参数、无类型参数实例方法
GetResult
- 实现
以下是判断对象可等待性的几种最佳方式:
1. 反射+动态调用实现检查与等待
由于可等待性是基于结构约定而非接口实现,我们可以通过反射验证这些约定,再结合dynamic自动处理await逻辑:
辅助检查方法
using System.Reflection; using System.Runtime.CompilerServices; using System.Linq; using System.Collections.Concurrent; public static class AwaitableChecker { // 缓存类型的可等待性检查结果,提升性能 private static readonly ConcurrentDictionary<Type, bool> _awaitableTypeCache = new(); public static bool IsAwaitable(object obj) { if (obj == null) return false; Type type = obj.GetType(); if (_awaitableTypeCache.TryGetValue(type, out bool isAwaitable)) { return isAwaitable; } bool result = CheckAwaitableType(type); _awaitableTypeCache.TryAdd(type, result); return result; } private static bool CheckAwaitableType(Type type) { // 查找实例GetAwaiter方法 MethodInfo getAwaiterMethod = type.GetMethod("GetAwaiter", Type.EmptyTypes); // 实例方法不存在时,查找扩展方法 if (getAwaiterMethod == null) { getAwaiterMethod = FindExtensionGetAwaiterMethod(type); } if (getAwaiterMethod == null) return false; Type awaiterType = getAwaiterMethod.ReturnType; // 验证awaiter的三个核心约定 bool implementsINotifyCompletion = typeof(INotifyCompletion).IsAssignableFrom(awaiterType); PropertyInfo isCompletedProp = awaiterType.GetProperty("IsCompleted", typeof(bool)); MethodInfo getResultMethod = awaiterType.GetMethod("GetResult", Type.EmptyTypes); return implementsINotifyCompletion && isCompletedProp != null && isCompletedProp.CanRead && getResultMethod != null; } private static MethodInfo FindExtensionGetAwaiterMethod(Type targetType) { // 扫描所有已加载程序集中的扩展方法 return AppDomain.CurrentDomain.GetAssemblies() .SelectMany(asm => asm.GetTypes()) .Where(t => t.IsSealed && !t.IsGenericType && !t.IsNested) .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public)) .Where(m => m.IsDefined(typeof(ExtensionAttribute), false)) .Where(m => m.Name == "GetAwaiter" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsAssignableFrom(targetType)) .FirstOrDefault(); } }
使用示例
object result = GetResult(); object actualResult = result; if (AwaitableChecker.IsAwaitable(result)) { dynamic awaitable = result; actualResult = await awaitable; }
这里借助dynamic让编译器自动处理await的完整流程,无需手动调用GetAwaiter、检查IsCompleted等步骤,既简洁又符合规范。
2. 参考ASP.NET Core的内部实现
你提到的ASP.NET MVC控制器处理可等待返回值的逻辑,核心也是基于反射检查可等待约定+动态调用。其内部通过TaskHelper等工具类封装了类似逻辑,本质和上面的实现思路一致——先验证类型是否符合可等待约定,再通过动态类型执行await操作。
关键注意事项
- 扩展方法扫描范围:上述示例扫描了所有已加载程序集的扩展方法,若场景明确,可缩小扫描范围提升性能。
- 性能优化:通过
ConcurrentDictionary缓存类型的可等待性检查结果,避免重复反射操作。 - dynamic的开销:
dynamic会带来少量性能损耗,但在绝大多数业务场景下可忽略不计。
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

