For Eachで使えるクラスを自作しようと勉強しています。 備忘録として、自分なりにまとめてみたのですが、イマイチ自信がありません。 内容的には、次のような理解で合っているのでしょうか? 詳しい方、チェックして頂けませんでしょうか?
For Each…Next対応コレクションを自作するために、IEnumerablelインターフェースを実装する方法についてまとめました。 IEnumerableを実装がされていると、GetEnumerator()メソッドを実行するとコレクションを反復処理できる列挙子(IEnumeratorが実装されているインスタンス)が返ってくることが保証されるクラスとなります。(そのようにクラスを構築する義務があるということ) IEnumeratorを実装するということは、Currentプロパティ、MoveNext()メソッド、Reset()メソッドが実装されていることを指し、その方法には、連結リストを用いて独自に機能を実装する方法や、既に、IEnumeratorを実装しているArrayクラスやList(of T)クラスや、Yieldステートメントを用いて外部的に等価の機能的を実装する方法があります。
IEnumeratorが実装されていると、Currentプロパティで、コレクション内の列挙子の現在位置にある要素が取得でき、MoveNext()メソッドを実行すると列挙子に次の要素があれば、Currentプロパティにその要素の参照を代入しTrueを返し、次の要素がなければFalseが返されることが保証されます。Reset()メソッドは、列挙子を初期位置(コレクションの最初の要素の前:一般的にはNotihing)に設定します。即ち、反復処理を行うには、MoveNext()メソッドを実行してからCurrentプロパティで取得することを繰り返す必要があります。 具体例として、ArrayクラスがIEnumerableインターフェースを実装していることを確認するためのコードを示します。 (Windowsフォームにボタンを4つ配置) Public Class Form1 Dim TestArray As Array Dim TestArrayEnumerator As IEnumerator Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '初期化 TestArray = {1, 3, 5, 7, 9} TestArrayEnumerator = TestArray.GetEnumerator End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click 'Current MsgBox(TestArrayEnumerator.Current) End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click 'MoveNext TestArrayEnumerator.MoveNext() End Sub
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click 'Reset TestArrayEnumerator.Reset() End Sub End Class ポイントとしては、MoveNext()メソッドを呼び出さなければ、Currentプロパティに値が設定されないという点です。
Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click For Each o As Object In CType(Nothing, Sample1) MsgBox(o) Next For Each o As Object In New Sample2() MsgBox(o) Next End Sub End Class
'実装時のメソッド名は GetEnumerator でなくても良いし、 'Public である必要もない Private Function Ichigo() As IEnumerator Implements IEnumerable.GetEnumerator '返却値は「IEnumerator インターフェイス」を '実装したクラス(または構造体)でなければならない Return "test".ToCharArray().GetEnumerator() End Function End Structure
Public Class Sample2 Implements IEnumerable 'VB11(VB2012) や C#2.0(C#2005)以上であれば、IEnumerator を持つクラスを '用意する代わりに、イテレーター構文を使って簡単に列挙値を返却できる Private Iterator Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator Yield Now Yield True Yield "String Data" Yield 12345 Yield 12345.67 Yield 123.456D End Function End Class
Public Class Form1 Private rs As Object Private TestArrayEnumerator As IEnumerator Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click rs = CreateObject("ADODB.Recordset") Dim fs As Object = rs.Fields fs.Append("Col1Int", 3) fs.Append("Col2Str", 202, 4)
rs.Open() rs.AddNew(New Object() {"Col1Int", "Col2Str"}, New Object() {100, "ABCD"}) rs.AddNew(New Object() {"Col1Int", "Col2Str"}, New Object() {200, "EFGH"}) rs.AddNew(New Object() {"Col1Int", "Col2Str"}, New Object() {300, "IJKL"}) rs.Update() rs.MoveFirst()
'Dim eo As Object = CallByName(fs, "[DispId=-4]", CallType.Get) Dim eo As Object = fs._NewEnum TestArrayEnumerator = DirectCast(eo, ICustomAdapter).GetUnderlyingObject() End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click 'Current Dim o As Object = TestArrayEnumerator.Current If o Is Nothing Then MsgBox("(Nothing)") Else MsgBox(o.Name & "=" & o.Value) End If End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click 'MoveNext MsgBox("MoveNext() : " & TestArrayEnumerator.MoveNext()) End Sub
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click 'Reset TestArrayEnumerator.Reset() End Sub End Class
IEnumerableを実装したクラスは、GetEnumerator()メソッドを実行することによりコレクションを反復処理できる列挙子(IEnumeratorを実装したインスタンス)が返ってくることを保証します。(そのようにクラスを構築する義務がある)厳密にいうと、列挙士を返すメソッド名は、GetEnumeratorである必要がなく、Implementsキーワードでインターフェース名.メソッド名(IEnumerable.GetEnumerator)を指定したメソッドがあればクラスでも構造体でも問題ありません。また、Private属性を付けても For Each…Next文は、内部的に呼び出して問題なく機能します。 IEnumeratorを実装すると、Currentプロパティ、MoveNext()メソッド、Reset()メソッドの実装が強要されますが、For Each…Next対応コレクションという点でみると、For Each…Next構文では使用されないが無いとエラーとなるReset()メソッドは、空実装やThrow New NotSupportedException()を返すだけのメソッドとする場合があります。Currentプロパティ、MoveNext()メソッドの実装方法には、連結リストを用いて独自に機能を組込む方法、既に、IEnumeratorを実装しているArrayクラスやList(of T)クラスを用いる方法、Yieldステートメントを用いて外部的に等価な機能を実現する方法があります。
Currentプロパティには、コレクション内の列挙子の現在位置にある要素が取得でる機能を、MoveNext()メソッドには、列挙子に次の要素があれば、Currentプロパティにその要素の参照を代入た上でTrueを返し、次の要素がなければFalseを返す機能が求められます。MoveNext()メソッドを実行して次の要素があることを確認した上で、Currentプロパティで要素を取得することが、基本的なルールとなります。 If (IEnumeratorを実装したインスタンス).MoveNext Then Currentを実行 End If 一方、Reset()メソッドは、列挙子を初期位置に設定する機能が求められますが、前述のMoveNextメソッドとCurrentプロパティの関係から、コレクションの最初の要素の前を初期位置とする必要があります。(一般的にはNotihing)何故ならば、列挙士の先頭を代入すると、MoveNext()メソッドを実行した段階で、2番目の要素を取り出すことになってしまうからです。