これは、Visual Basic Advent Calendar 2012 12/18日の記事。
今回のYeildネタは、u_1rohさんの下記の記事を基にしている。
C# の yield return の使い道
http://d.hatena.ne.jp/u_1roh/20080302/1204471238
C# でイベントハンドラを yield return してみよう
http://d.hatena.ne.jp/u_1roh/20080909/1220937726
IEnumerator は下記のような状態遷移モデルで捉えることが出来る。
MoveNext
●→○→○→○→◎
状態遷移モデルは結構実装が面倒でコードがぐちゃぐちゃになりやすい。
これを Yield を上手く使えば、状態遷移がすっきりと記述できる。
例として直線作図コマンド。
ビューを2点クリックされたら、その2点を結ぶ直線を作図する機能を見てみよう。
このコマンドは、次のような状態遷移図でモデル化できる。
起動 クリック クリック
●→クリック待ち→クリック待ち→直線描画
素直に組めば、クリックされた回数のカウント変数を用意したり、カウント値の偶数や奇数などで開始点や終了点を判断したり
するなど、「状態遷移」と「イベントドリブン」のコンボはスパゲッティコードの温床となりかねない。
Function Iterator 直線作図() As IEnumerator(Of 状態)
Yield クリック待ち状態 '1点目のクリック待ち
Yield クリック待ち状態 '2点目のクリック待ち
直線オブジェクトを生成
End Function
これならクリック回数をカウントするような妙な変数を用意する必要がない。関心の分離が出来る。
関心の分離 http://ja.wikipedia.org/wiki/%E9%96%A2%E5%BF%83%E3%81%AE%E5%88%86%E9%9B%A2
C#で書かれたプログラムをVBで書き換えてみました。
VBで書き換える際に、悩んだところが「_handler.Current(Me, e)」で「式は値を生成しません」となってしまうこと。これはIEnumerator(Of MouseEventArgs) とすると値を返さないためコンパイルエラーとなってしまう。結局、DelegateTypeとして値を返すのを定義することで解決できた。
Public Class Form1
ReadOnly _entities As New List(Of Action(Of Graphics))()
Private Delegate Function DelegateType(sender As Object, e As MouseEventArgs) As Point
Private _handler As IEnumerator(Of DelegateType)
Public Sub New()
' この呼び出しはデザイナーで必要です。
InitializeComponent()
' InitializeComponent() 呼び出しの後で初期化を追加します。
_handler = Me.DrawLine()
Me.MoveNext()
End Sub
Private Iterator Function DrawLine() As IEnumerable(Of DelegateType)
While True
Dim p1 As New Point()
Dim p2 As New Point()
Yield Function(sender, e) InlineAssignHelper(p1, e.Location)
Yield Function(sender, e) InlineAssignHelper(p2, e.Location)
_entities.Add(Sub(g) g.DrawLine(New Pen(Brushes.Black), p1, p2))
Me.Invalidate()
End While
End Function
Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
Private Sub MoveNext()
If Not _handler.MoveNext() Then
_handler.Dispose()
_handler = Nothing
End If
End Sub
Private Sub Mouse_Click(sender As Object, e As MouseEventArgs) Handles Me.MouseClick
If _handler IsNot Nothing Then
Dim result As Point
result = _handler.Current(Me, e)
Me.MoveNext()
End If
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
For Each draw In _entities
draw(e.Graphics)
Next
End Sub
End Class
次回は、Yieldの特徴を利用してシューティングゲームの考察をしてみます。