三浦ノート

自分の経験したことを検索可能にしていくブログ.誰かの役に立ってくれれば嬉しいです.

C#で生成メソッドを用いた無限リスト作成(非同期版も)

C#(.net6.0)でFunc<T>型の生成メソッドを使ってEnumerableの無限リストを作成するメソッドを定義します。

public static IEnumerable<T> Generate<T>(Func<T> supplier)
{
    while (true) yield return supplier();
}

生成メソッドsupplierに非同期メソッドを使いたい場合は次のように少し変更が必要です。

public static async IAsyncEnumerable<T> GenerateAsync<T>(Func<Task<T>> supplierAsync)
{
    while (true) yield return await supplierAsync();
}

使用例

StreamReaderで読み込んだテキストファイルをReadLineを使って行ごとのリストにする処理です。

非同期版のReadLineAsyncでも同様のことをしてみます。

public static class Program
{
    public static IEnumerable<T> Generate<T>(Func<T> supplier)
    {
        while (true) yield return supplier();
    }

    public static async IAsyncEnumerable<T> GenerateAsync<T>(Func<Task<T>> supplierAsync)
    {
        while (true) yield return await supplierAsync();
    }

    public static void ForEach<T>(this IEnumerable<T> it, Action<T> consumer)
    {
        foreach (var item in it) consumer(item);
    }

    static async Task Main(string[] args)
    {
        // 同期版
        using var file1 = new StreamReader("test.txt");
        Generate(file1.ReadLine)
            .TakeWhile(line => line is not null)
            .ForEach(Console.WriteLine);

        Console.WriteLine();

        // 非同期版
        using var file2 = new StreamReader("test.txt");
        await GenerateAsync(file2.ReadLineAsync)
            .TakeWhile(line => line is not null)
            .ForEachAsync(line => Console.WriteLine(line));
    }
}

ForEachAsyncは System.Linq.Async -version 6.0.1に入っているものです。

ForEachAsyncで同期版のようにConsole.WriteLineを直接デリゲートで渡すのではなくラムダ式で渡している理由は、

ForEachAsyncのオーバーロードの関数が次の二つあり、

ForEachAsync<T>(this IAsyncEnumerable<T> source, Action<T> action, CancellationToken cancellationToken = default(CancellationToken))
ForEachAsync<T>(this IAsyncEnumerable<T> source, Action<T, int> action, CancellationToken cancellationToken = default(CancellationToken))

そしてたまたまConsole.WriteLineのオーバーロード関数にも次の2つがあったため、直接Console.WriteLineのデリゲートを渡しただけでは関数シグネチャの解決ができなかったためです。

 WriteLine(string? value)
 WriteLine(string format, object? arg0)