さて、ここからはお待ちかねの活用編だ。システムで定義されたインターフェイスを自作クラスに実装すると何ができるか見ていこう。まず最初は、自作クラスをforeach可能にする方法からだ。
C#のforeach文は、Visual Basicなどから取り入れた構文と思われるが、配列などを渡すだけで、自動的にすべての要素を繰り返してくれる便利なものである。これは、.NET Frameworkに含まれる多くのクラスで利用できるが、なぜこれほど多くのクラスで使えるかというと、繰り返しのメカニズムをインターフェイスを経由して実現しているからだ。つまり、インターフェイスさえ実装されていれば、どんなクラスであろうと、foreachで利用できるのである。もちろん、自作クラスでも問題ない。
List 14-7は実際に自作クラスをforeach可能にした例である。
1: using System;
2: using System.Collections;
3:
4: namespace Sample007
5: {
6: class ClassMyEnumerator : IEnumerator
7: {
8: private int pointer;
9: string [] target;
10: object IEnumerator.Current
11: {
12: get { return target[pointer]; }
13: }
14: bool IEnumerator.MoveNext()
15: {
16: if( pointer >= target.GetUpperBound(0) ) return false;
17: pointer++;
18: return true;
19: }
20: void IEnumerator.Reset()
21: {
22: pointer = target.GetLowerBound(0)-1;
23: }
24: public ClassMyEnumerator( string [] array )
25: {
26: target = array;
27: pointer = target.GetLowerBound(0)-1;
28: }
29: }
30: class ClassSample : IEnumerable
31: {
32: string [] ar = { "ABC", "DEF", "GHI" };
33: IEnumerator IEnumerable.GetEnumerator()
34: {
35: return new ClassMyEnumerator(ar);
36: }
37: }
38: class Class1
39: {
40: [STAThread]
41: static void Main(string[] args)
42: {
43: ClassSample c = new ClassSample();
44: foreach( string s in c )
45: {
46: Console.WriteLine(s);
47: }
48: }
49: }
50: }
これを実行するとFig.14-7のようになる。
ここでは2つのインターフェイスが登場している。1つ目は、IEnumerableで、これを実装したクラスは、foreach可能と見なされる。2つ目はIEnumeratorで、実際に要素を1個ずつ列挙する機能を提供する。IEnumerableは、あなた自身の自作クラスに実装し、IEnumeratorはあなた自身の自作クラスを列挙するクラスに実装するものである。
では、30〜37行目を見ていただきたい。ここでは、3つの文字列を内部に持つクラスが定義されている。実際には32行目の配列はそのままforeach可能なので、いちいちインターフェイスを実装しなくてもよいのだが、ここではサンプル・プログラムということで、あえて実装してみよう。foreach可能にするには、IEnumerableを実装するのだが、これには、GetEnumeratorというメソッドだけが宣言されている。そこで、33〜36行目で、そのメソッドを実装している。といっても、込み入ったことは何もしていない。やるべきことは、列挙を実際に行うクラスのインスタンスを作成して、それを返してやることだけだ。
実際に列挙するのは、少々長いが、6〜29行目のClassMyEnumeratorクラスだ。IEnumeratorに含まれるCurrentプロパティ、MoveNextメソッド、Resetメソッドを実装すればよい。このほかに、初期化のために24〜28行目にコンストラクタも書いている。まず、20〜23行目のResetメソッドは、列挙の初期化を行う。列挙は、最初のアイテムの1個手前の状態から始まるので、22行目の最後でマイナス1をしている。次は14〜19行目のMoveNextメソッドである。このメソッドは、次の要素に1個進める機能を持つ。17行目のようにアクセスする対象を示す値をプラス1するのが基本動作だ。そして、いよいよ、データそのものにアクセスするのが、10〜13行目のCurrentプロパティだ。これは読み出し専用で、MoveNextメソッドで進めた場所にある内容を返す。なお、GetUpperBoundメソッドは配列の上限の添え字を返すメソッド、GetLowerBoundは同様に下限の添え字を返すメソッドである。
以上のような処理が記述されていれば、44〜47行目のように、自作クラスのインスタンスを、いきなりforeach文で使うことができる。
.NET Frameworkのクラス・ライブラリには、一次元リスト構造でデータを管理するArrayListクラス(System.Collections.ArrayList)がある。これは、配列に似ているが配列では実現できない機能をいくつか持っていて便利な代物である。例えば、動的にサイズを増加させるというのは、配列ではできないが、ArrayListならできる。ところで、ArrayListには、Sortという便利なメソッドがある。その名のとおり、内容をソートしてくれるというもので、なかなか使いでがありそうだ。さて、そこで問題になるのは、ソートの並び順をどうやって指定するかである。数値の大小や、文字列をロケール順に並び替えるのは、何も考えることはない。だが、込み入った自作クラスのインスタンスを詰め込んだArrayListをソートする場合、どんな基準で並び替えるべきなのだろうか?
これに対する答えは、インターフェイスIComparableを実装する、というものだ。ArrayListに格納するインスタンスが、すべて、このインターフェイスを実装していれば、これを基準にしてSortメソッドは並べ替えを行うことができる。つまり、IComparableを実装することによって、プログラマーはソートの並び順を思いどおりにコントロールできるわけである。List 14-8にその例を示す。
1: using System;
2: using System.Collections;
3:
4: namespace Sample008
5: {
6: class ClassSample : IComparable
7: {
8: public string number;
9: public int CompareTo( object obj )
10: {
11: string s = ((ClassSample)obj).number;
12: if( number == "one" && s == "two" ) return -1;
13: if( number == "one" && s == "three" ) return -1;
14: if( number == "two" && s == "one" ) return 1;
15: if( number == "two" && s == "three" ) return -1;
16: if( number == "three" && s == "one" ) return 1;
17: if( number == "three" && s == "two" ) return 1;
18: return 0;
19: }
20: public ClassSample( string s )
21: {
22: number = s;
23: }
24: }
25: class Class1
26: {
27: [STAThread]
28: static void Main(string[] args)
29: {
30: ArrayList al = new ArrayList();
31: al.Add( new ClassSample("two") );
32: al.Add( new ClassSample("three") );
33: al.Add( new ClassSample("one") );
34: al.Sort();
35: foreach( ClassSample cs in al )
36: {
37: Console.WriteLine( cs.number );
38: }
39: }
40: }
41: }
これを実行するとFig.14-8のようになる。
IComparableは、CompareToというメソッドを1つだけ持つ。このメソッドは、自分自身と、引数として渡されたオブジェクトobjを比較して、小さければ負数、同じなら0、大きければ正数を返す。ここでは文字列one、two、threeを数値の1、2、3に見立てた結果を返している。あとは難しいことはないだろう。31〜33行目ででたらめな順番で値をArrayListに登録しているが、34行目でSortメソッドを実行すると、正しい順番に内容は整列される。もちろん、SortメソッドからCompareToメソッドが何度も呼び出されて、その情報を基準にして並べ替えが行われているのである。
Copyright© Digital Advantage Corp. All Rights Reserved.