はじめに
今回はIEnumerable
とyield return
についての記事になります!
ネットサーフィンをしていたところ、以下のような記事を見かけました。
これを見た時にあまりのすごさにビックリしてしまいました。
この中で重要になってくるIEnumerable<T>
とyield return
について取り上げてみたいと思います。
では早速見ていきましょう。
yield文
まずはyield
とは何かを見ていきます。
ステートメントで yield コンテキスト キーワードを使用した場合、メソッド、演算子、または get アクセサーが反復子であることを示します。
コンテキスト キーワード yield - C# リファレンス | Microsoft Docs
反復子の説明はこんな感じ。
反復子を使用して、リストや配列などのコレクションをステップ実行することができます。
C# でのコレクションの反復処理 | Microsoft Docs
なんとなくニュアンスが伝わったかもしれませんが、もっと簡単な言葉で書くとメソッドの処理を一時的に中断し、処理を呼び出し元に返すことができるということです。
以下のコードを見てみてください。
using System; using System.Collections; class Program { static void Main() { foreach(var n in GetSomeNumbers()) { Console.WriteLine(n); } } public static IEnumerable GetSomeNumbers() { Console.WriteLine("yield return 1"); yield return 1; Console.WriteLine("yield return 2"); yield return 2; Console.WriteLine("yield return 3"); yield return 3; } }
これを実行してみるとこんな出力になります。
yield return 1 1 yield return 2 2 yield return 3 3
ここから処理を一時中断,次回の呼び出し時に中断した箇所の次の行から再開されていることが分かります。
また処理を止めたいときはyield break
を使えばOKです。
public static IEnumerable GetSomeNumbers() { Console.WriteLine("yield return 1"); yield return 1; Console.WriteLine("yield break"); yield break; Console.WriteLine("yield return 3"); yield return 3; }
yield return 1 1 yield break
遅延評価
下のコードを見てみてください。
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { List<int> numbers = GetSomeNumbers().ToList(); foreach (var n in numbers) Console.WriteLine(n); } public static IEnumerable<int> GetSomeNumbers() { Console.WriteLine("yield return 1"); yield return 1; Console.WriteLine("yield return 2"); yield return 2; Console.WriteLine("yield return 3"); yield return 3; } }
yield return 1 yield return 2 yield return 3 1 2 3
これはリストをforeach
して要素を取り出していますが、IEnumerable
・IEnumerable<T>
を用いたときと異なる挙動をしています。
List
ではToList()
の段階で処理を行い、foreach
の部分ではほとんどなにもしていません。
しかし2つ前のコードのようにforeach
にIEnumerable<T>
型の変数を使った場合では、必要になった要素をそのたびに(IEnumerator
のMoveNext
が呼ばれるたびに)計算を行っています。
これは遅延評価と呼ばれるもので、上手に使うことでメモリの節約・処理の分散をすることができることもあります。
ただし間違った使い方をすると余分に計算が増えたりと注意してください。
○ダメな例
using System; using System.Collections; class Program { static void Main() { var numbers = GetSomeNumbers(); // GetSomeNumbersが3回も実行されてしまう foreach (var n in numbers) Console.WriteLine(n); foreach (var n in numbers) Console.WriteLine(n); foreach (var n in numbers) Console.WriteLine(n); } public static IEnumerable GetSomeNumbers() { Console.WriteLine("yield return 1"); yield return 1; Console.WriteLine("yield return 2"); yield return 2; Console.WriteLine("yield return 3"); yield return 3; } }
○改善例
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { // この時点でGetSomeNumbersメソッドによる{1, 2, 3}が格納される List<int> numbers = GetSomeNumbers().ToList(); // GetSomeNumbersメソッドは一切呼ばれない foreach (var n in numbers) Console.WriteLine(n); foreach (var n in numbers) Console.WriteLine(n); foreach (var n in numbers) Console.WriteLine(n); } public static IEnumerable<int> GetSomeNumbers() { Console.WriteLine("yield return 1"); yield return 1; Console.WriteLine("yield return 2"); yield return 2; Console.WriteLine("yield return 3"); yield return 3; } }
サンプル
これらを使った簡単なサンプルとして、平方数を求めるプログラムを書いてみましょう。
平方数とは自然数の自乗(二乗)で表される整数のことで、1, 4(=2*2), 9(=3*3),....
で奴ですね。
using System; using System.Collections.Generic; class Program { static void Main() { foreach(var n in GetSquareNumber(10)) { Console.WriteLine(n); } } /// <summary> /// 平方数を列挙する /// </summary> public static IEnumerable<int> GetSquareNumber(int n) { yield return 0; // 一応0も平方数らしい for(int i = 1; i < n; i++) { yield return i * i; } } }
0 1 4 9 16 25 36 49 64 81
さいごに
特に遅延評価は上手に使うとかなり強力なので、是非うまく活用してみてください。
ではまた。