プロパティ (プログラミング)

プロパティ （英: property） はオブジェクト指向プログラミング言語において、フィールドの操作と同じ構文でアクセッサーを定義するための言語機能および構文の一種である。 オブジェクト指向プログラミングにおいてオブジェクトの操作を共通化することは極めて重要であり核である。クラスが異なるオブジェクトの間で操作を共通化しようとする際、特定のオブジェクトにしか存在しないフィールドを直接操作する処理が存在すると共通化の邪魔になってしまう。このためオブジェクト指向プログラミングではアクセッサーと呼ばれるメソッド経由でフィールドを操作するというのが定石となっている。しかし、当面の間同じフィールドしか操作しない状態でアクセッサーを定義するのは手間である。また、メソッドの呼び出し方がフィールドと同じであればわざわざ必要も無いときにアクセッサーを用意しなくてもよい。そこでObject Pascal (Delphi)で導入されたのがプロパティである。 Object Pascalを例として以下にプロパティの構文例を示す。プロパティは下記の使用例のValue1の様にクラス外からは公開したフィールドFValue2とまったく同じ様に操作することができる。しかし、プロパティの定義では下記の例のように実体をメソッドの呼び出しとして実装したり、読み書きに制限を設けたり、フィールドの値を操作することには変わりないものの派生型で再定義可能にしたり別名にしたりと柔軟性をもたせることができる。 type TValueHolder = class private FValue1: Integer; protected function GetValue: Integer; procedure SetValue( aValue: Integer ); public FValue2: Integer; published // プロパティの定義例 // メソッドとして実装したプロパティ // Value1に対する操作がGetValueとSetValueの操作となる property Value1: Integer read GetValue write SetValue; // 読み取り専用にしたプロパティ property Value2: Integer read FValue1; // 書き込み専用にしたプロパティ property Value3: Integer write FValue1; // フィールドの読み書きに特化したプロパティ // (派生型で再定義しなければほぼフィールドと同じ) property Value4: Integer read FValue1 write FValue1; end; function TValueHolder.GetValue: Integer; begin Result := FValue1; end; procedure TValueHolder.SetValue( aValue: Integer ); begin FValue1 := aValue. end; var Example: TValueHolder; ResultValue: Integer; begin Example := TValueHolder.Create; // プロパティの使用例 Example.Value1 := 0; // SetValueの呼び出しとなる ResultValue := Example.Value1; // GetValueの呼び出しとなる // 参考：フィールドの直接操作 Example.FValue2 := 0; ResultValue := Example.FValue2; end; 概要で述べた通りフィールドを直接操作してしまうと下記の例の様にフィールドが存在しないオブジェクトを共通の関数(例ではCount)を再利用することができなくなってしまう。これを防ぐためオブジェクト指向プログラミングではアクセッサーを用いるが、プロパティが存在しない言語では一度公開してしまったフィールドをアクセッサーに変換するのは、フィールドを操作している箇所全てを修正する必要が発生するため容易ではない。このため、将来的に共通化する必要が出てくるか否かに関わらずアクセッサーを定義することが鉄則となっている。 ■フィールド直接操作による問題の具体例(Object Pascal) // フィールドを直接操作する汎用関数の例 function Count<T>( Collection : T ): Integer; begin // 例示用であり現実的に意味のある処理ではない。またfCountも公開されてはいない。 Result := Collection.FCount // フィールドを直接参照 end; // 呼び出し側 // ◯："TArrayにfCount"があるので構文エラーにならない Count( TArray<Integer>.Create ); // ✗："TLinkedListにfCount"がないので構文エラーとなる Count( TLinkedList<Integer>.Create ); // アクセッサー経由で操作する汎用関数の例 function Count<T>( Collection : T ): Integer; begin // 例示用であり現実的に意味のある処理ではない。 Result := Collection.Count // アクセッサー(プロパティ)経由で参照 end; // 呼び出し側 // どちらも構文エラーとならない。 Count( TArray<Integer>.Create ); Count( TLinkedList<Integer>.Create ); 反面プロパティを持つ言語においてはソースコード互換の範囲ではあるがフィールドからアクセッサーへの変更が極めて容易となる。下記の例では、フィールドとして公開していたValueを削除してプロパティであるValueに置換しているが、プロパティがフィールドと互換性を持つためフィールドを操作していた箇所には一切変更が発生しない。これによりプロパティが存在する言語ではとりあえずはフィールドを公開しておき必要なってきたらプロパティに変更していくという運用が可能になっている。 ■プロパティ化(Object Pascal) type // Valueをプロパティ化 TValueHolder = class private // 追加 FValue: Integer; public // 削除 //Value: Integer; published // 追加 property Value: Integer read FValue write FValue; end; var Example: TValueHolder; ResultValue: Integer; begin Example := TValueHolder.Create; // プロパティ化しても一切変更は必要ない Example.Value := 0; ResultValue := Example.Value; end; なお、フィールドに制約を掛けやすい言語ほど運用性が高まる。フィールドをConst等で修飾しオブジェクト外部からは読み取り専用とできるような言語であればフィールドの書き込み操作を気にすることなくフィールドを読み取り専用のプロパティに変更することができる。Objective-Cではより進んでおりプロパティ宣言とフィールド宣言が一体化しており、最も単純なプロパティは派生型で再定義可能かどうかを除いてフィールドと変わらない。 @property float value; Objective-Cと互換性のあるSwiftでは格納方法の指定方法が属性の定義の中(下記であれば@SmallNumberの定義の中)に含まれるようになり、よりフィールドとプロパティの融合が進んでいる。 @SmallNumber var value: Int 言語仕様となっているもの Object Pascal (Delphi) Python Objective-C Swift Ruby .NET系言語 C# Visual Basic .NET 他 Java VM系 Kotlin 処理系依存で対応しているもの C++ ( Microsoft Visual C++など ) C#においては、クラスおよび構造体の内部にプロパティを持つことができる。 明示的にバッキングフィールド (backing field) を記述する基本的なプロパティの例を示す。 プロパティの構文内ではget, setが、またsetアクセサ内ではvalueがコンテキスト（文脈）キーワードとして機能する。 class Person { /// バッキングフィールド private string name; /// nameをカプセル化するプロパティ public string Name { get { return name; } set { if (value == null) { throw new ArgumentNullException(); } name = value; } } } 呼び出し側では、フィールドにアクセスするような構文でプロパティの機能を呼び出す。 var person = new Person(); // set アクセス person.Name = "John"; // get アクセス string userName = person.Name; 値の取得と設定のみを行い、取得設定時に追加の処理を行わない場合、自動実装プロパティ (auto-implemented properties) と呼ばれる簡易な構文を用いることができる。 自動実装プロパティでは、バッキングフィールドが内部的に自動生成される。 プログラマは自動生成されたバッキングフィールドには直接アクセスすることはできない。 class Person { // バッキングフィールドが(不可視ではあるが)自動生成されている public string Job { get; set; } }