10

私は次のインターフェースを持っています:

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複数の値を返すか、複数の値を返すことができます。で同じことを行うことが許可されていないのはなぜIAsyncEnumerableですか?

4

2 に答える 2

7

仕様の提案を読むと、これはバグまたは少なくとも意図しない制限のように見えます。

仕様では、 が存在するとyieldイテレータ メソッドが生成されると記載されています。と の両方が存在すると、非同期イテレータ メソッドにasyncなります。yield

しかし、私の場合、IAsyncEnumerable を返せない理由を理解したいと思います。

キーワードは、asyncこれを非同期反復子メソッドにしています。には が必要なのでasync、同様awaitに使用する必要があります。yield

「古典的な」IEnumerable メソッドを記述する場合、IEnumerable を返すか、複数の値を返すことができます。IAsyncEnumerable で同じことを行うことが許可されていないのはなぜですか?

と の両方を使用するIEnumerable<T>IAsyncEnumerable<T>、列挙型を直接返す前に同期作業を実行できます。この場合、メソッドはまったく特別なものではありません。何らかの作業を行ってから、呼び出し元に値を返します。

ただし、非同期列挙子を返す前に非同期作業を行うことはできません。この場合、asyncキーワードが必要です。キーワードを追加するasyncと、メソッドが非同期メソッドまたは非同期反復子メソッドのいずれかになります。

別の言い方をすれば、C# ではすべてのメソッドを次のような異なる型に分類できます。

  • 通常の方法。いいえasync、またはyield存在します。
  • イテレータ メソッド。なしのyieldボディでasyncIEnumerable<T>(または) を返す必要がありIEnumerator<T>ます。
  • 非同期メソッド。Anasyncは なしで存在しyieldます。tasklike を返す必要があります。
  • 非同期反復子メソッド。asyncとの両方yieldが存在します。IAsyncEnumerable<T>(または) を返す必要がありIAsyncEnumerator<T>ます。

さらに別の観点から、このようなメソッドを実装するために使用する必要があるステート マシンを考えてみましょう。特に、await GetRequiredThingAsync()コードが実行されるタイミングについて考えてみてください。

のない 同期の世界yieldでは、列挙型を返すGetRequiredThing()に実行されます。との同期の世界は、列挙型の最初の項目が要求されたときに実行されます。 yieldGetRequiredThing()

のない 非同期の世界では、非同期の列挙型を返すyieldawait GetRequiredThingAsync()が実行されます (この場合、非同期の列挙型を取得するには非同期作業を行う必要があるため、戻り値の型は になります)。を使用した非同期の世界は、列挙型の最初の項目が要求されたときに実行されます。Task<IAsyncEnumerable<T>> yieldawait GetRequiredThingAsync()

一般的に言えば、列挙型を返す前に作業を行いたい場合は、事前条件チェック (本質的に同期) を行う場合のみです。API/DB 呼び出しを行うことは正常ではありません。ほとんどの場合、期待されるセマンティクスは、すべての API/DB 呼び出しがenumerationの一部として実行されることです。言い換えれば、同期コードでさえ、非同期コードが強制されているのと同じように、おそらくandを使用していたはずです。foreachyield

余談ですが、これらのシナリオでは、yield*同期イテレータと非同期イテレータの両方を使用すると便利ですが、C# はそれをサポートしていません。

于 2021-01-27T14:23:18.960 に答える