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