機能の拡張 -インターフェイス-

ホームC#プログラミング応用講義 > インデクサ

目次

インデクサとは?

ついに最後の項目になりました。今回は最後にいろいろ述べたいので、前置きはなしにします。

インデクサとは記号「 [ ] 」に対して働きを与えることです。演算子のオーバーロードでは演算子に対して働きを与えることができました。
今回は [ ] この記号に働きを与えます。

そもそも、[ ] この記号はすでに意味を持っています。これは配列のインデックスを指定する際に利用します。
よって、新たに意味を付け加えるのですから「[ ] のオーバーロード」と言い表せるでしょう。

インデクサで [ ] に独自の機能を実装することにより得られる最大のものは「配列のインデックス範囲チェック」であるといえます。
配列を扱う際に最も多く発生する例外が「インデックスが配列の範囲を超えている」という例外です。

[ ] この記号に、この例外を未然に防ぐ機能を実装することにより、配列を安全に利用することができます。

インデクサは何も例外の未然処置のためだけにあるのではありません。その他の機能も実装できます。
しかし、配列の範囲内かどうかを調べる役割が最も有益でしょう。

インデクサの定義は以下のようになります。
public 戻り値の型 this[int index]
index はこの変数名でなくともかまいませんが、混乱を防ぐためにインデックスが代入される変数名は index にします。

戻り値の型は通常、扱う配列の型になります。
そしてまた、this と [ の間はスペースなしです。これについては、後にサンプルを見たら納得できるでしょう。

インデクサはプロパティと同様 get set を持ちます。
配列の場合も set のときは value とペアで利用します。おまけに言うと get は return とペアになります。
このあたりはプロパティで既習事項ですので、わからない場合はプロパティに戻りましょう。

では、実際にサンプルを使って例外を未然に防ぐところを見てみましょう。ここでの配列は string 型です。
なぜなら、配列の要素にトリックのキャスト名を代入したいので、文字列型になるためです。最後の最後までトリックでサンプルを書いてみました。

サンプル

using System;

class TRICK
{
    private string[] trick;

    public TRICK(string[] cast)
    {
        // 渡されたstring配列を代入
        trick = cast;
    }

    public int Length
    {
        get
        {
            // 配列の要素数を返す
            return trick.Length;
        }
    }

    // インデクサの実装
    public string this[int index]
    {
        get
        {
            if (TF(index))
                return trick[index];
            else
                return "配列の外です。";
        }
        set
        {
            if (TF(index))
                trick[index] = value;
        }
    }
    
    // 配列の要素数に適しているか判別するメソッド
    private bool TF(int i)
    {
        // 0以上要素数未満
        // 要素数Lengthは0からでなく1から始まるので「未満」
        if (0<=i && i<Length)
            return true;
        else
            return false;
    }
}
class Test
{
    public static void Main(string[] args)
    {
        // 渡すstring配列の用意
        string[] cast = new string[] {"仲間由紀恵", "阿部寛", "生瀬勝久"};
        TRICK tri = new TRICK(cast);
        
        // 表示
        for(int i=0;i<5;i++)
            Console.WriteLine(tri[i]);
        
        // インデクサのsetアクセサを利用して代入
        tri[2] = "野際陽子";
        
        // 表示
        for(int i=0;i<5;i++)
            Console.WriteLine(tri[i]);
    }
}
私自身も整理するためにコメントをつけています。

まず大きく捉えてみましょう。このプログラムは2つのクラスからなっています。TRICKTest です。これらには親子関係(基本・派生)はありません。

TRICK クラスを見てみると5つのメンバがあります。
string[ ] 配列 trick
コンストラクタ 引数は string[ ] 配列
プロパティ Length 戻り値の型は int
インデクサ 戻り値の型は string
メソッド TF 引数は int , 戻り値の型は bool

始めの配列はトリックのキャストを入れる文字列配列です。
コンストラクタによって渡されてきた配列をこの配列に入れるためのものです。よってアクセス修飾子は private で十分。

コンストラクタは今述べたように渡されてきた文字列配列をローカルの配列に入れる作業だけです。

プロパティ Length はその名の通り「配列の要素数を返す」ものです。
ただし、注意すべきなのは get しかないことです。あ、ちなみに get や set のことを「get アクセサ」「set アクセサ」と呼びます。アクセサはメソッドよりは機能が低いのが特徴で、プロパティとインデクサで主に利用されます。

get アクセサしかない場合は「取得」のみのプロパティとなります。設定はできません。する必要がないから、ここでは定義していません。定義すると逆にいけません。

インデクサを飛ばしてメソッド TF を見てみましょう。
このメソッドがまさに配列の範囲内かどうかチェックするメソッドになります。

配列のインデックスは0から要素数-1までです。要素数が3なら、インデックスは 0, 1, 2 の3つですから 2 までになります。
そのためここでは未満(<)を利用してチェックを行っています。範囲内なら true を、範囲外でそのままいくと例外が発生しそうなときは false を返します。

ではインデクサに戻ってみましょう。
取得用の get アクセサではインデックスの値が本当に配列の中にあるかどうかを TF() でチェックし、適していれば指定されたインデックスの要素を返します。
もし違っていれば仕方がないので独自のエラーメッセージを表示しています。

設定用の set アクセサでは、範囲に適していなければ何もしません。すべては適しているときだけです。
適していたら指定されたとおりのインデックスに、指定された文字列を代入します。ここでは string 配列ですから value は string 型と考えられます。

さて、Test クラスに目を移します。
重要なのは1つだけです。インデクサへのアクセス方法がその唯一の重要項目です。

変数 tri には TRICK オブジェクトへの参照が入っています。その参照に続けて [ ] この記号をつけるという、大変面白いアクセス方法です。
ここにおける [ ] は配列への参照を意味しているのではありません。tri は配列ではありません。オブジェクトへの参照です。

後にわかるといった this[ ] のインデクサの定義方法はまさにここにありますです。this は自分自身の実体への参照が入っていますから、上の呼び出し方法にマッチしていることがわかります。

そしてまた、インデクサの get アクセサにアクセスするには、メソッドとして考えた場合引数を1つとります。[ と ] の間の int 型がそれに当たります。(2次元配列などにすると変わってきます。1次元以外ももちろんできます)
set アクセサでは get アクセサでのインデックスのほかに右辺の値も引数として考えられます。これは value としてインデクサに渡されます。

メソッドとして考えると、インデクサは必ずしも配列とセットで扱う必要はありませんが、セットで扱うべきです。混乱してしまいますから。
これも前に述べましたが、サンプルを見て実感していただければ結構です。

最後に、ここではあえて配列の範囲を超えさせています。0から2までなのに、0から4まで呼び出しています。
よって、2つはエラーメッセージが表示されます。試してみてください。これは途中で「生瀬勝久」を「野際陽子」に変えても同じです。復習ですが、変えているのは設定ですから set アクセサですね。

配列の範囲内かどうかチェックするプログラムは他にもいろいろできるでしょう。TF() という専用メソッドを使わなくてもできますが、ここでは「ある一定の働き」を持っていますので、メソッドとして独立させています。
ちなみに TF は True or False の略です。これはプログラムには関係ありませんが、判別ということでわかりやすい名前がいいでしょう。

また、TF() のような判別メソッドはインデクサのあるクラスに定義すべきです。ここでは TRICK にすべきですが、Test にするとオブジェクト指向からずれることになります。

インデクサとはこれだけです。プロパティを先に学んでいますから、理解も早いと思います。

practiceとtheory

あなたはようやく着火剤としての講義を習得し終わりました。
今後はこれを踏み台にして、さらなる知識の習得に励んでください。

などということは言いません。入門講座では言いました。
ところがここは応用講義の終結であり、入門講座の終結ではありません。

私自身の実感としてはもはや知識の習得は十分です。これ以上知識を詰め込むことは無理です。
ここまで根気強く読み続け、理解を深めてきたあなたはもはや立派なプログラマです。

立派なプログラマといっても、最初に文字列の表示ができるようになった後でよく言う
「あなたはもはや立派なプログラマです。がんばっていきましょう。」
とはわけが違います。あなたは質の高い、十分な知識を持ったプログラマであり、ステップアップC# のターゲットである日曜プログラマであれば最高峰です。お世辞ではありません。

事実、多くの書籍においてここまで習得できるようになっているものを見たことがありません。
入門講座でソースから Windows アプリケーションを作れるようになり、応用講義で徹底的にオブジェクト指向とその機能を習得する。

ただ、私が言いたいのは「ここまで読んだあなたは完璧だ」ということではありません。むしろ「これからだ」ということです。
それもこれからはじめるのは知識の習得ではありません。それはもう終わり。

Practice is one thing and theory is anothor.
練習は1つのことで、理論はもう1つのことだ。
ではなく、
実践と練習は別物。
と捉えてください。

さらなる知識の習得に励む必要はありません。theory の方はもう十分です。
ただ、圧倒的に practice が足りない。これからはもう実践だけです。これは経験といってもいいでしょう。

経験は何かを読んで理解するのではなく、自分でソフトを作ることにより得られます。
どんな講座を読んでも、どんなサンプルを見ても、それは経験ではありません。

で、今後自作ソフトを作る際に壁にぶち当たったら迷わずステップアップC#に戻ってきてください。しかし、ジャンプするのは入門講座でも応用講義でもありません。
リンク集です。

リンク集の中には多くのサンプルを所有しておられるサイトが密集しています。
壁にぶち当たればそれらのサイトに行き、サンプルを見、自分のものにしてください。そのサイトの管理人さんたちはみな、あなたにサンプルを習得されることを望んでいるはずです。

その過程においてサンプルが理解できないことは少ないはずです。知識は身に付いていますから、ステップアップC# のサンプルを読むようにそのサイトのサンプルを読めばいいのです。
理解できなければ、その項目に該当する項目をステップアップC# の中から探してください。それでもない場合やわからない場合は質問をすれば、誰かが必ず答えてくれます。

ステップアップC# はサンプルを公開するサイトではないため、具体的な小技はありません。その代わり、それらの小技を身に付けるための講義が用意されています。
それらをうまく使い分け、経験をどんどん増やすことでコーディングに必要な時間などは明らかに進歩していきます。

自作ソフトに無理に応用講義の内容を組み込む必要はありません。それをすると逆に失敗します。

最後ということでついつい長くなってしまいました。要するに
これからはどんどんソフトを作っていこう
ということと、
これからはリンク集のサイトのサンプル集を頼りにしよう
ということです。私が言いたかったのは。

とはいっても、トリックのサンプルが懐かしくなったらいつでも見に来てください。
あせってサンプルを理解せずにただコピーして使うくらいなら、のんびりトリックのサンプルを眺めていた方がましです。

常にのんびりゆっくり。時間をかけて経験値を増やしていってください。では最後に一言。私の好きな言葉です。
Make haste slowly.

[ ステップアップC# ] [ C#プログラミング応用講義 ]