首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么不允许我在返回IAsyncEnumerable的方法中返回IAsyncEnumerable

为什么不允许我在返回IAsyncEnumerable的方法中返回IAsyncEnumerable
EN

Stack Overflow用户
提问于 2021-01-27 10:34:16
回答 2查看 3.6K关注 0票数 14

我有以下界面:

代码语言:javascript
复制
public interface IValidationSystem<T>
{
    IAsyncEnumerable<ValidationResult> ValidateAsync(T obj);
}

我正试图以这样的方式来实现:

代码语言:javascript
复制
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不能从迭代器返回值。使用产率返回语句返回一个值,或者使用屈服中断结束迭代。

我知道我可以通过迭代枚举来修复:

代码语言:javascript
复制
foreach (var error in GetErrors(bar, foo))
{
    yield return new ValidationResult(error);
}

或者返回一个Task<IEnumerable<ValidationResult>>

代码语言:javascript
复制
public async Task<IEnumerable<ValidationResult>> ValidateAsync(Bar bar)
{
    var foo = await GetRequiredThingAsync;

    return GetErrors(bar, foo).Select(e => new ValidationResult(e));
}

但是我想理解为什么在我的情况下我不能返回一个IAsyncEnumerable。在编写“经典”IEnumerable方法时,可以返回一个IEnumerable,也可以返回几个值。为什么不允许我对IAsyncEnumerable做同样的事情?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-01-27 14:23:18

在读取规格提案时,这看起来像是一个bug,或者至少是无意中的限制。

规范指出,yield的存在导致迭代器方法的出现;asyncyield的存在导致异步迭代器方法的出现。

但是我想理解为什么在我的情况下我不能返回一个IAsyncEnumerable。

async关键字使其成为异步迭代器方法。因为您需要用于asyncawait,所以您也需要使用yield

在编写“经典”IEnumerable方法时,可以返回一个IEnumerable,也可以返回几个值。为什么不允许我对IAsyncEnumerable做同样的事情呢?

使用IEnumerable<T>IAsyncEnumerable<T>,您可以在直接返回枚举之前执行同步工作。在这种情况下,该方法一点也不特殊;它只是做一些工作,然后将一个值返回给调用方。

但是,在返回异步枚举数之前不能执行异步工作。在这种情况下,您需要async关键字。添加async关键字会迫使该方法要么是异步方法,要么是异步迭代器方法。

换句话说,在C#中,所有方法都可以分为这些不同的类型:

  • 正常的方法。不存在asyncyield
  • 迭代法。在没有yield的情况下体内的async。必须返回IEnumerable<T> (或IEnumerator<T>)。
  • 异步方法async在没有yield的情况下存在。必须返回类似任务的。
  • 异步迭代器方法。asyncyield都在场。必须返回IAsyncEnumerable<T> (或IAsyncEnumerator<T>)。

从另一个角度来看,考虑实现这样一个方法必须使用的状态机,尤其要考虑await GetRequiredThingAsync()代码何时运行。

在没有yield的同步世界中,GetRequiredThing()将在返回枚举之前运行。在使用yield的同步世界中,当请求枚举的第一项时,GetRequiredThing()将运行。

在没有yield的异步世界中,await GetRequiredThingAsync()将在返回异步枚举之前运行(在这种情况下,返回类型将是Task<IAsyncEnumerable<T>>,因为您必须做异步工作才能获得异步枚举)。在使用yield的异步世界中,当请求枚举的第一项时,await GetRequiredThingAsync()将运行。

一般来说,在返回枚举之前要做工作的唯一情况是执行先决条件检查(本质上是同步的)。执行API/DB调用是不正常的;大多数情况下,预期的语义是任何API/DB调用都将作为枚举的一部分进行。换句话说,即使是同步代码也应该使用foreachyield,就像异步代码被迫使用的那样。

另外,在这些场景中,为同步迭代器和异步迭代器设置一个yield*是很好的,但是C#不支持这一点。

票数 10
EN

Stack Overflow用户

发布于 2021-01-27 10:59:00

异步方法的通常语法是返回一个任务:

代码语言:javascript
复制
public async Task<Foo> GetFooAsync()
{
    /...
}

如果将其设置为async,但不返回Task<T>,则编译器将在标头中标记错误。

有一个异常:返回IAsyncEnumerable的迭代器方法

代码语言:javascript
复制
private static async IAsyncEnumerable<int> ThisShouldReturnAsTask()
{
    yield return 0;
    await Task.Delay(100);
    yield return 1;
}

在您的示例中,您有一个返回IAsyncEnumerable的异步函数,但它不是迭代器。(它只是返回一个直接可枚举的对象,而不是一个接一个地生成值。)

因此,错误是:“无法从迭代器返回值”。

如果您将签名更改为返回Task<IAsyncEnumerable<ValidationResult>>

代码语言:javascript
复制
public async Task<IAsyncEnumerable<ValidationResult>> ValidateAsync(Bar bar)
{
    var foo = await GetRequiredThingAsync();
    return GetErrors(bar, foo).Select(e => new ValidationResult(e)).ToAsyncEnumerable();
}

然后,您需要改变调用它的方式:它必须是

代码语言:javascript
复制
await foreach(var f in await ValidateAsync(new Bar()))

而不是

代码语言:javascript
复制
await foreach(var f in ValidateAsync(new Bar()))

参见这里要使用的示例:https://dotnetfiddle.net/yPTdqp

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65917317

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档