为什么我不允许在返回IAsyncEnumerable的方法中返回IAsyncEnumerable
c#
我有以下界面:
public interface IValidationSystem<T>
{
IAsyncEnumerable<ValidationResult> ValidateAsync(T obj);
}
我正在尝试以这种方式实现它:
public class Foo
{ }
public class Bar
{ }
public class BarValidationSystem : IValidationSystem<T>
{
public async IAsyncEnumerable<ValidationResult> ValidateAsync(Bar bar)
{
var foo = await GetRequiredThingAsync();
return GetErrors(bar, foo).Select(e => new ValidationResult(e)).ToAsyncEnumerable();
}
private static IEnumerable<string> GetErrors(Bar bar, Foo foo)
{
yield return "Something is wrong";
yield return "Oops something else is wrong";
yield return "And eventually, this thing is wrong too";
}
private Task<Foo> GetRequiredThingAsync()
{
return Task.FromResult(new Foo());
}
}
但这不能编译:
CS1622 无法从迭代器返回值。使用 yield return 语句返回一个值,或使用 yield break 结束迭代。
我知道我可以通过迭代可枚举来修复:
foreach (var error in GetErrors(bar, foo))
{
yield return new ValidationResult(error);
}
或者通过返回一个Task<IEnumerable<ValidationResult>>:
public async Task<IEnumerable<ValidationResult>> ValidateAsync(Bar bar)
{
var foo = await GetRequiredThingAsync;
return GetErrors(bar, foo).Select(e => new ValidationResult(e));
}
但我想了解为什么我不能IAsyncEnumerable在我的情况下返回 an 。在编写“经典”IEnumerable方法时,您可以返回一个IEnumerable或 yield 返回多个值。为什么我不能对 做同样的事情IAsyncEnumerable?
回答
在阅读规范提案时,这看起来像是一个错误或至少是一个无意的限制。
规范指出yield迭代器方法中存在结果;和两者的存在async和yield结果在异步迭代方法。
但我想了解为什么在我的情况下我不能返回 IAsyncEnumerable。
所述async关键字被制作成异步迭代方法这一点。既然你需要asyncfor await,那么你也需要使用yield。
在编写“经典” IEnumerable 方法时,您可以返回一个 IEnumerable 或 yield 返回多个值。为什么我不能对 IAsyncEnumerable 做同样的事情?
使用IEnumerable<T>and IAsyncEnumerable<T>,您可以在直接返回可枚举之前执行同步工作。在这种情况下,该方法并不特殊;它只是做了一些工作,然后将一个值返回给它的调用者。
但是您不能在返回异步枚举器之前进行异步工作。在这种情况下,您需要async关键字。添加async关键字会强制方法成为异步方法或异步迭代器方法。
换句话说,在 C# 中,所有方法都可以分为以下不同类型:
- 常规方法。没有
async或yield存在。 - 迭代器方法。一个
yield在身体没有async。必须返回IEnumerable<T>(或IEnumerator<T>)。 - 异步方法。An
async在没有 的情况下存在yield。必须返回一个 tasklike。 - 异步迭代器方法。双方
async并yield都存在。必须返回IAsyncEnumerable<T>(或IAsyncEnumerator<T>)。
从另一个角度来看,考虑实现这种方法必须使用的状态机,尤其要考虑await GetRequiredThingAsync()代码何时运行。
在没有 yield,的同步世界中,GetRequiredThing()将在返回可枚举之前运行。在使用 的同步世界中 yield,GetRequiredThing()将在请求可枚举的第一项时运行。
在没有 yield,的异步世界中,await GetRequiredThingAsync()将在返回异步枚举之前运行(在这种情况下,返回类型将为Task<IAsyncEnumerable<T>>,因为您必须执行异步工作才能获得异步枚举)。在带有 的异步世界中yield,await GetRequiredThingAsync()将在请求可枚举的第一项时运行。
一般来说,在返回可枚举之前要进行工作的唯一情况是进行前提条件检查(本质上是同步的)。进行 API/DB 调用是不正常的;大多数情况下,预期的语义是任何 API/DB 调用都将作为枚举的一部分完成。换句话说,即使是同步代码也可能应该使用foreachand yield,就像异步代码被迫这样做一样。
附带说明一下,在这些场景yield*中同时使用同步和异步迭代器会很好,但 C# 不支持。