参考:How to write a async method with out parameter?

为什么 C# 异步方法不允许有 ref(out) 参数

Why async methods cannot have ref or out parameters?

As for why async methods don’t support out-by-reference parameters? (or ref parameters?) That’s a limitation of the CLR. We chose to implement async methods in a similar way to iterator methods — i.e. through the compiler transforming the method into a state-machine-object. The CLR has no safe way to store the address of an “out parameter” or “reference parameter” as a field of an object. The only way to have supported out-by-reference parameters would be if the async feature were done by a low-level CLR rewrite instead of a compiler-rewrite. We examined that approach, and it had a lot going for it, but it would ultimately have been so costly that it’d never have happened.

至于为什么异步方法不支持 out(ref) 引用参数?这是 CLR 的限制。我们选择以类似于迭代器方法的方式实现异步方法 —— 即通过编译器将方法转换为状态机对象。CLR 没有安全的方法来将 out 参数或 ref 参数的地址存储为对象的字段。支持 out(ref) 引用参数的唯一方法是重写底层 CLR(而非重写编译器)来实现 async。我们研究过这种方法,它有很多事情要做,最终考虑到代价太大就永久搁置了。

几种解决方案

用 ref(out) 一般有两个目的,一是对参数进行辅助说明,二是方法返回多个返回值。由于 async 确实不支持 ref(out),所以第一个目的难以实现,但第二个目的还是能通过多种方式达成的。

Tuple

.NET 4 引入的 Tuple 的常规使用方式就包含了使方法返回多个返回值。

Tuples are commonly used in four ways:

  • To represent a single set of data. For example, a tuple can represent a database record, and its components can represent individual fields of the record.
  • To provide easy access to, and manipulation of, a data set.
  • To return multiple values from a method without using out parameters (in C#) or ByRef parameters (in Visual Basic).
  • To pass multiple values to a method through a single parameter. For example, the Thread.Start(Object) method has a single parameter that lets you supply one value to the method that the thread executes at startup time. If you supply a Tuple object as the method argument, you can supply the thread’s startup routine with three items of data.
  1. public async Task Method1()
  2. {
  3. var tuple = await GetDataTaskAsync();
  4. int op = tuple.Item1;
  5. int result = tuple.Item2;
  6. }
  7. public async Task<Tuple<int, int>> GetDataTaskAsync()
  8. {
  9. //...
  10. return new Tuple<int, int>(1, 2);
  11. }

C# 7 的 Named Tuples 体验更佳:

  1. static (int count, double sum) Tally(IEnumerable<double> values)
  2. {
  3. int count = 0;
  4. double sum = 0.0;
  5. foreach (var value in values)
  6. {
  7. count++;
  8. sum += value;
  9. }
  10. return (count, sum);
  11. }
  12. ...
  13. var values = ...
  14. var t = Tally(values);
  15. Console.WriteLine($"There are {t.count} values and their sum is {t.sum}");

自定义 Class

贴个 SOF 上的示例:

  1. public class Data
  2. {
  3. public int Op {get; set;}
  4. public int Result {get; set;}
  5. }
  6. public async void Method1()
  7. {
  8. Data data = await GetDataTaskAsync();
  9. // use data.Op and data.Result from here on
  10. }
  11. public async Task<Data> GetDataTaskAsync()
  12. {
  13. var returnValue = new Data();
  14. // Fill up returnValue
  15. return returnValue;
  16. }