Java プログラマのための C# 入門 (#4 クラス定義の応用的なことと演算子のオーバーロード)

#3 クラス定義の基本とメソッド」 に引き続き、今回は C# のクラス定義の応用的なことを書いていきます。 C# のクラスには、Java にはないプロパティやインデクサというものを定義できます。 また、演算子オーバーロードすることもできます。

参考にしている書籍は 『独習 C#』 です。

インデクサとプロパティ

一般に公開するクラスのフィールドは private にし、フィールドの値の参照や代入は setter/getter メソッドを通して行うようにすべし、というのは Java でも言われることです。 C# では、この setter/getter メソッドに相当するものをプロパティとして定義できます。 プロパティは、呼び出し側ではフィールドへの値の代入や参照のように扱うことができ、呼び出され側ではメソッドのように処理を行うことができるものです。 プロパティはクラスの定義の中で、以下のように記述することで定義できます。

// プロパティの定義 (型はプロパティの型で, 名前はプロパティの名前)
型 名前 {
    // get アクセサの定義 (参照時に実行される)
    get {
        /* ... */
    }
    // set アクセサの定義 (代入時に実行される)
    set {
        /* ... */
    }
}

プロパティには、private や public などのアクセス修飾子をつけることができます。 また、get や set のアクセサのアクセス可能範囲は、通常はプロパティのそれと同一になります。 しかし、アクセサに対してアクセス修飾子をつけ、アクセス可能範囲を変更することもできます *1。 例えば、get は public で set を private にすることもできます。 これは、外から見ると読み取り専用のプロパティになります。

また、そもそも set アクセサのみが必要な場合や get アクセサのみが必要な場合は、それだけを定義するということもできます。 (set アクセサのみ定義して、get アクセサは定義しない、など。) なお、プロパティへの代入により渡された値は、set アクセサの中では value として扱うことができます。

class Test {
    // プロパティ PropTest のための記憶領域
    int PropTestField;
    // プロパティ (0 より小さい値が代入された場合に, 0 にする)
    public int PropTest {
        get {
            return PropTestField;
        }
        set {
            if (value < 0) value = 0;
            PropTestField = value;
        }
    }
}
class App {
    static void Main(string[] args) {
        Test t = new Test();
        // プロパティを使う (インスタンス変数と同じように使える)
        t.PropTest = 20;
        System.Console.WriteLine( t.PropTest ); //=> 20
        // 0 より小さい値を代入すると, プロパティの set アクセサにより 0 になる
        t.PropTest = -20;
        System.Console.WriteLine( t.PropTest ); //=> 0
    }
}
自動実装されるプロパティ

C# 3.0 から、set アクセサと get アクセサの中身を記述せず、自動的に実装されるプロパティを定義できるようになりました。

型 名前 { get; set; }

通常のプロパティとは異なり、getset も記述する必要があります。 プロパティが値を保持するためのフィールドは宣言する必要がなく、自動的に確保されます。 アクセサに対してアクセス修飾子をつけることはできるため、set のみを private にして、外側からは参照のみ可能なプロパティなどを、自動実装されるプロパティとして定義できます。

インデクサ

オブジェクトに対して、[] 演算子を使って値を参照したり代入したりしたいこともあると思います。 そのような場合に使用できるのがインデクサです。

// インデクサの定義this[引数リスト] {
    // get アクセサの定義 (参照時に実行される)
    get {
        /* ... */
    }
    // set アクセサの定義 (代入時に実行される)
    set {
        /* ... */
    }
}

プロパティの場合はプロパティ名を指定していましたが、インデクサの場合は [] 演算子の中身を引数として受け取れるため、引数リストを指定する必要があります。 逆に名前はありません。 get アクセサや set アクセサに関してはプロパティと同様です。

class Test {
    // インデクサのための記憶領域
    int[] A = new int[200];
    // インデクサの定義
    public int this[int idx] {
        get {
            return A[idx];
        }
        set {
            if( 0 <= idx && idx < A.Length ) A[idx] = value;
        }
    }
}
class App {
    static void Main(string[] args) {
        Test t = new Test();
        // インデクサを使う
        t[0] = 200;
        System.Console.WriteLine(t[0]);
    }
}

演算子オーバーロード

C++Ruby を使っている人には馴染みのあるものですが、C# では演算子オーバーロードを行うこともできます。 演算子メソッドは、その演算の対象となるクラスの static メソッドとして定義するもののようです *2

// 演算子のオーバーロード
public static 戻り値型 operator 演算子(引数リスト) {
    /* ... */
}

代入演算子は、複合代入演算子 (+= など) を含め、オーバーロードできません。 他にオーバーロードできない演算子は、以下のとおりです。

&&, (), ., ?, ??, [], ||, =>, ->, as, checked, default, is, new, sizeof, typeof, unchecked

関係演算子オーバーロードするときは、対応する関係演算子のペアの両方を定義しなければなりません。 ペアとなるのは、以下の 3 組です。

  • ==!=
  • <>
  • <=>=

また、オブジェクトが true か false かを評価するために使用される際のメソッドとして、true と false という名前の 2 つの演算子メソッドを定義しておけば、if 文の条件としてこのメソッドが使われるとのことですが、なぜ true と false の両方を定義しなければいけないのかちょっと謎です。

参考文献

*1:プロパティ自体のアクセサよりも制限のゆるいアクセス修飾子は付けることができない

*2:ここらへんの正確な定義は言語仕様を読んでないのでちょっとわかりません。