プロパティ
アクセッサメソッド
標準的なオブジェクト指向デザインに乗っ取って設計するのであれば、オブジェクトは外部のコードが自分自身の状態を無条件に変更させるべきではありません。これまでは、学習の便宜上、フィールドを public 修飾子で公開してきました。しかし、これでは外部のコードが無条件に、制約を無視して値を変更できてしまいます。これは、好ましいことではありません。
そこで、外部のコードからアクセスされることが好ましくないメンバには private キーワードを修飾子に指定して非公開にします。多くの場合、フィールドは private 修飾子を指定することになります。経験の浅い開発者で設計判断が難しい場合、クラスのフィールドには private を設定すると考えて間違いありません。public や private など修飾子の詳細は「アクセス修飾子」で解説します。
class MagicalGirl { private string name; } class Test { public static void Main(string[] args) { MagicalGirl nanoha = new MagicalGirl(); nanoha.name = "高町なのは"; //エラー } }
上の実行結果はコード1をコンパイルした結果です。MagicalGirl クラスの name フィールドは private 修飾子が設定されているため非公開メンバです。Test クラスの Main() メソッド内で name フィールドに文字列を代入しようとしていますが、非公開メンバに外部のクラスからはアクセスできないため、コンパイルエラーとなっています。
外部のコードがオブジェクトの状態を変更するには、必ずメソッドを通して設定するように設計します。オブジェクトの状態を設定または取得するために用いられるメソッドのことをアクセッサ(accessor)と呼びます。表記の揺れで Microsoft 公式の日本語訳ではアクセサーとなっていますが、どちらも同じです。構文上の制約はありませんが、一般的なアクセッサは取得用のメソッドが Get から始まり、設定用のメソッドが Set から始まる名前になります。
class MagicalGirl { private string name; public string GetName() { return name; } public void SetName(string value) { name = value; } } class Test { public static void Main(string[] args) { MagicalGirl nanoha = new MagicalGirl(); nanoha.SetName("高町なのは"); System.Console.WriteLine("Name=" + nanoha.GetName()); } }
コード2の MagicalGirl クラスでは非公開の name フィールドに対して外部から設定または取得するためのアクセッサを用意しています。GetName() メソッドは name フィールドの値を返し、SetName() メソッドはパラメータで受け取った値を name フィールドに設定します。
このときの GetName() メソッドのような取得用のアクセッサのことを getter と呼び、SetName() メソッドのような設定用のアクセッサのことを setter とも呼びます。
これだけでは、フィールドを private 修飾子で非公開に、わざわざ別のメソッドを用意する理由が理解できないかもしれません。重要なのは、オブジェクト自身が状態の変更を管理できることです。データの入出力にコードを介しているため、例えば SetName() メソッドの中で渡された値が正しいものかどうかを調べることができます。値が変更されたとき、合わせて何か別の処理を実行したいということもあるでしょう。
暗黙的なアクセッサの生成
多くのオブジェクト指向型プログラミング言語では、上記のように Set~() と Get~() というメソッドを組み合わせて、オブジェクトが持つ 1 つの状態を表します。しかし、プログラミング言語の構文上では SetName() メソッドと GetName() メソッドに関係性はなく、開発者がドキュメントなどを通じて、それらのメソッドが 1 つの状態を読み書きする対のメソッドであることを理解しなければなりません。
C# 言語を含む .NET Framework では、中間言語レベルでプロパティ(property)という概念を導入しています。プロパティはオブジェクトが提供するプログラム可能な状態であり、コード2のような状態を保存するためのフィールドとフィールドにアクセスするメソッドを 1 つの構文で表現できるのです。プロパティを用いることで、クラスの開発者はシンプルにアクセッサを記述でき、クラスの利用者はアクセッサメソッドを意識することなくフィールドと同じように状態の読み書きが可能になります。
プロパティはフィールドやメソッドと同じようにクラスのメンバです。プロパティを作成するには、以下のようなプロパティ宣言(property declaration)をクラス内に記述します。
プロパティ修飾子 型 プロパティ名 { アクセッサ宣言 }
プロパティ修飾子にはアクセス修飾子など、このプロパティの性質を設定する修飾子を指定します。この場では public を指定します。続いて、このプロパティが読み書きする値の型を指定します。プロパティの型とはコード2における SetName() メソッドのパラメータ型、GetName() メソッドの戻り値型に対応するものです。プロパティ名は宣言するプロパティの識別子となるメンバ名です。
プロパティの本体となるものは中括弧 { } の中に記述するアクセッサ宣言(accessor-declaration)です。アクセッサ宣言には、プロパティを読み取る get アクセッサ宣言(get-accessor-declaration)と、プロパティに値を代入する set アクセッサ宣言(set-accessor-declaration)の 2 種類があり、いずれか一方、または両方を記述できます。
アクセッサ修飾子 get { アクセッサ本体 }
アクセッサ修飾子 set { アクセッサ本体 }
アクセッサ修飾子には public や private などのアクセス修飾子を記述してアクセッサごとのアクセス制御を指定します。アクセッサ修飾子は省略可能で、省略された場合、アクセッサの公開範囲はプロパティ宣言を引き継ぎます。例えばプロパティ全体は public で公開し、set アクセッサのみ private で非公開にする(get アクセッサは公開のまま)といった書き方ができます。
get キーワードと set キーワードの後にはアクセッサ本体を指定します。アクセッサ本体はメソッド本体と同じように中括弧 { } からなるブロックです。この中には、やはりメソッドと同じように任意のコードを記述できます。
get アクセッサはプロパティ型の値を return 文で返さなければなりません。この値は、プロパティの呼び出しもとに結果として返されます。
set アクセッサは value という暗黙的なパラメータを持ち、この value パラメータに渡された値が格納されています。通常 value パラメータで受け取った値をフィールドなどに保存し、オブジェクト内部の状態を更新します。
これでプロパティが用意できました。外部のクラスから、オブジェクトが持つプロパティへアクセス方法はフィールドと同じです。プロパティに値が代入された場合、暗黙的に set アクセッサが実行されます。同様に、プロパティが式の項として評価されると get アクセッサが実行され、戻り値が評価結果として用いられます。
class MagicalGirl { private string nameField; public string Name { get { System.Console.WriteLine("Name プロパティを取得"); return nameField; } set { System.Console.WriteLine("Name プロパティを「" + value + "」に設定"); nameField = value; } } } class Test { public static void Main(string[] args) { MagicalGirl nanoha = new MagicalGirl(); nanoha.Name = "高町なのは"; System.Console.WriteLine("Name=" + nanoha.Name); } }
コード3はコード2を改良したプロパティの動作を確認するためのプログラムです。MagicalGirl クラスは Name プロパティを公開しており、このプロパティを通じて外部のコードはオブジェクトの状態を読み書きできます。Name プロパティは get アクセッサで nameField フィールドを返し、set アクセッサで暗黙的な value パラメータで受け取った値を nameField フィールドに代入しています。
実行結果を見れば、プロパティに値を代入すると set アクセッサが実行され、プロパティを読み取ると get アクセッサが実行されていることが確認できます。
読み取り・書き込み専用プロパティ
プロパティの get アクセッサと set アクセッサは、どちらか一方を省略できます。get アクセッサしか持たないプロパティのことを読み取り専用プロパティ(read-only property)と呼び、set アクセッサしか持たないプロパティのことを書き込み専用プロパティ(write-only property)と呼びます。両方のアクセッサを持つプロパティのことを強調する場合は読み取り書き込みプロパティ(read-write property)とも呼びますが、単にプロパティと呼べば get アクセッサと set アクセッサを持つプロパティだと解釈できるでしょう。
一般的な設計で書き込み専用プロパティが用いられることは滅多にありませんが、読み取り専用プロパティは頻繁に利用される技法です。例えば、コンストラクタで受け取ったパラメータでオブジェクトを初期化し、それ以降は状態を変更したくないプロパティとして応用できます。中でも、生成後に状態を変化しないオブジェクトのことを不変オブジェクト(immutable object)と呼び、不変オブジェクトを実現するために読み取りプロパティを応用するのは代表的な事例でしょう。
class MagicalGirl { private string nameField; public string Name { get { return nameField; } } public MagicalGirl(string name) { nameField = name; } } class Test { public static void Main(string[] args) { MagicalGirl nanoha = new MagicalGirl("高町なのは"); nanoha.Name = "鹿目まどか"; //エラー System.Console.WriteLine("Name=" + nanoha.Name); } }
コード4の MagicalGirl クラスは、読み取り専用の Name プロパティを宣言しています。このプロパティは get アクセッサしか持たないことに注目してください。Name プロパティの値は、常にコンストラクタのパラメータに渡した値で固定されます。
get アクセッサしか持たないプロパティは読み取り専用プロパティです。Main() メソッドで Name プロパティに文字列を代入しようとしていますが、Name プロパティに対する代入は無効なので実行結果のようにコンパイルエラーとなります。一方、項として読み取ることは問題ありません。