簡體   English   中英

為什么我不允許在返回 IAsyncEnumerable 的方法中返回 IAsyncEnumerable

[英]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 無法從迭代器返回值。 使用 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 在編寫“經典” IEnumerable方法時,您可以返回一個IEnumerable或 yield 返回多個值。 為什么我不允許對IAsyncEnumerable做同樣的事情?

在閱讀規范提案時,這看起來像是一個錯誤或至少是一個無意的限制。

規范指出, yield的存在導致迭代器方法; asyncyield的存在導致異步迭代器方法。

但我想了解為什么在我的情況下我不能返回 IAsyncEnumerable。

async關鍵字使它成為一個異步迭代器方法。 由於您需要asyncawait ,那么您也需要使用yield

在編寫“經典” IEnumerable 方法時,您可以返回一個 IEnumerable 或 yield 返回多個值。 為什么我不能對 IAsyncEnumerable 做同樣的事情?

使用IEnumerable<T>IAsyncEnumerable<T> ,您可以在直接返回可枚舉之前執行同步工作。 在這種情況下,方法一點也不特別; 它只是做一些工作,然后將一個值返回給它的調用者。

但是在返回異步枚舉器之前不能進行異步工作。 在這種情況下,您需要async關鍵字。 添加async關鍵字會強制該方法為異步方法或異步迭代器方法。

換句話說,C#中的所有方法都可以分為這些不同的類型:

  • 常規方法。 沒有asyncyield存在。
  • 迭代器方法。 沒有async的 body 中的yield 必須返回IEnumerable<T> (或IEnumerator<T> )。
  • 異步方法。 沒有yieldasync存在。 必須返回一個tasklike。
  • 異步迭代器方法。 asyncyield都存在。 必須返回IAsyncEnumerable<T> (或IAsyncEnumerator<T> )。

從另一個角度來看,考慮必須用於實現此類方法的 state 機器,並特別考慮await GetRequiredThingAsync()代碼何時運行。

沒有yield的同步世界中, GetRequiredThing()將在返回可枚舉之前運行。 使用yield的同步世界中, GetRequiredThing()將在請求可枚舉的第一項時運行。

沒有yield的異步世界中, await GetRequiredThingAsync()將在返回異步枚舉之前運行(在這種情況下,返回類型將為Task<IAsyncEnumerable<T>> ,因為您必須執行異步工作才能獲得異步枚舉) . 使用yield的異步世界中, await GetRequiredThingAsync()將在請求可枚舉的第一項時運行。

一般而言,您想要在返回可枚舉之前進行工作的唯一情況是您進行前置條件檢查(本質上是同步的)。 進行 API/DB 調用是不正常的; 大多數時候,預期的語義是任何 API/DB 調用都將作為enumeration的一部分完成。 換句話說,即使是同步代碼也可能應該使用foreachyield ,就像異步代碼被迫這樣做一樣。

附帶說明一下,在這些場景中,同步和異步迭代器都有一個yield*會很好,但是 C# 不支持。

異步方法的常用語法是返回一個任務:

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

如果將其設為async但不返回Task<T> ,則編譯器將在 header 中標記錯誤。

有一個例外:返回IAsyncEnumerable的迭代器方法

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

在您的示例中,您有一個異步 function 返回IAsyncEnumerable ,但不是迭代器。 (它只返回一個直接可枚舉的 object,而不是一個接一個地產生值。)

因此出現錯誤:“無法從迭代器返回值”

如果您將簽名更改為返回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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM