これは、Visual Basic Advent Calendar 2012 12/3日の記事。
VB2012(VB11)から、反復子のYieldステートメントが使えるようになりました。
yield自体は、C#2.0(Visual Studio 2005)から追加された機能でC#erからすれば特に目新しい機能ではありません。
(VBではC#にある「yield break」が無いです)
また、yield文は他言語ではPhythonやJavascript 1.7やRubyで使用できます。(※Rubyに関しては使い方が異なります)
反復子のYeildステートメントとは、どのような機能であるのかを書いていきたいと思います。
まず、下記のプログラムの構文と出力結果を見ると、一般的な手続き型言語での解釈では、どうしてそうなるのか非常に分かりにくいものとなります。
Module Test
Sub Main()
For Each number As Integer In someNumbers()
Console.Write(number & " ")
Next
'Output: 3,5,8
Console.ReadLine()
End Sub
Private Iterator Function someNumbers() As System.Collections.IEnumerable
Yield 3
Yield 5
Yield 8
End Function
End Module
簡単に説明していくと、someNumersメソッドで3,5,8という数値リストを単純に生成しています。
イメージ的には、Dim someNumbers As New List(Of Integer)({3, 5, 8}) となるのですが、Yeildでは評価するタイミングが違うのです。
一般的な手続き型言語の解釈からすると、someNumbers() が逐次実行されたときに、内部上に動的な配列が生成された値を使用しているように思いますよね、それでは先ほどのList(Of Integer)と同じになってしまいます。
Yeildでは遅延評価といって、必要な時にはじめて評価する方法です。
といっても、上記のプログラムではさすがに分かりにくいため、もう少し分解してみましょう。
For Eachの正体は、デザインパターンでいう「イテレータ」であり、VB.NETのコンパイラによりコンパイル時に展開される。
http://www.atmarkit.co.jp/fdotnet/basics/oop07/oop07_03.html
Module Test
Sub Main()
Dim x As IEnumerator = someNumbers()
x.MoveNext()
Console.Write(x.Current) 'Output: 3
x.MoveNext()
Console.Write(x.Current) 'Output: 5
x.MoveNext()
Console.Write(x.Current) 'Output: 8
Console.ReadLine()
End Sub
Private Iterator Function someNumbers() As System.Collections.IEnumerable
Dim i As Integer
i = 3
Yield i
i = 5
Yield i
i = 8
Yield i
End Function
End Module
以下に、手順を示しますが、実際にステップ実行して見ると分かりやすいと思います。
1. x に IEnumerator 型のインスタンスを受け取る(遅延評価のため、この段階ではリストは生成されない)
2. x.MoveNext で、someNumbers メソッドが呼び出される
3. someNumbers メソッドの、最初の Yield i によって、その時点の i の値である 3 が返される
4. Mainに戻り、返された値(x.Current)を表示する
5. x.MoveNext で、someNumbers メソッドが呼び出され、前回実行した「Yield i」の次のステートメントが実行される。
6. someNumbers メソッドで、次の Yield i によって、その時点の i の値である 5 が返される
7. Mainに戻り、返された値(x.Current)を表示する
8. x.MoveNext で、someNumbers メソッドが呼び出され、前回実行した「Yield i」の次のステートメントが実行される。
9. someNumbers メソッドで、次の Yield i によって、その時点の i の値である 8 が返される
10. Mainに戻り、返された値(x.Current)を表示する
このようにYieldステートメントを使用することで、処理を中断した後にその続きから処理を再開できることが出来ます。
これを「コルーチン」と呼びます。
現在、someNumbersメソッドのローカル変数 i に直接値をセットしていますが、i=5のところを i = i + 2、i=8のところを i = i + 3としても結果は同じになり、ローカル変数の値は保持されていることが分かるでしょう。
次回は、Yieldステートメントを使用したフィナボッチ数列を求めるプログラムを作成します。