プロパティ(property:所有物、特性)とは、JavaやC++にはない(Visual Basicにはある)機能で、 クラス外部から見るとメンバー変数のように振る舞い、 クラス内部から見るとメソッドのように振舞うものです。
JavaやC++がこの機能を持ってないことからも分かると思いますが、 プロパティはオブジェクト志向言語に必須の機能ではありません。 しかし、これから説明していくように、あると便利なものです。
「実装の隠蔽」 で、 メンバー変数はクラス外部から直接アクセス出来ないようにして、 オブジェクトの状態の変更はすべてメソッドを通して行うべきだと書きました。 これを忠実に実行すると、クラスを利用する側のコードは以下の例のように少々見栄えの悪いものになってしまいます。
using System; // 「実装の隠蔽」で作った複素数クラス class Complex { // 実装は外部から隠蔽(privateにしておく) private double re; // 実部を記憶しておく private double im; // 虚部を記憶しておく public double Re(){return this.re;} // 実部を取り出す public void Re(double x){this.re = x;} // 実部を書き換え public double Im(){return this.im;} // 虚部を取り出す public void Im(double y){this.im = y;} // 虚部を書き換え public double Abs(){return Math.Sqrt(re*re + im*im);} // 絶対値を取り出す } // クラス利用側 class ConcealSample { static void Main() { // x = 5 + 1i Complex x = new Complex(); x.Re(5); // x.re = 5 x.Im(1); // x.im = 1 // y = -2 + 3i Complex y = new Complex(); y.Re(-2); // y.re = -2 y.Im( 3); // y.im = 3 Complex z = new Complex(); z.Re(x.Re() + y.Re()); // z.re = x.re + y.re z.Im(x.Im() + y.Im()); // z.im = x.im + y.im Console.Write("|{0} + {1}i| = {2}\n", z.Re(), z.Im(), z.Abs()); // |3 + 4i| = 5 と表示される } }
void Re(double x)、double Re()などの、
メンバー変数の値の取得・変更を行うためのメソッドのことをアクセサー(accessor)といいます。
C++やJavaなどの言語では、下手をするとメンバー変数の数だけアクセサーが存在するという状態になることもあります。
C++やJavaではアクセサーのメソッド名はvoid SetRe(double x)、double GetRe()というように、メンバー変数名に Set/Get をつけた物を使うことが多く、メンバ変数の数だけ Set/Get で始まるメソッドのペアができ、ちょっと見苦しいものになります。
(参考:
「Set / Get とプロパティ」
)
また、クラス作成側からすると、オブジェクトの状態の取得・変更はすべてメソッドを通して行ったほうがいいのですが、 クラス利用側からすると、メンバー変数に値を直接代入するほうが見た目がすっきりします。
このような理由から、 C#では クラス内部から見るとメソッドのように振る舞い、 クラス利用側から見るとメンバー変数のように振舞う プロパティという機能を用意しました。 プロパティの定義の仕方は以下のような書式になります。
アクセスレベル 型名 プロパティ名 { set { // setアクセサー(setter とも言う) // ここに値の変更時の処理を書く。 // value という名前の変数に代入された値が格納される。 } get { // getアクセサー (getter とも言う) // ここに値の取得時の処理を書く。 // メソッドの場合と同様に、値はreturnキーワードを用いて返す。 } }
例えば先ほどの複素数クラスのアクセサーをプロパティを使って書き換えると以下のようになります。
using System; // クラス定義 class Complex { // 実装は外部から隠蔽(privateにしておく) private double re; // 実部を記憶しておく private double im; // 虚部を記憶しておく // 実部の取得・変更用のプロパティ public double Re { set{this.re = value;} get{return this.re;} } /* ↑のコードは意味的には以下のコードと同じ。 public void SetRe(double value){this.re = value;} public double GetRe(){return this.re;} メソッドと同じ感覚で使える。 */ // 実部の取得・変更用のプロパティ public double Im { set{this.im = value;} get{return this.im;} } // 絶対値の取得用のプロパティ public double Abs { // 読み取り専用プロパティ。 // setブロックを書かない。 get{return Math.Sqrt(re*re + im*im);} } } // クラス利用側 class PropertySample { static void Main() { Complex c = new Complex(); c.Re = 4; // Reプロパティのsetアクセサーが呼び出される。 c.Im = 3; // Imプロパティのsetアクセサーが呼び出される。 Console.Write("|{0} + ", c.Re); // Reプロパティのgetアクセサーが呼び出される。 Console.Write("{0}i| =", c.Im); // Imプロパティのgetアクセサーが呼び出される。 Console.Write(" {0}\n", c.Abs); // Absプロパティのgetアクセサーが呼び出される。 } }
「実装の隠蔽」 のときと同様に、 このコードの実装方法を 「実部と虚部をメンバー変数に記憶しておく」方法から 「絶対値と偏角をメンバー変数に記憶しておく」方法に変更しても、 以下のように、クラス利用側のコードに手を加える必要は一切ありません。
using System; // クラス定義 class Complex { // 実装は外部から隠蔽(privateにしておく) private double abs; // 絶対値を記憶しておく private double arg; // 偏角を記憶しておく // 実部の取得・変更用のプロパティ public double Re { set { double im = this.abs * Math.Sin(this.arg); this.abs = Math.Sqrt(value*value + im*im); this.arg = Math.Atan2(im, value); } get { return this.abs * Math.Cos(this.arg); } } // 実部の取得・変更用のプロパティ public double Im { set { double re = this.abs * Math.Cos(this.arg); this.abs = Math.Sqrt(value*value + re*re); this.arg = Math.Atan2(value, re); } get { return this.abs * Math.Sin(this.arg); } } // 絶対値の取得用のプロパティ public double Abs { get{return this.abs;} } } // クラス利用側 class PropertySample { static void Main() { Complex c = new Complex(); c.Re = 4; // クラス利用側は一切変更せず c.Im = 3; Console.Write("|{0} + ", c.Re); Console.Write("{0}i| =", c.Im); Console.Write(" {0}\n", c.Abs); } }
Ver. 2.0
C# 2.0 の新機能で、 プロパティの set/get アクセサーそれぞれ異なるアクセスレベルを設定できるようになりました。
class A { private int n; public int N { get{ return this.n; } protected set{ this.n = value; } } }
Ver. 3.0
C# 3.0 では、プロパティの get/set の中身の省略もできるようになりました。 この機能を自動プロパティ(auto-property, auto-implemented property)といいます。
例えば、
public string Name { get; set; }
というように、
get; set; とだけ書いておくと、
private string __name; public string Name { get { return this.__name; } set { this.__name = value; } }
というようなコードに相当するものが自動的に生成されます。
(__name という変数名はプログラマが参照できるものではありません。)
C# プログラミングでは、 この手のコード(メンバー変数 name をプロパティ Name で覆う)は定型文的によく使います。 また、クラス内からであっても、private のメンバー変数には直接アクセスせず、 プロパティを通してアクセスする方が後々の保守がしやすかったりします。 ということで、自動プロパティのような省略記法が導入されました。
ちなみに、元々 C# 2.0 以前でも、 「プロパティのデリゲート版」にあたるイベントでは自動プロパティを同じような省略が可能でした。 (デリゲート、イベントについては後述。 参考: 「デリゲート」 、 「イベント」 。) その省略機能がプロパティにも実装されたということになります。
クラスの問題 1の Point 構造体および Triangle クラスの各メンバー変数に対して、
プロパティを使って実装の隠蔽を行え。
VB にはある「インデックス付きプロパティ」は、C# にはありません。 C# の流儀的には、「インデックス付きプロパティ」よりも、「コレクションクラスを返す普通のプロパティ」推奨です。 (その方が、foreach が使えたり、色々便利だから。)
int[] x; // ↓これは文法違反。 public int X[int i] { get { return x[i]; } private set { x[i] = value; } }
int[] x; // ↓これなら OK。 public int[] X { get { return x; } }
C# 2.0 や C# 3.0 を見こすなら、以下のように、配列や ICollection ではなく、IEnumerable を返すようにする方がいいかもしれません。 (詳細は 「イテレータ」 参照。)
int[] x; public IEnumerable<int> X { get { foreach (var item in x) yield return item; } }