再入門C#：yield

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

# C# の yield

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

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

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

こんな風に使います

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

すると、１秒ごとに

```
Number: 0
Number: 1
  :
```

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

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

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

```csharp
        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を自分で実装してみましょう。うお、めっちゃ簡単や！


```csharp
    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;
            }
        }
    }
```

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

```csharp
        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;
            });
        }
```
じゃあ、しっかり要素にアクセスしてみましょう。

```csharp
        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<T> 

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

```csharp
        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}");
            }
        }
```


# 参考

* [非同期ストリーム](https://ufcpp.net/study/csharp/async/asyncstream/)
* [C#におけるyieldの挙動](https://www.taihei.tips/entry/2013/08/07/C%23%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8Byield_return%E3%81%AE%E6%8C%99%E5%8B%95)
* [IEnumerator<T> Interface](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerator-1?view=netframework-4.8)
* [IEnumerable<T> Interface](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1?view=netframework-4.8)