15
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

再入門C#:yield

Last updated at Posted at 2020-03-22

非同期処理が出てくる中で、yieldが出てきて、はて、C#でどうだっけ?と思ったので書いてみます。yieldは JavaScriptとかで出てきました。ちなみに、アメリカに住んでいると、信号機のところにYeildって書いてあって、その意味は状況を見て曲がっていいよです。

C# の yield

C#のYield は、IEnumerable<T>や、IEnumerator<T>が戻り値になるようなメソッドで使用可能です。簡単な例を示すとこんな感じです。

こういうメソッドを書いておいて、

        static IEnumerable<int> Generate()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i; 
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
        }

こんな風に使います

        static void Display()
        {
            foreach (int i in Generate())
            {
                Console.WriteLine($"Number: {i}");
            }
        }

すると、1秒ごとに

Number: 0
Number: 1
  :

といった感じで表示されていきます。IEnumerable<T>は、シンプルなイテレーターを表すインターフェイスで、実装クラスはGetEnumerator()を実装する必要があります。IEnumerator は、Dispose(), MoveNext(), Reset() の三つのメソッドをもっていて、yield のところで待ち受けをしているのですが、MoveNext() が呼ばれたタイミングで、yieldのところで値が返ります。

この仕組みはLinqでも使われているものです。

これと似たメソッドは下記のように書けますが、1秒ごとに待たせるというのは難しくて、メソッドが呼ばれた時点で、表示がかかる前に、このメソッドが10秒時間がかかってから、一気にすべての行が表示されます。

        static IEnumerable<int> GenerateWithCollection()
        {
            var numbers = new List<int>();
            for (int i = 0; i < 10; i++)
            {
                numbers.Add(i);
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }

            return numbers; 
        }

Map を実装する

これを使って何か実装してみましょう。昔私はScalaを書いていて、MapやFlatMapが懐かしいなと思っていました。C#だと、Linqで書けるのですが、あえてMapを自分で実装してみましょう。うお、めっちゃ簡単や!

    public static class Extensions
    {
        public static IEnumerable<U> Map<T, U>(this IEnumerable<T> self, Func<T, U> func)
        {
            foreach (var item in self)
            {
                var converted = func(item);
                yield return converted;
            }
        }
    }

じゃあ使ってみよう。ちなみに、これを実行すると何も起こりませんw。なぜなら、yieldで書いたものは遅延評価になるからです。Linqが遅延評価なのも同じインターフェイスを使っているからです。

        private static void TestOriginalMap()
        {
            var names = new List<string>
            {
                "ushio",
                "yamada",
                "kim"
            };

            var namesWithRespect = names.Map(item =>
            {
                var withRespect = $"{item} sama";
                Console.WriteLine(withRespect);
                return withRespect;
            });
        }

じゃあ、しっかり要素にアクセスしてみましょう。

        private static void TestOriginalMap()
        {
            var names = new List<string>
            {
                "ushio",
                "yamada",
                "kim"
            };

            var namesWithRespect = names.Map(item =>
            {
                var withRespect = $"{item} sama";
                Console.WriteLine(withRespect);
                return withRespect;
            });

            foreach (var name in namesWithRespect)
            {
                Console.WriteLine($"{name} sama de gozaimasu ne.");
            }
        }

実行結果

ushio sama
ushio sama sama de gozaimasu ne.
yamada sama
yamada sama sama de gozaimasu ne.
kim sama
kim sama sama de gozaimasu ne.

非同期型のIEnumerable

非同期型のものもあります。IAsyncEnumerable<T>を返す型を定義すると、asyncメソッドの中でyieldを使うことができます。その際、イテレータは、foreachにawaitキーワードをつけてループを回すことができます。

        static async IAsyncEnumerable<int> GenerateAsync()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i;
                await Task.Delay(TimeSpan.FromSeconds(1));
            }
        }

        static async Task DisplayAsync()
        {
            await foreach (var i in GenerateAsync())
            {
                Console.WriteLine($"NumberAsync: {i}");
            }
        }

参考

15
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?