为什么我不允许 return IAsyncEnumerable 方法中的 IAsyncEnumerable return
Why am I not allowed to return an IAsyncEnumerable in a method returning an IAsyncEnumerable
我有以下界面:
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 Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration.
我知道我可以通过迭代可枚举来修复:
foreach (var error in GetErrors(bar, foo))
{
yield return new ValidationResult(error);
}
或者通过 returning a Task<IEnumerable<ValidationResult>>
:
public async Task<IEnumerable<ValidationResult>> ValidateAsync(Bar bar)
{
var foo = await GetRequiredThingAsync;
return GetErrors(bar, foo).Select(e => new ValidationResult(e));
}
但我想了解为什么我不能 return IAsyncEnumerable
。在编写“经典”IEnumerable
方法时,您可以 return 一个 IEnumerable
或产生 return 多个值。为什么不允许我对 IAsyncEnumerable
做同样的事情?
异步方法的常用语法是return一个任务:
public async Task<Foo> GetFooAsync()
{
/...
}
如果您将其设为 async
而不是 return Task<T>
,则编译器将在 header.
中标记错误
有一个例外:迭代器方法 returns IAsyncEnumerable
private static async IAsyncEnumerable<int> ThisShouldReturnAsTask()
{
yield return 0;
await Task.Delay(100);
yield return 1;
}
在您的示例中,您有一个 returns IAsyncEnumerable
的异步函数,但不是迭代器。 (它只是 return 一个直接可枚举的 object,而不是产生值 one-by-one。)
因此出现错误:“无法 return 来自迭代器的值”
如果您将签名更改为 return Task<IAsyncEnumerable<ValidationResult>>
public async Task<IAsyncEnumerable<ValidationResult>> ValidateAsync(Bar bar)
{
var foo = await GetRequiredThingAsync();
return GetErrors(bar, foo).Select(e => new ValidationResult(e)).ToAsyncEnumerable();
}
那么您将需要更改调用它的方式:它必须是
await foreach(var f in await ValidateAsync(new Bar()))
而不是
await foreach(var f in ValidateAsync(new Bar()))
在此处查看示例:https://dotnetfiddle.net/yPTdqp
This looks like a bug or at least an unintentional limitation, when reading the spec proposal.
规范指出 yield
的存在导致迭代器方法; async
和 yield
的存在导致异步迭代器方法。
But I would like to understand why I cannot return an IAsyncEnumerable in my case.
async
关键字使它成为一个异步迭代器方法。由于 await
需要 async
,因此您还需要使用 yield
。
When writing "classic" IEnumerable methods, you can either return an IEnumerable or yield return several values. Why am I not allowed to do the same with IAsyncEnumerable?
对于 IEnumerable<T>
和 IAsyncEnumerable<T>
,您可以在 return 直接枚举之前执行同步工作。在这种情况下,该方法一点也不特殊;它只是做一些工作,然后 return 给它的调用者一个值。
但是在 return 一个异步枚举器之前你不能做异步工作。在这种情况下,您需要 async
关键字。添加 async
关键字强制方法为异步方法或异步迭代器方法。
换句话说,在C#中所有的方法都可以分为这些不同的类型:
- 常规方法。不存在
async
或 yield
。
- 迭代器方法。
yield
中有yield
没有async
。必须returnIEnumerable<T>
(或IEnumerator<T>
).
- 异步方法。
async
存在,但没有 yield
。必须 return tasklike.
- 异步迭代器方法。
async
和 yield
都存在。必须returnIAsyncEnumerable<T>
(或IAsyncEnumerator<T>
).
从另一个角度考虑必须用于实现这种方法的状态机,尤其是考虑 await GetRequiredThingAsync()
代码 运行s.
在同步世界中 没有 yield
, GetRequiredThing()
运行 之前 return枚举。在同步世界中,with yield
、GetRequiredThing()
将 运行 当请求可枚举的第一项时。
在异步世界中 如果没有 yield
,await GetRequiredThingAsync()
会 运行 在 之前 returning 异步可枚举(在这种情况下,return 类型将是 Task<IAsyncEnumerable<T>>
,因为您必须进行异步工作才能 get 异步可枚举).在异步世界中,with yield
、await GetRequiredThingAsync()
将在请求可枚举的第一项时 运行。
一般来说,您想要在 return 枚举之前进行工作的唯一情况是进行前提条件检查(本质上是同步的)。进行 API/DB 调用是不正常的;大多数情况下,预期的语义是任何 API/DB 调用都将作为 enumeration 的一部分完成。也就是说,即使是同步代码也可能should have been using foreach
and yield
,就像异步代码是forced[=89一样=] 要做。
附带说明一下,在这些情况下为同步和异步迭代器提供 yield*
会很好,但 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 Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration.
我知道我可以通过迭代可枚举来修复:
foreach (var error in GetErrors(bar, foo))
{
yield return new ValidationResult(error);
}
或者通过 returning a Task<IEnumerable<ValidationResult>>
:
public async Task<IEnumerable<ValidationResult>> ValidateAsync(Bar bar)
{
var foo = await GetRequiredThingAsync;
return GetErrors(bar, foo).Select(e => new ValidationResult(e));
}
但我想了解为什么我不能 return IAsyncEnumerable
。在编写“经典”IEnumerable
方法时,您可以 return 一个 IEnumerable
或产生 return 多个值。为什么不允许我对 IAsyncEnumerable
做同样的事情?
异步方法的常用语法是return一个任务:
public async Task<Foo> GetFooAsync()
{
/...
}
如果您将其设为 async
而不是 return Task<T>
,则编译器将在 header.
有一个例外:迭代器方法 returns IAsyncEnumerable
private static async IAsyncEnumerable<int> ThisShouldReturnAsTask()
{
yield return 0;
await Task.Delay(100);
yield return 1;
}
在您的示例中,您有一个 returns IAsyncEnumerable
的异步函数,但不是迭代器。 (它只是 return 一个直接可枚举的 object,而不是产生值 one-by-one。)
因此出现错误:“无法 return 来自迭代器的值”
如果您将签名更改为 return Task<IAsyncEnumerable<ValidationResult>>
public async Task<IAsyncEnumerable<ValidationResult>> ValidateAsync(Bar bar)
{
var foo = await GetRequiredThingAsync();
return GetErrors(bar, foo).Select(e => new ValidationResult(e)).ToAsyncEnumerable();
}
那么您将需要更改调用它的方式:它必须是
await foreach(var f in await ValidateAsync(new Bar()))
而不是
await foreach(var f in ValidateAsync(new Bar()))
在此处查看示例:https://dotnetfiddle.net/yPTdqp
This looks like a bug or at least an unintentional limitation, when reading the spec proposal.
规范指出 yield
的存在导致迭代器方法; async
和 yield
的存在导致异步迭代器方法。
But I would like to understand why I cannot return an IAsyncEnumerable in my case.
async
关键字使它成为一个异步迭代器方法。由于 await
需要 async
,因此您还需要使用 yield
。
When writing "classic" IEnumerable methods, you can either return an IEnumerable or yield return several values. Why am I not allowed to do the same with IAsyncEnumerable?
对于 IEnumerable<T>
和 IAsyncEnumerable<T>
,您可以在 return 直接枚举之前执行同步工作。在这种情况下,该方法一点也不特殊;它只是做一些工作,然后 return 给它的调用者一个值。
但是在 return 一个异步枚举器之前你不能做异步工作。在这种情况下,您需要 async
关键字。添加 async
关键字强制方法为异步方法或异步迭代器方法。
换句话说,在C#中所有的方法都可以分为这些不同的类型:
- 常规方法。不存在
async
或yield
。 - 迭代器方法。
yield
中有yield
没有async
。必须returnIEnumerable<T>
(或IEnumerator<T>
). - 异步方法。
async
存在,但没有yield
。必须 return tasklike. - 异步迭代器方法。
async
和yield
都存在。必须returnIAsyncEnumerable<T>
(或IAsyncEnumerator<T>
).
从另一个角度考虑必须用于实现这种方法的状态机,尤其是考虑 await GetRequiredThingAsync()
代码 运行s.
在同步世界中 没有 yield
, GetRequiredThing()
运行 之前 return枚举。在同步世界中,with yield
、GetRequiredThing()
将 运行 当请求可枚举的第一项时。
在异步世界中 如果没有 yield
,await GetRequiredThingAsync()
会 运行 在 之前 returning 异步可枚举(在这种情况下,return 类型将是 Task<IAsyncEnumerable<T>>
,因为您必须进行异步工作才能 get 异步可枚举).在异步世界中,with yield
、await GetRequiredThingAsync()
将在请求可枚举的第一项时 运行。
一般来说,您想要在 return 枚举之前进行工作的唯一情况是进行前提条件检查(本质上是同步的)。进行 API/DB 调用是不正常的;大多数情况下,预期的语义是任何 API/DB 调用都将作为 enumeration 的一部分完成。也就是说,即使是同步代码也可能should have been using foreach
and yield
,就像异步代码是forced[=89一样=] 要做。
附带说明一下,在这些情况下为同步和异步迭代器提供 yield*
会很好,但 C# 不支持。