简体   繁体   English

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

[英]Why am I not allowed to return an IAsyncEnumerable in a method returning an IAsyncEnumerable

I have the following interface:我有以下界面:

public interface IValidationSystem<T>
{
    IAsyncEnumerable<ValidationResult> ValidateAsync(T obj);
}

And I am trying to implement it this way:我正在尝试以这种方式实现它:

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());
    }
}

But this does not compile:但这不会编译:

CS1622 Cannot return a value from an iterator. CS1622 无法从迭代器返回值。 Use the yield return statement to return a value, or yield break to end the iteration.使用 yield return 语句返回一个值,或者使用 yield break 来结束迭代。

I know I can fix by iterating the enumerable:我知道我可以通过迭代可枚举来修复:

foreach (var error in GetErrors(bar, foo))
{
    yield return new ValidationResult(error);
}

Or by returning a Task<IEnumerable<ValidationResult>> :或者通过返回Task<IEnumerable<ValidationResult>>

public async Task<IEnumerable<ValidationResult>> ValidateAsync(Bar bar)
{
    var foo = await GetRequiredThingAsync;

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

But I would like to understand why I cannot return an IAsyncEnumerable in my case.但我想了解为什么在我的情况下我不能返回IAsyncEnumerable When writing "classic" IEnumerable methods, you can either return an IEnumerable or yield return several values.在编写“经典” IEnumerable方法时,您可以返回一个IEnumerable或 yield 返回多个值。 Why am I not allowed to do the same with IAsyncEnumerable ?为什么我不允许对IAsyncEnumerable做同样的事情?

This looks like a bug or at least an unintentional limitation, when reading the spec proposal .在阅读规范提案时,这看起来像是一个错误或至少是一个无意的限制。

The spec states that the presence of yield results in an iterator method;规范指出, yield的存在导致迭代器方法; and the presence of both async and yield results in an asynchronous iterator method. asyncyield的存在导致异步迭代器方法。

But I would like to understand why I cannot return an IAsyncEnumerable in my case.但我想了解为什么在我的情况下我不能返回 IAsyncEnumerable。

The async keyword is making this into an asynchronous iterator method. async关键字使它成为一个异步迭代器方法。 Since you need the async for the await , then you'll need to use yield as well.由于您需要asyncawait ,那么您也需要使用yield

When writing "classic" IEnumerable methods, you can either return an IEnumerable or yield return several values.在编写“经典” IEnumerable 方法时,您可以返回一个 IEnumerable 或 yield 返回多个值。 Why am I not allowed to do the same with IAsyncEnumerable?为什么我不能对 IAsyncEnumerable 做同样的事情?

With both IEnumerable<T> and IAsyncEnumerable<T> , you can perform synchronous work before returning the enumerable directly.使用IEnumerable<T>IAsyncEnumerable<T> ,您可以在直接返回可枚举之前执行同步工作。 In this case, the method is not special at all;在这种情况下,方法一点也不特别; it just does some work and then returns a value to its caller.它只是做一些工作,然后将一个值返回给它的调用者。

But you can't do asynchronous work before returning an asynchronous enumerator.但是在返回异步枚举器之前不能进行异步工作。 In this case, you need the async keyword.在这种情况下,您需要async关键字。 Adding the async keyword forces the method to either be an asynchronous method or an asynchronous iterator method.添加async关键字会强制该方法为异步方法或异步迭代器方法。

To put it another way, all methods can be classified into these different types in C#:换句话说,C#中的所有方法都可以分为这些不同的类型:

  • Normal methods.常规方法。 No async or yield present.没有asyncyield存在。
  • Iterator methods.迭代器方法。 A yield in the body without async .没有async的 body 中的yield Must return IEnumerable<T> (or IEnumerator<T> ).必须返回IEnumerable<T> (或IEnumerator<T> )。
  • Asynchronous methods.异步方法。 An async is present without yield .没有yieldasync存在。 Must return a tasklike.必须返回一个tasklike。
  • Asynchronous iterator methods.异步迭代器方法。 Both async and yield are present. asyncyield都存在。 Must return IAsyncEnumerable<T> (or IAsyncEnumerator<T> ).必须返回IAsyncEnumerable<T> (或IAsyncEnumerator<T> )。

From yet another perspective, consider the state machine that must be used to implement such a method, and especially think about when the await GetRequiredThingAsync() code runs.从另一个角度来看,考虑必须用于实现此类方法的 state 机器,并特别考虑await GetRequiredThingAsync()代码何时运行。

In the synchronous world without yield , GetRequiredThing() would run before returning the enumerable.没有yield的同步世界中, GetRequiredThing()将在返回可枚举之前运行。 In the synchronous world with yield , GetRequiredThing() would run when the first item of the enumerable is requested.使用yield的同步世界中, GetRequiredThing()将在请求可枚举的第一项时运行。

In the asynchronous world without yield , await GetRequiredThingAsync() would run before returning the async enumerable (and in that case, the return type would be Task<IAsyncEnumerable<T>> , since you have to do asynchronous work to get the async enumerable).没有yield的异步世界中, await GetRequiredThingAsync()将在返回异步枚举之前运行(在这种情况下,返回类型将为Task<IAsyncEnumerable<T>> ,因为您必须执行异步工作才能获得异步枚举) . In the asynchronous world with yield , await GetRequiredThingAsync() would run when the first item of the enumerable is requested.使用yield的异步世界中, await GetRequiredThingAsync()将在请求可枚举的第一项时运行。

Generally speaking, the only case when you want to do work before returning the enumerable is when you're doing precondition checks (which are synchronous by nature).一般而言,您想要在返回可枚举之前进行工作的唯一情况是您进行前置条件检查(本质上是同步的)。 Doing an API/DB call is not normal;进行 API/DB 调用是不正常的; most of the time the expected semantics are that any API/DB calls will be done as part of enumeration .大多数时候,预期的语义是任何 API/DB 调用都将作为enumeration的一部分完成。 In other words, even the synchronous code probably should have been using foreach and yield , just like the asynchronous code is forced to do.换句话说,即使是同步代码也可能应该使用foreachyield ,就像异步代码被迫这样做一样。

On a side note, it would be nice in these scenarios to have a yield* for both synchronous and asynchronous iterators, but C# does not support that.附带说明一下,在这些场景中,同步和异步迭代器都有一个yield*会很好,但是 C# 不支持。

The usual syntax for an async method is to return a Task:异步方法的常用语法是返回一个任务:

public async Task<Foo> GetFooAsync()
{
    /...
}

If you make it async but not return a Task<T> then the compiler will flag an error in the header.如果将其设为async但不返回Task<T> ,则编译器将在 header 中标记错误。

There is an exception: an iterator method that returns IAsyncEnumerable有一个例外:返回IAsyncEnumerable的迭代器方法

private static async IAsyncEnumerable<int> ThisShouldReturnAsTask()
{
    yield return 0;
    await Task.Delay(100);
    yield return 1;
}

In your example, you have an async function that returns IAsyncEnumerable , but is NOT an iterator.在您的示例中,您有一个异步 function 返回IAsyncEnumerable ,但不是迭代器。 (It just returns a straight enumerable object, instead of yielding values one-by-one.) (它只返回一个直接可枚举的 object,而不是一个接一个地产生值。)

Hence the error: "Cannot return a value from an iterator"因此出现错误:“无法从迭代器返回值”

If you changed the signature to return Task<IAsyncEnumerable<ValidationResult>>如果您将签名更改为返回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();
}

then you will need to change the way you invoke it: it would have to be那么你需要改变你调用它的方式:它必须是

await foreach(var f in await ValidateAsync(new Bar()))

Instead of代替

await foreach(var f in ValidateAsync(new Bar()))

See examples to play with here: https://dotnetfiddle.net/yPTdqp在此处查看示例: https://dotnetfiddle.net/yPTdqp

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 从异步方法返回 IAsyncEnumerable - Return IAsyncEnumerable from an async method 是否可以在异步方法中返回指向 IAsyncEnumerable 的指针? - Is it possible to return a pointer to an IAsyncEnumerable in an async method? 在 function 中迭代 IAsyncEnumerable 并返回 IAsyncEnumerable 并取消 - Iterating an IAsyncEnumerable in a function returning an IAsyncEnumerable with cancellation 如何使用 IAsyncEnumerable 拦截异步方法<t>作为 .netcore 3.0 中的返回类型</t> - How to intercept an async method with IAsyncEnumerable<T> as return type in .netcore 3.0 为什么使用 IAsyncEnumerable 比返回 async/await 任务慢<T> ? - Why is using IAsyncEnumerable slower than returning async/await Task<T>? 如何处理以 IAsyncEnumerable 作为返回类型的方法中的错误 - How to handle errors in method that has IAsyncEnumerable as return type 如何使用 IAsyncEnumerable 对方法进行单元测试<dynamic>返回类型</dynamic> - How to unit test method with IAsyncEnumerable<dynamic> return type 如何使用 SqlDataReader 返回和使用 IAsyncEnumerable - How to Return and Consume an IAsyncEnumerable with SqlDataReader 返回 IAsyncEnumerable 的方法是否有明确的命名约定? - Is there a definitive naming convention for methods returning IAsyncEnumerable? 有没有办法在不使用 await foreach 的情况下返回 IAsyncEnumerable - Is there a way to return an IAsyncEnumerable without using await foreach
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM