鸭子类型与 await

前言 最近在逛 Reddit 时,看到一个有趣的帖子: What is the lowest effort, highest impact helper method you’ve ever written? 大家讨论了许多常用的扩展方法,其中有一个非常亮眼的例子: public static TaskAwaiter<(T1, T2)> GetAwaiter<T1, T2>(this (Task<T1>, Task<T2>) tasks) => WhenAllResult(tasks).GetAwaiter(); public static async Task<(T1, T2)> WhenAllResult<T1, T2>(this (Task<T1>, Task<T2>) tasks) { await Task.WhenAll(tasks.Item1, tasks.Item2).ConfigureAwait(false); return (tasks.Item1.Result, tasks.Item2.Result); } var (result1, result2) = await ( GetDataAsync(), GetOtherDataAsync() ); 这样就可以通过元组优雅地获取并发结果。那么问题来了,这究竟是如何实现的? 原理在于 await 关键字实际上采用了“鸭子类型”模型,所以我们才能 await 一个元组。 什么是鸭子类型? 鸭子类型的定义是:只要一个对象“看起来像鸭子、叫起来像鸭子”,就可以当作鸭子用。也就是说,不要求类型继承某个接口或基类,只要拥有所需的方法和属性即可。 上述代码之所以能正常运行,是因为我们实现了 GetAwaiter 方法,并且返回的 Awaiter 拥有 IsCompleted、GetResult() 和 OnCompleted() 方法。在 C# 中,Awaiter 还必须实现 INotifyCompletion,否则编译器会报错。这正是鸭子类型的体现:只要有 GetAwaiter,就能被 await。 下面我们写一个简单的 Demo 体验一下: public class DemoAwaiter<TResult> : System.Runtime.CompilerServices.INotifyCompletion { private TResult _num; public DemoAwaiter(TResult num) => _num = num; public bool IsCompleted => true; public TResult GetResult() => _num; public void OnCompleted(Action continuation) { Console.WriteLine("Continuation registered."); continuation?.Invoke(); } } // 自定义 Awaitable public class DemoAwaitable<TResult> { private TResult _num; public DemoAwaitable(TResult num) => _num = num; public DemoAwaiter<TResult> GetAwaiter() => new DemoAwaiter<TResult>(_num); } public class Program { public static async Task Main(string[] args) { var result = await new DemoAwaitable<int>(123); Console.WriteLine($"Result: {result}"); } } 运行结果: ...

2024-08-13 · 2 min · 270 words · yess