在异步方法中ref和out参数

Ned*_*nov 53 c# asynchronous

有谁知道为什么async不允许方法refout参数?我已经做了一些或研究它,但我唯一能找到的是它与堆栈展开有关.

Jon*_*eet 96

有谁知道为什么不允许异步方法有ref和out参数?

当然.想一想 - 异步方法通常几乎立即返回,早在大多数实际逻辑被执行之前......这是异步完成的.因此out,必须在第一个await表达式之前分配任何参数,并且可能必须对ref参数进行一些限制以阻止它们在第一个await表达式之后被使用,因为之后它们甚至可能无效.

考虑调用异步方法与outref参数,采用局部变量的参数:

int x;
int y = 10;
FooAsync(out x, ref y);
Run Code Online (Sandbox Code Playgroud)

FooAsync返回时,方法本身可能返回-所以这些局部变量将不再存在逻辑......但异步方法仍然有效能够在其延续使用它们.大问题.编译器可以创建一个新类来捕获变量,就像它对lambda表达式一样,但这会导致其他问题......除了其他任何问题,你可以通过一个方法在任意点改变一个局部变量,当continuation在不同的线程上运行时.奇怪的是至少可以说.

基本上,由于涉及的时间安排,使用方法outref参数是没有意义的async.使用包含您感兴趣的所有数据的返回类型.

如果你只在兴趣outref第一前参数改变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!
{
}
Run Code Online (Sandbox Code Playgroud)

编辑:out使用参数Task<T>并直接在方法中分配值就像返回值一样是可行的.虽然这有点奇怪,但它不适用于ref参数.


小智 14

C#编译为CIL,CIL不支持此功能.

CIL本身没有async.async方法被编译为一个类,所有(使用过的)参数和局部变量都存储在类字段中,因此当调用该类的特定方法时,代码知道继续执行的位置以及变量具有的值.

refout参数是使用托管指针实现的,并且不允许托管指针类型的类字段,因此编译器无法保留传入的引用.

对类字段中托管指针的这种限制可以防止一些无意义的代码,如Jon Skeet的答案中所解释的那样,因为类字段中的托管指针可能引用已返回的函数的局部变量.但是,这种限制非常严格,即使安全和正确使用也会被拒绝.该ref/ out可以工作,如果他们提到了另一个类领域,并确保编译器总是缠上通过局部变量ref/ out在一个类(像它已经知道该怎么做).

所以,C#根本无法解决CIL施加的限制.即使C#设计师想要允许它(我不是说他们这样做),他们也不能.