非同期処理が出てくる中で、yield
が出てきて、はて、C#でどうだっけ?と思ったので書いてみます。yield
は JavaScriptとかで出てきました。ちなみに、アメリカに住んでいると、信号機のところにYeildって書いてあって、その意味は状況を見て曲がっていいよです。
C# の yield
C#のYield は、IEnumerable<T>
や、IEnumerator<T>
が戻り値になるようなメソッドで使用可能です。簡単な例を示すとこんな感じです。
こういうメソッドを書いておいて、
staticIEnumerable<int>Generate(){for(inti=0;i<10;i++){yieldreturni;Thread.Sleep(TimeSpan.FromSeconds(1));}}
こんな風に使います
staticvoidDisplay(){foreach(intiinGenerate()){Console.WriteLine($"Number: {i}");}}
すると、1秒ごとに
Number: 0
Number: 1
:
といった感じで表示されていきます。IEnumerable<T>
は、シンプルなイテレーターを表すインターフェイスで、実装クラスはGetEnumerator()
を実装する必要があります。IEnumerator は、Dispose(), MoveNext(), Reset() の三つのメソッドをもっていて、yield のところで待ち受けをしているのですが、MoveNext() が呼ばれたタイミングで、yieldのところで値が返ります。
この仕組みはLinqでも使われているものです。
これと似たメソッドは下記のように書けますが、1秒ごとに待たせるというのは難しくて、メソッドが呼ばれた時点で、表示がかかる前に、このメソッドが10秒時間がかかってから、一気にすべての行が表示されます。
staticIEnumerable<int>GenerateWithCollection(){varnumbers=newList<int>();for(inti=0;i<10;i++){numbers.Add(i);Thread.Sleep(TimeSpan.FromSeconds(1));}returnnumbers;}
Map を実装する
これを使って何か実装してみましょう。昔私はScalaを書いていて、MapやFlatMapが懐かしいなと思っていました。C#だと、Linqで書けるのですが、あえてMapを自分で実装してみましょう。うお、めっちゃ簡単や!
publicstaticclassExtensions{publicstaticIEnumerable<U>Map<T,U>(thisIEnumerable<T>self,Func<T,U>func){foreach(variteminself){varconverted=func(item);yieldreturnconverted;}}}
じゃあ使ってみよう。ちなみに、これを実行すると何も起こりませんw。なぜなら、yieldで書いたものは遅延評価になるからです。Linqが遅延評価なのも同じインターフェイスを使っているからです。
privatestaticvoidTestOriginalMap(){varnames=newList<string>{"ushio","yamada","kim"};varnamesWithRespect=names.Map(item=>{varwithRespect=$"{item} sama";Console.WriteLine(withRespect);returnwithRespect;});}
じゃあ、しっかり要素にアクセスしてみましょう。
privatestaticvoidTestOriginalMap(){varnames=newList<string>{"ushio","yamada","kim"};varnamesWithRespect=names.Map(item=>{varwithRespect=$"{item} sama";Console.WriteLine(withRespect);returnwithRespect;});foreach(varnameinnamesWithRespect){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キーワードをつけてループを回すことができます。
staticasyncIAsyncEnumerable<int>GenerateAsync(){for(inti=0;i<10;i++){yieldreturni;awaitTask.Delay(TimeSpan.FromSeconds(1));}}staticasyncTaskDisplayAsync(){awaitforeach(variinGenerateAsync()){Console.WriteLine($"NumberAsync: {i}");}}