VB.NETではクラスと構造体にプロパティを持たせることができます。 SubやFunctionなどのプロシージャに似た構文で簡単にプロパティを実装することができます。
プロパティは見かけ上はフィールド変数へのアクセスと変わりませんが、実際にはフィールド変数へのアクセスを行うプロパティープロシージャ(メソッド)が呼び出されます。 これにより、通常の代入文と同じ構文を使いつつ、値の設定時には値のチェック処理を行うといったことができるようになります。 また、単にメソッド呼び出しよりも簡易な記述で値の設定・取得を行えるというメリットもあります。
プロパティの概要
まずはプロパティがどのようなものかを理解するためにいくつかの例を挙げて解説します。
設定される値のチェック
ここでは次のようなクラスを使ってプロパティの役割や利点についてを見ていきます。
このクラスは二つのパブリックフィールドを持っています。 このクラスのIDフィールドとNameフィールドは任意の値を自由に設定することができます。
ここで、IDフィールドとNameフィールドに設定できる値に制限を加えたい場合を考えます。 具体的には、IDフィールドには0以上の値、NameフィールドはNothing
以外の値のみを許容し、それ以外の値は設定できないようにしたいとします。 また、値の設定時にはその値をチェックして、不正な値を設定しようとした場合には例外をスローすることにします。
このような要求を満たすために、まずは値の設定をメソッドで行うようにし、設定する際にはその値をチェックすることにします。 次に、フィールドをPublic
にしたままにするとフィールドに直接値が設定された場合には値のチェックが行われないため不正な値が設定されてしまう恐れもあるので、外部からは変更できないようにフィールドのアクセス修飾子をPrivate
に変更します。
これでフィールドに不正な値が設定されることはなくなりました。 しかし、値を設定・取得する側ではその度にメソッド呼び出しを行わなければならなくなったため、記述が煩雑になっています。 特に、単に値を取得するにも毎回メソッド呼び出しを行わなければならないのは面倒です。
このような呼び出し側の利便性を考え、プロパティを導入します。 メソッドによって値の設定・取得を行なっていた個所をプロパティに置き換えると次のようになります。 プロパティの構文については追って解説します。
このようにプロパティを用いると、パブリックフィールドに対する値の設定・取得と同じ簡便さを維持しつつ、メソッドと同じように値のチェックなどの機能を持たせることができます。
計算済みの値の取得
もうひとつ、次のような構造体でプロパティを使った別の例を見てみます。
この構造体は長方形の座標を表すものです。 この構造体から長方形の幅と高さを求めるには次のようにする必要があります。
このようにフィールドを直接参照して計算を行う場合、同じ計算式を何度も記述することになるため、面倒です。 計算結果を別の変数に格納して再利用することもできますが、その場合でも座標が変わった場合はその都度計算し直す必要があります。 このような場合にもプロパティを導入することで利便性を高めることができます。
長方形の座標から幅と高さを求めるプロパティを追加すると次のようになります。
ここでは読み取り専用のプロパティ、つまり設定はできず値の取得のみが可能なプロパティを使っています。 このように、プロパティを使うことによってフィールドに設定されている値から計算済みの値を返すようにすることができます。 これは、プロパティによって複数のフィールドの組み合わせに別の意味を与えるという風に見ることもできます。
この例ではフィールドをPublic
のままにしていますが、例えばRight
は常にLeft
より大きい値でなければならないようにするには、Left
およびRight
をプロパティにして値のチェック処理を追加することもできます。
プロパティの構文
プロパティの値を設定・取得するメソッドに相当するプロシージャはプロパティプロシージャと呼ばれます。 プロパティプロシージャはクラス・構造体・モジュールで宣言することができます。 プロパティプロシージャの構文は次のようになっています。 VB6以前にもプロパティは存在していましたが、VB.NETにおけるプロパティプロシージャの構文はVB6以前でのものとは多少異なります。
プロパティの値を設定する部分はSet
ブロック、Set
アクセサあるいはsetterと呼ばれます。 同様に値を取得する部分はGet
ブロック、Get
アクセサあるいはgetterと呼ばれます。 この二つをまとめてアクセサと呼ぶこともあります。 Property
キーワードで記述されるプロパティプロシージャの中にGet
ブロックおよびSet
ブロックを記述します。
VB6以前ではProperty Get
、Property Let/Set
のように取得と設定で別々のプロシージャを記述していましたが、VB.NETでは一つのプロシージャで記述するようになっています。 また、値の設定に関してSet
とLet
の区別は廃止されていて、すべてSet
で扱います。
一般に、プロパティを使う場合にはプロパティの値を保持するための外部に公開されない内部変数を用意します。 具体的には次のようになります。
プロパティの値を保持するフィールドはバッキングフィールドと呼ばれます。 バッキングフィールド名にはプロパティ名と異なる名前を付ける必要がありますが、一般的にはプロパティ名にアンダースコア_
を前置した名前を使用することが多いようです。 例えばプロパティ名がWidth
であれば、そのプロパティに対応するバッキングフィールド名を_width
または_Width
とします。 他にも、m_
を前置してm_Width
あるいはm_width
とする場合も多いようです。 このmはmemberのmで、クラス・構造体のグローバルではないメンバ変数であることを表すものです。 (ハンガリアン記法)
Set
ブロックでは、プロパティに設定される値を暗黙の引数Value
で参照できます。 Set
ブロックでは引数を明示することでこの引数に別の名前を指定することもできます。 例えば、Value
という名前が競合する場合などには別の名前を付けて参照するといったことができます。 次の例ではプロパティに設定される値をnewValue
という引数名で参照しています。 次のコードはどちらも同じ結果となります。
読み取り専用・書き込み専用
ここまでの例では読み取りおよび書き込みが可能なプロパティを挙げてきましたが、プロパティを読み取り専用または書き込み専用にすることも可能です。 読み取り専用のプロパティは値の取得のみが可能なプロパティ、書き込み専用のプロパティは値の設定のみが可能なプロパティとなります。 プロパティを読み取り専用とする場合はReadOnly
、書き込み専用ではWriteOnly
キーワードを付加します。 また、読み取り専用プロパティではGet
ブロックのみ、書き込み専用プロパティではSet
ブロックのみを記述します。
読み取り専用は設定を許可しない場合、設定することが意味を持たない値の場合、他のフィールドの値から計算された値を返す場合などに多く使用されます。 一方書き込み専用のプロパティが必要になることは稀です。 書き込み専用のプロパティを用意するより、SetXXX()
といったメソッドを用意することの方が多いためです。
読み取り専用のプロパティに値を設定しようとした場合、書き込み専用のプロパティから値を取得しようとした場合はコンパイルエラーとなります。
アクセシビリティ
通常のメソッドやフィールドと同様に、プロパティにもアクセス修飾子を指定することができます。 例えば次のようにすればプロパティはPublic
のアクセシビリティを持ちます。
さらに、VB8(VB2005)以降ではSet
ブロックとGet
ブロックのそれぞれに異なるアクセス修飾子を付けることもできるようになっています。
このように異なるアクセス修飾子を設定することにより、クラス内または派生クラスからは値を設定できるが、クラス外からは取得しかできない、といったプロパティを作成することができます。
なお、Set
ブロックとGet
ブロックの両方にアクセス修飾子を指定することはできません。 Set
ブロックまたはGet
ブロックのどちらか一方のみにプロパティプロシージャと異なるアクセス修飾子を指定できます。 また、プロパティプロシージャ自体のアクセス修飾子より公開範囲の広いアクセス修飾子を指定することはできません。 たとえばプロパティプロシージャにProtected
が指定されている場合は、アクセサブロックにはProtected
より公開範囲の狭いPrivate
のみが指定できます。
アクセス修飾子と公開範囲について詳しくはアクセス修飾子を参照してください。
自動実装プロパティ
VB9(VB2010)からは自動実装プロパティがサポートされています。 これはSet
ブロックとGet
ブロックが単に値の設定と返却を行うだけのプロパティを作成するための省略記法です。 例えば次のようなプロパティがあった場合、これを自動実装プロパティを使うと次のように記述することができます。
自動実装プロパティは値の取得・設定のみを行うプロパティを数多く公開する必要がある場合に便利な構文です。 一方で、Set
とGet
に異なるアクセス修飾子を指定できない(常に同一のアクセス範囲となる)、読み取り専用・書き込み専用のプロパティは作成できない、といった制限もあります。
自動実装プロパティではプロパティの初期値を持たせることができます。 自動実装プロパティでは次のように初期化子を指定することができます。
自動実装プロパティでは、プロパティ名にアンダースコア_
を前置した名前のバッキングフィールドが自動的に作成されます。 例えば、ID
というプロパティを作成した場合は、その値を保持するためのフィールド_ID
が自動的に作成されます。
そのため、自動実装プロパティが作成するバッキングフィールドと同名のフィールドが存在している場合はコンパイル時にエラーとなるため注意が必要です。
値型のプロパティと参照型のプロパティ
プロパティには任意の型を指定できますが、プロパティの型が値型と参照型の場合で動作が異なり、一見すると予想に反する動作となることがあります。 この点について詳しくは値型と参照型 §.値型のプロパティ・インデクサで解説しています。 ジェネリックコレクション(1) List §.構造体要素の変更、ジェネリックコレクション(2) Dictionary §.値に対する変更などもあわせてご覧ください。