有谁知道为什么async
方法不允许有ref
和out
个参数?我已经做了一些或研究它,但我唯一能找到的是它与堆栈展开有关。
答案 0 :(得分:77)
有谁知道为什么异步方法不允许有参数和参数?
不确定。想一想 - 异步方法通常几乎立即返回,早在大部分实际逻辑执行之前......就是异步完成的。因此,必须在第一个out
表达式之前分配任何await
参数,并且很可能必须对ref
参数进行一些限制,以阻止它们在第一个之后被使用无论如何await
表达,因为之后它们甚至可能无效。
考虑使用out
和ref
参数调用异步方法,使用参数的局部变量:
int x;
int y = 10;
FooAsync(out x, ref y);
在FooAsync
返回后,方法本身可以返回 - 因此这些局部变量将不再在逻辑上存在......但异步方法仍然可以有效地在其连续中使用它们。大问题。编译器可以创建一个新类来捕获变量,就像它对lambda表达式一样,但这会导致其他问题...除了其他任何事情,你可以改变本地变量在任意点通过方法,当continuation在不同的线程上运行时。奇怪的是至少可以说。
基本上,由于所涉及的时间安排,对out
方法使用ref
和async
参数没有意义。请使用包含您感兴趣的所有数据的返回类型。
如果您只对out
和ref
参数在第一个await
表达式之前更改感兴趣,您可以随时将该方法拆分为两个:
public Task<string> FooAsync(out int x, ref int y)
{
// Assign a value to x here, maybe change y
return FooAsyncImpl(x, y);
}
private async Task<string> FooAsyncImpl(int x, int y) // Not ref or out!
{
}
编辑:使用out
获取Task<T>
参数是可行的,并在方法中直接指定值,就像返回值一样。虽然这有点奇怪,但它不适用于ref
参数。
答案 1 :(得分:12)
C#编译为CIL,CIL不支持此功能。
CIL本身没有async
。 async
方法被编译为一个类,所有(使用过的)参数和局部变量都存储在类字段中,这样当调用该类的特定方法时,代码知道继续执行的位置和值变量有。
ref
和out
参数,并且不允许托管指针类型的类字段,因此编译器无法保留传入的引用。
对类字段中托管指针的这种限制会阻止一些无意义的代码,如Jon Skeet的回答所述,因为类字段中的托管指针可能引用已返回的函数的局部变量。但是,这种限制非常严格,即使安全和正确使用也会被拒绝。 ref
/ out
字段可以工作,如果它们引用另一个类字段,并且编译器确保始终包装使用ref
/ {传递的局部变量{1}}在一个类中(就像它已经知道该怎么做)。
因此,C#根本无法解决CIL施加的限制。即使C#设计师想要允许它(我不是说他们这样做),他们也不能。