为什么我不允许 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 的存在导致迭代器方法; asyncyield 的存在导致异步迭代器方法。

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#中所有的方法都可以分为这些不同的类型:

  • 常规方法。不存在 asyncyield
  • 迭代器方法。 yield中有yield没有async。必须returnIEnumerable<T>(或IEnumerator<T>).
  • 异步方法。 async 存在,但没有 yield。必须 return tasklike.
  • 异步迭代器方法。 asyncyield 都存在。必须returnIAsyncEnumerable<T>(或IAsyncEnumerator<T>).

从另一个角度考虑必须用于实现这种方法的状态机,尤其是考虑 await GetRequiredThingAsync() 代码 运行s.

在同步世界中 没有 yield, GetRequiredThing() 运行 之前 return枚举。在同步世界中,with yieldGetRequiredThing() 将 运行 当请求可枚举的第一项时。

在异步世界中 如果没有 yieldawait GetRequiredThingAsync() 会 运行 之前 returning 异步可枚举(在这种情况下,return 类型将是 Task<IAsyncEnumerable<T>>,因为您必须进行异步工作才能 get 异步可枚举).在异步世界中,with yieldawait GetRequiredThingAsync() 将在请求可枚举的第一项时 运行。

一般来说,您想要在 return 枚举之前进行工作的唯一情况是进行前提条件检查(本质上是同步的)。进行 API/DB 调用是不正常的;大多数情况下,预期的语义是任何 API/DB 调用都将作为 enumeration 的一部分完成。也就是说,即使是同步代码也可能should have been using foreach and yield,就像异步代码是forced[=89一样=] 要做。

附带说明一下,在这些情况下为同步和异步迭代器提供 yield* 会很好,但 C# 不支持。