有人知道为什么async
方法不允许使用ref
和out
参数吗?我已经做了一些调查,但唯一找到的是这与堆栈展开有关。
有人知道为什么async
方法不允许使用ref
和out
参数吗?我已经做了一些调查,但唯一找到的是这与堆栈展开有关。
int x;
int y = 10;
FooAsync(out x, ref y);
在FooAsync
返回之后,该方法本身可以返回 - 因此那些局部变量在逻辑上不再存在...但异步方法仍然可以在其继续运行时有效地使用它们。这是一个大问题。编译器可以创建一个新类来捕获变量,就像对lambda表达式一样,但这会引起其他问题...除此之外,当继续运行在不同的线程上时,你可能会看到一个本地(local)变量在方法中任意点处改变。最好不要这样做。
基本上,由于涉及时间问题,对于async
方法来说,使用out
和ref
参数是没有意义的。相反,应该使用包含您感兴趣的所有数据的返回类型。
如果您只关心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!
{
}
编辑:使用 Task<T>
可以使用 out
参数,并直接在方法中分配值,就像返回值一样。尽管这可能有点奇怪,并且对于 ref
参数不起作用。
out
参数作为另一个out
参数的参数传递... - Jon SkeetAction<T>
而不是 Task<T>
?如果是,那是个好主意。 - user743382await FooAsyncImpl(val => x = val, val => y = val)
而不是 await FooAsyncImpl(out x, out y)
,后者只适用于 out
,且仅适用于 out
(而且您在设置后不会再读取它)。感谢澄清,我仍然不确定我是否理解了,但我会考虑一下。 :) - user743382C#被编译为公共中间语言(CIL),而CIL不支持此功能。
CIL本身不支持async
。 async
方法被编译为类,所有(使用的)参数和局部变量都存储在类字段中,因此当调用该类的特定方法时,代码会知道如何继续执行以及变量具有哪些值。
ref
和out
参数使用托管指针实现,并且不允许托管指针类型的类字段,因此编译器无法保留传递的引用。
类字段上的托管指针限制防止了一些荒谬的代码,正如Jon Skeet的答案中所解释的那样,因为类字段中的托管指针可能指向已经返回的函数的局部变量。但是,这种限制太严格了,即使安全和其他正确的用法也被拒绝。 如果ref
/out
字段引用另一个类字段,并且编译器确保始终将使用ref
/out
传递的局部变量包装在类中(就像它已经知道如何做的那样),则其可以工作。
所以,C# 简单地没有任何方法可以绕过 CIL 强加的限制。即使 C# 设计师想要允许它(我并不是说他们这样做),他们也不能。