DOBON.NETプログラミング道掲示板
(現在 過去ログ3 を表示中)

[ 最新記事及び返信フォームをトピックトップへ ]

■33959 / inTopicNo.1)  IEnumerableについてまとめてみました。
  
□投稿者/ いちご 一般人(5回)-(2018/08/13(Mon) 21:56:25)
  • アイコン環境/言語:[VB.NET] 
    分類:[.NET] 

    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プロパティに値が設定されないという点です。

引用返信 削除キー/
■33960 / inTopicNo.2)  Re[1]: IEnumerableについてまとめてみました。
□投稿者/ 魔界の仮面弁士 大御所(1141回)-(2018/08/14(Tue) 04:21:22)
  • アイコンNo33959に返信(いちごさんの記事)
    > IEnumerablelインターフェースを実装する方法についてまとめました。
    「IEnumerablel」ではなく
    「IEnumerable」ですね。もしくは IEnumerable(Of ) でも良いですが。


    > IEnumerableを実装がされていると、GetEnumerator()メソッドを実行すると
    > コレクションを反復処理できる列挙子(IEnumeratorが実装されているインスタンス)が
    > 返ってくることが保証されるクラスとなります。
    その通りです。さらに補足するならば、
    『IEnumerable インターフェイス の GetEnumerator』メソッドさえ実装されていれば、
    そのクラス(または構造体)自身には、GetEnumerator という名のメソッドが無くても構いません。


    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

    Public Structure Sample1 'クラスでも構造体でも構わない
      Implements IEnumerable 'IEnumerable または IEnumerable(Of ) の実装が必要

      '実装時のメソッド名は 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




    > IEnumeratorを実装するということは、
    > Currentプロパティ、MoveNext()メソッド、Reset()メソッドが実装されていることを指し、
    > その方法には、連結リストを用いて独自に機能を実装する方法や、
    > 既に、IEnumeratorを実装しているArrayクラスやList(of T)クラスや、
    > Yieldステートメントを用いて外部的に等価の機能的を実装する方法があります。

    そうですね。


    > IEnumeratorが実装されていると、Currentプロパティで、
    > コレクション内の列挙子の現在位置にある要素が取得でき、
    > MoveNext()メソッドを実行すると列挙子に次の要素があれば、
    > Currentプロパティにその要素の参照を代入しTrueを返し、
    > 次の要素がなければFalseが返されることが保証されます。

    そうですね。そして Current と MoveNext の実装は必須です。


    > Reset()メソッドは、列挙子を初期位置(コレクションの最初の要素の前:一般的にはNotihing)に設定します。

    一方で、Reset 動作は必須では無いため、ただの空実装になっていたり、
    単に Throw New NotSupportedException() するだけの実装で済まされることもしばしばあります。


    > 即ち、反復処理を行うには、MoveNext()メソッドを実行してからCurrentプロパティで取得することを繰り返す必要があります。
    > ポイントとしては、MoveNext()メソッドを呼び出さなければ、Currentプロパティに値が設定されないという点です。

    提示頂いたコードだと、MoveNext せずに Current が呼ばれるとエラーになりますね。
    ただ、すべての実装がそうなっているというわけではありません。


    Current を呼ぶ前には、事前に MoveNext を呼び出すルールにはなっていますが、
    MoveNext せずに Current を呼び出した場合の動作は実装依存だったりします。

    事前に MoveNext していないと例外を発生させる実装になっていることもあれば、
    いきなり Current を呼び出しても例外にはならず、単に Nothing を返す物もありますし、
    中には、初回の MoveNext が呼ばれていなければ内部で MoveNext してくれる実装もあります。



    Imports System.Runtime.InteropServices

    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
引用返信 削除キー/
■33961 / inTopicNo.3)  Re[2]: IEnumerableについてまとめてみました。
□投稿者/ いちご 一般人(6回)-(2018/08/14(Tue) 11:05:55)
  • アイコンご丁寧な回答ありがとうございます。大変、勉強になりました。教えていただいたことを踏まえて、備忘録を修正しました。コードは、前回のままで記録したいと考えています。本当にありがとうございました。


    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番目の要素を取り出すことになってしまうからです。

    具体例として、Arrayクラスを用いた、標準のIEnumerableインターフェース実装の動作を確認するためのコードを示します。初期化直後にMoveNextを実行する前に、Currentで取得をすると、'列挙は開始していません。MoveNext を呼び出してください。'とエラーが発生し、MoveNextがFalseを返しているのに、Currentで取得しようとすると、'列挙は既に完了しています。'のエラーが発生します。

解決み!
引用返信 削除キー/



トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

Mode/  Pass/


- Child Tree -