いろいろ備忘録日記

主に .NET と Python絡みのメモを公開しています。

yieldを用いたコレクションのループ(foreach)

.NET 2.0から新たにyieldというキーワードが追加されました。


これを利用すると、今までIEnumerableを実装していた部分を
ちょっと簡単にできます。


yieldが現れる以前は、IEnumerableとIEnumeratorインターフェースを実装する必要がありました。


ついでに、IEnumerableってどんな時に使うのかというと
自分で作ったコレクションクラスをイテレート可能、つまりforeachで
ループできるようにする場合です。


javaでいうと、イテレータを自分で作っているのと同じような感じです。


yieldの場合は、理屈よりもサンプルを見た方が分かりやすいです。


以下サンプルです。
イテレータの処理については、面倒なのでListクラスのものをそのまま
使ってます。

// vim:set ts=4 sw=4 et ws is nowrap ft=cs:

using System;
using System.Collections.Generic;

namespace Gsf.Samples.CSharp{

    /// 
    /// yield不使用版のカスタムコレクションクラスです。
    ///
    /// Generic対応のIEnumerableを実装しています。
    ///
    /// [実装の際の注意点]
    /// System.Collections.Generic.IEnumerableインターフェースは
    /// System.Collections.IEnumerableインターフェースを継承しています。
    ///
    /// なので、genericの方のGetEnumerator()とnon-genericの方のGetEnumerator()の
    /// 両方を実装しないといけません。
    /// 
    class CustomCollection : IEnumerable{

        List _list;

        public CustomCollection(){
            _list = new List();
        }

        public CustomCollection AddRange(T values){
            _list.AddRange(values);

            return this;
        }

        /// 
        /// Genericな方のGetEnumeratorメソッド。
        /// 
        public IEnumerator GetEnumerator(){
            return _list.GetEnumerator();
        }

        /// 
        /// Genericじゃない方のGetEnumeratorメソッド。
        /// 
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator(){
            return GetEnumerator();
        }
    }

    /// 
    /// カスタムコレクションクラスです。
    ///
    /// シンプルにイテレート可能なクラスを作成する方法であり、
    /// yieldキーワードを使用してIEnumerableをimplementsせずにイテレート可能にします。
    /// 
    class CustomCollection4Yield{

        List _data;

        public CustomCollection4Yield(){
            _data = new List();
        }

        public CustomCollection4Yield AddRange(T values){
            _data.AddRange(values);
            
            return this;
        }

        /// 
        /// イテレータブロックです。
        /// クラス内にこの書式のメソッドが存在している場合で、内部でyieldを使っている場合
        /// コンパイラがコンパイル時に、IEnumeratorを自動作成してくれます。
        /// 
        public IEnumerator GetEnumerator(){
            foreach(T v in _data){
                //
                // 呼び元にて、foreachを利用して本クラスをループしている
                // 場合、yieldがコールされると、現在の値を返し処理を中断。
                // 呼び元が再び次の値を要求すると、処理が復帰し次の値を返す。
                // (つまりコルーチン)
                //
                yield return v;
            }
        }

    }

    class YieldSample : IExecutable{

        /// 
        /// 以下のように戻り値をIEnumerableにし、内部で
        /// yieldを使う事もできる。
        /// 
        IEnumerable GetIterator(){
            yield return "hoge";
            yield return "hehe";
            yield return "fuga";
        }

        public void Execute(){
            foreach(string v in GetIterator()){
                Console.WriteLine(v);
            }

            foreach(string v in new CustomCollection().AddRange(new string{"hoge", "hehe", "fuga"})){
                Console.WriteLine(v);
            }

            foreach(string v in new CustomCollection4Yield().AddRange(new string{"hoge", "hehe", "fuga"})){
                Console.WriteLine(v);
            }
        }

    }

}


補足:VisualBasicの場合は、yieldという構文が存在しないので、C#のようには出来ません。
   直接、IEmumerable(Of T)を実装します。

Public Class Sample
    Implements IEnumerable(Of String)

    Private _list As New List(Of String)()

    Public Function GetEnumerator() As IEnumerator(Of String) Implements IEnumerable(Of String).GetEnumerator
        Return _list.GetEnumerator()
    End Function

    Private Function NonGenericGetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
        Return GetEnumerator()
    End Function
End Class