連載

C#入門

第12 インデクサとプロパティ

(株)ピーデー
川俣 晶
2001/09/27


インデクサを自作する

 インデクサは自分で作成するクラスの中で定義することもできる。以下にインデクサを実装した例を示す。

 1: using System;
 2:
 3: namespace ConsoleApplication40
 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:     static void Main(string[] args)
23:     {
24:       Class2 t = new Class2();
25:       for( int i=0; i<3; i++ )
26:       {
27:         Console.WriteLine( t[i] );
28:       }
29:       t[0] = 'X';
30:       t[1] = 'Y';
31:       t[2] = 'Z';
32:       for( int i=0; i<3; i++ )
33:       {
34:         Console.WriteLine( t[i] );
35:       }
36:     }
37:   }
38: }
インデクサを実装したサンプル・プログラム4
インデクサの宣言はメソッドと異なり、名前にはthisを入れて、引数は角括弧[]でくくる。そしてget、set、valueのキーワードを用いて記述する。

 このプログラムを実行すると以下のようになる。

サンプル・プログラム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行目で宣言した配列変数の値を読み書きするだけなので、これだけなら配列変数を宣言する方が手っ取り早い。実際にインデクサを使う場合は、もっと込み入った処理内容を持つことになる。ここではサンプルとしての分かりやすさを重視して、シンプルな配列の読み書きの機能を実装している。

 それでは、インデクサをアクセスする側のコードを見てみよう。まず、24行目でインデクサを持つオブジェクトを作成している。25〜28行目は、オブジェクトの中身を表示するコードである。これを実行すると初期値であるA、B、Cが読み出される。もちろん、27行目のt[i]を実行すると、10〜13行目が処理されるのである。次に29〜31行目のt[0]などを実行すると、それぞれ、14〜17行目が呼び出される。これによって、配列aの中身は書き変わる。32〜35行目では、もう一度、25〜28行目と同じことを行うが、この段階で出力されるのは書き換えられた結果の値X、Y、Zになる。

読み出し専用インデクサ

 インデクサを定義する場合はgetとsetの2つの内容を記述するのが普通だが、場合によっては、getだけ、あるいはsetだけを記述するということも可能である。これは、読み出し専用、あるいは、書き込み専用のインデクサを作れるということである。

 ここでは読み出し専用インデクサの例を見てみよう。これは手前のサンプルソースからset処理を取り除いたものである。

 1: using System;
 2:
 3: namespace ConsoleApplication41
 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:     static void Main(string[] args)
19:     {
20:       Class2 t = new Class2();
21:       for( int i=0; i<3; i++ )
22:       {
23:         Console.WriteLine( t[i] );
24:       }
25:       //t[0] = 'X';   // プロパティまたはインデクサ 'ConsoleApplication41.Class2.this[int]' は読み取り専用なので割り当てることはできません。
26:     }
27:   }
28: }
読み出し専用インデクサを使用したサンプル・プログラム5
set処理を記述しない場合、インデクサは読み込み専用となる。読み出し専用インデクサへの書き込みはコンパイル・エラーとなる。

 このプログラムを実行すると以下のようになる。

サンプル・プログラム5の実行結果
インデクサを使用して配列の読み出しを行っている。

 ここで注目すべき点は、8〜14行目のインデクサの定義にsetキーワードが含まれないことである。このサンプルソースでは、23行目のようにインデクサから読み出すコードは何ら問題なく実行できる。しかし、25行目のように、書き込みを行うコードを記述すると、コンパイラがエラーを出してしまう。

 このような機能はどんな場合に使えるだろうか。たとえば複数のディスクドライブの残り容量を集計してインデクサでアクセスできるようなクラスを作ったとしよう。このようなクラスの場合、残り容量の情報を読み出すことには意味があるが、書き込むことに意味はない。いくら、残り容量に大きな値を代入できたとしても、それで実際のディスクドライブの残り容量が増えるわけではないのだ。また通信を行うアプリケーションソフトでは、インデクサで値が代入されると同時にデータを送信して手元に残さないという実装もあり得る。その場合、値の読み出しはやりたくてもできないことになるので、書き込み専用のインデクサとして実装するのである。

数値以外で指定するインデクサ

 インデクサの角括弧[]の内側に記述する値は整数とは限らない。実際に添え字に文字列を使った例を以下に示そう。

 1: using System;
 2: using System.Collections;
 3:
 4: namespace ConsoleApplication42
 5: {
 6:   class Class1
 7:   {
 8:     static void Main(string[] args)
 9:     {
10:       Hashtable h = new Hashtable();
11:       h["斉藤"] = "Windows 2000";
12:       h["田中"] = "Windows 98";
13:       h["鈴木"] = "FreeBSD";
14:       Console.WriteLine( h["斉藤"] );
15:       Console.WriteLine( h["田中"] );
16:       Console.WriteLine( h["鈴木"] );
17:     }
18:   }
19: }
インデクサの添え字に文字列を使用したサンプル・プログラム6
インデクサを使用しているHashtableクラスでは連想配列が可能になる。

 このプログラムを実行すると以下のようになる。

サンプル・プログラム6の実行結果
文字列をキーとして、配列の値を取得することができる。

 awk(というスクリプト言語)をご存じの方であれば、「ああ、連想配列だ」と思うかもしれない。これは、.NET Frameworkに標準で含まれるHashtable(System.Collections.Hashtable)クラスを用いて記述したサンプルである。Hashtableクラスのインデクサは、読み書きする値も、添え字も、object型として定義されている。そのため、原理的には、どんな値であろうと、文字列であろうと、どんなオブジェクトであろうと、添え字に指定することができる。配列を数字でしか指定したことのない人には信じられないかもしれないが、文字列で指定する配列(連想配列)というのは、なかなか便利な機能である。こういう使い方もあるということを、頭の片隅に入れておくとよいだろう。


 INDEX
  第12回 インデクサとプロパティ
    1.インデクサとは何か
  2.インデクサを自作する
    3.プロパティを使う
    4.好ましくない使い方
 
「C#入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間