インデクサは自分で作成するクラスの中で定義することもできる。List 12-4にインデクサを実装した例を示す。
1: using System;
2:
3: namespace Sample003
4: {
5: class Class2
6: {
7: private char [] a = { 'A', 'B', 'C' };
8: public char this[int index]
9: {
10: get
11: {
12: return a[index];
13: }
14: set
15: {
16: a[index] = value;
17: }
18: }
19: }
20: class Class1
21: {
22: [STAThread]
23: static void Main(string[] args)
24: {
25: Class2 t = new Class2();
26: for( int i=0; i<3; i++ )
27: {
28: Console.WriteLine( t[i] );
29: }
30: t[0] = 'X';
31: t[1] = 'Y';
32: t[2] = 'Z';
33: for( int i=0; i<3; i++ )
34: {
35: Console.WriteLine( t[i] );
36: }
37: }
38: }
39: }
このプログラムを実行するとFig.12-4のようになる。
ここでは、Class2にインデクサを実装している。インデクサの定義は8〜18行目で行われている。インデクサの宣言は、8行目のようにメソッドの宣言とよく似ている。しかし、メソッドと異なり、名前にはthisを入れて、引数は角括弧([])でくくる。
さて、インデクサを定義する場合は、書き込みと読み出しの2つの動作について記述する。つまり、「a[b] = c」という使い方で値を入れる場合と、「a = b[c]」として値を取り出す場合の、2つの使い方に対する処理内容を記述するのである。
この2つを区別するために、getとsetというキーワードが用意されている。つまり、普通のメソッドの場合はすぐ中身を書けばよいのだが、インデクサの場合は、もう1段階、getかsetかを示す記述を入れる必要がある。10〜13行目のように、getキーワードに続けて中括弧({})を記述し、その中に読み出し時の実行内容を記述する。この場合は値を返したいので、return文を記述して、そこで返すべき値を記述する。一方、書き込みの場合は、14〜17行目のように、setキーワードに続いて中括弧({})を記述する。中に記述するのは処理内容だが、書き込むべき値はvalueというキーワードで参照できる。valueはサンプル・ソース上のどこにも宣言されていないが、C#の言語仕様で決められた特別なキーワードなので、宣言なしで使用できる。
実際に処理する内容は、7行目で宣言した配列変数の値を読み書きするだけなので、これだけなら配列変数を宣言するほうが手っ取り早い。実際にインデクサを使う場合は、もっと込み入った処理内容を持つことになる。ここではサンプルとしての分かりやすさを重視して、シンプルな配列の読み書きの機能を実装している。
それでは、インデクサにアクセスする側のコードを見てみよう。まず、25行目でインデクサを持つオブジェクトを作成している。26〜29行目は、オブジェクトの中身を表示するコードである。これを実行すると初期値であるA、B、Cが読み出される。もちろん、28行目の「t[i]」を実行すると、10〜13行目が処理されるのである。次に30〜32行目の「t[0]」などを実行すると、それぞれ、14〜17行目が呼び出される。これによって、配列aの中身は書き換わる。33〜36行目では、もう一度、26〜29行目と同じことを行うが、この段階で出力されるのは、書き換えられた結果の値X、Y、Zになる。
インデクサを定義する場合はgetとsetの2つの内容を記述するのが普通だが、場合によっては、getだけ、あるいはsetだけを記述するということも可能である。これは、読み出し専用、あるいは、書き込み専用のインデクサを作れるということである。
ここでは読み出し専用インデクサの例を見てみよう。List 12-5はList 12-4のサンプル・ソースからset処理を取り除いたものである。
1: using System;
2:
3: namespace Sample004
4: {
5: class Class2
6: {
7: private char [] a = { 'A', 'B', 'C' };
8: public char this[int index]
9: {
10: get
11: {
12: return a[index];
13: }
14: }
15: }
16: class Class1
17: {
18: [STAThread]
19: static void Main(string[] args)
20: {
21: Class2 t = new Class2();
22: for( int i=0; i<3; i++ )
23: {
24: Console.WriteLine( t[i] );
25: }
26: // t[0] = 'X'; // プロパティまたはインデクサ 'Sample004.Class2.this[int]' は読み取り専用なので割り当てすることはできません。
27: }
28: }
29: }
このプログラムを実行するとFig.12-5のようになる。
ここで注目すべき点は、8〜14行目のインデクサの定義にsetキーワードが含まれないことである。このサンプル・ソースでは、24行目のようにインデクサから読み出すコードは何ら問題なく実行できる。しかし、26行目のように、書き込みを行うコードを記述すると、コンパイラがエラーを出してしまう。
このような機能はどんな場合に使えるだろうか? 例えば複数のディスク・ドライブの残り容量を集計して、インデクサでアクセスできるようなクラスを作ったとしよう。このようなクラスの場合、残り容量の情報を読み出すことには意味があるが、書き込むことに意味はない。いくら、残り容量に大きな値を代入できたとしても、それで実際のディスク・ドライブの残り容量が増えるわけではないのだ。また通信を行うアプリケーションソフトでは、インデクサで値が代入されると同時にデータを送信して手許に残さないという実装もありうる。その場合、値の読み出しはやりたくてもできないことになるので、書き込み専用のインデクサとして実装する意味がある。
インデクサの角括弧([])の内側に記述する値は整数とは限らない。実際に添え字に文字列を使った例をList 12-6に示そう。
1: using System;
2: using System.Collections;
3:
4: namespace Sample005
5: {
6: class Class1
7: {
8: [STAThread]
9: static void Main(string[] args)
10: {
11: Hashtable h = new Hashtable();
12: h["斉藤"] = "Windows 2000";
13: h["田中"] = "Windows 98";
14: h["鈴木"] = "FreeBSD";
15: Console.WriteLine( h["斉藤"] );
16: Console.WriteLine( h["田中"] );
17: Console.WriteLine( h["鈴木"] );
18: }
19: }
20: }
このプログラムを実行するとFig.12-6のようになる。
awkというスクリプト言語をご存じの方であれば、「ああ、連想配列だ」と思うかもしれない。これは、.NET Frameworkに標準で含まれるHashtable(System.Collections.Hashtable)クラスを用いて記述したサンプルである。Hashtableクラスのインデクサは、読み書きする値も添え字もobject型として定義されている。そのため、原理的には、どんな数値であろうと、文字列であろうと、どんなオブジェクトであろうと、添え字に指定することができる。配列を数字でしか指定したことのない人には信じられないかもしれないが、文字列で指定する配列(連想配列)というのは、なかなか便利な機能である。こういう使い方もあるということを、頭の片隅に入れておくとよいだろう。
Copyright© Digital Advantage Corp. All Rights Reserved.