[C#] IEnumerable<> と IEnumerator<> を使って独自のリストを作ってみる

HtmlDom のイテレーターを作る前に、List<> で利用される LINQ がどう動くのかをチェック。
手始めには、IEnumerable と IEnumerator を使えばよいとのことなので、自前で作ってみます。

IEnumerable(T) インターフェイス (System.Collections.Generic)
http://msdn.microsoft.com/ja-jp/library/9eekhta0.aspx
IEnumerable.GetEnumerator メソッド (System.Collections)
http://msdn.microsoft.com/ja-jp/library/system.collections.ienumerable.getenumerator.aspx

MSDN 自体に既にサンプルコードが載っているのでそれを使っていきます。

■ランダムな値を返す RandomNum リストを作る

ランダムな値を返すならば、Random を使えばいいだけなのですが、まぁ、これを LINQ で使えるようにします。

1
2
3
foreach ( var i in rnd ) {
  Debug.Print( i );
}

なんてことをやると無限に繰り返してしまうようなモノですね(苦笑)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class RandomNum : IEnumerable<int>
{
    int _max ;
    public RandomNum(int max)
    {
        _max = max;
    }
 
    public IEnumerator<int> GetEnumerator()
    {
        return new RandomNumEnum(_max);
    }
 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return new RandomNumEnum(_max);
    }
}
class RandomNumEnum : IEnumerator<int>
{
    int _max;
    Random _rnd;
    int _cur;
    public RandomNumEnum(int max)
    {
        _max = max;
        _rnd = new Random();
    }
    public int Current
    {
        get { return _cur; }
    }
 
    public void Dispose()
    {
    }
 
    object System.Collections.IEnumerator.Current
    {
        get { return _cur; }
    }
 
    public bool MoveNext()
    {
        _cur = _rnd.Next(_max);
        return true;
    }
 
    public void Reset()
    {
        _rnd = new Random();
    }
}

RandomNum が、List<int> にあたるコレクションで、RandomNumEnum がイテレーターです。ちょうど C++ の list<int> と list<int>::iterator の関係…でいいのかな? IEnumerable, IEnumerator で宣言されているメソッドは、右クリック「インターフェース実装」→「インターフェースの実装」を選択すると Visual Studio がひな形を作ってくれます。

これを利用する場合は、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void button1_Click(object sender, EventArgs e)
{
    // 初期化
    RandomNum rnd = new RandomNum(100);
    // 最初の50件を取得
    listBox1.DataSource = rnd.Take(50).ToList();
}
 
private void button2_Click(object sender, EventArgs e)
{
    // 初期化
    RandomNum rnd = new RandomNum(100);
    // 10から20までの値を取得
    listBox1.DataSource = rnd.Where(x => 10 <= x && x <= 20)
        .Take(30).ToList();
}

上記のように、Take メソッドを使って最初の50件を取得します。foreach を使うと無限に取得できるので暴走するハズです。
また、where メソッドを使って条件を付けることもできます。あまり意味がありませんが。

実行するとこんな感じ。

普通にランダムに表示したのと変わりませんが、一気にランダムな値を作る時に便利かと(便利なのか?)

■サイクリックなコレクションを作る

なんとなくコレクションの使い方が分かったので、今度はサイクリックな配列を作ってみます。最後の要素の次が先頭になってぐるぐるとまわるだけのリストです。

ランダム値と同じように、IEnumerable<string> を実装するリストと、IEnumerator<string> を実装するイテレーターを作ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class CyclicArray : IEnumerable<string>
{
    List<string> _lst;
    public CyclicArray(List<string> lst)
    {
        _lst = lst;
    }
 
    public IEnumerator<string> GetEnumerator()
    {
        return new CyclicArrayEnum(_lst);
    }
 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return new CyclicArrayEnum(_lst);
    }
 
}
class CyclicArrayEnum : IEnumerator<string>
{
    List<string> _lst;
    int _pos;
    public CyclicArrayEnum(List<string> lst)
    {
        _lst = lst;
        _pos = -1;
    }
 
    public string Current
    {
        get { return _lst[_pos] ; }
    }
 
    public void Dispose()
    {
    }
 
    object System.Collections.IEnumerator.Current
    {
        get { return _lst[_pos]; }
    }
 
    public bool MoveNext()
    {
        _pos++;
        if (_pos >= _lst.Count)
            _pos = 0;
        return true;
        throw new NotImplementedException();
    }
 
    public void Reset()
    {
        _pos = -1;
    }
}

サイクリックなので、CyclicArrayEnum::MoveNext メソッドは必ず true を返します。これも foreach をすると無限大のパターンになるのですが、まあ良しとします。内部的には _pos で現在位置を保存しておいて、末尾に来ると先頭に戻すという処理を入れます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void button3_Click(object sender, EventArgs e)
{
    var lst = new List<string>() {
        "1:masuda",
        "2:tomoaki",
        "3:yamasaki",
        "4:yumi",
        "5:kaho",
    };
    // 初期化
    var ca = new CyclicArray(lst);
    // 13件取得する
    listBox1.DataSource =
        ca.Take(13).ToList();
}

 

使う場合は、こんな風に List<string> を渡して、Take メソッドで一定数を取るというスタイルですね。
配列としては5つの要素しかないのですが、take で 13個を要求するとサイクリックに返してくれます。
簡単ですが、これでコレクションとイテレーターは作れそうなのが分かりました、と。

■ツリー構造を子孫まで巡って探すイテレーターはどのように作るのか?

普通のコレクションの場合は一段階しか要素を探しに行かないのですが、XML 構造のようにツリー構造になっていると再帰的に子要素を探す必要があるのです。

なので、これを擬似的に書いてみると…なところで詰まってしまったので後日。

カテゴリー: C# パーマリンク