VB.NETではクラスと構造体にプロパティを持たせることができます。 SubやFunctionなどのプロシージャに似た構文で簡単にプロパティを実装することができます。

プロパティは見かけ上はフィールド変数へのアクセスと変わりませんが、実際にはフィールド変数へのアクセスを行うプロパティープロシージャ(メソッド)が呼び出されます。 これにより、通常の代入文と同じ構文を使いつつ、値の設定時には値のチェック処理を行うといったことができるようになります。 また、単にメソッド呼び出しよりも簡易な記述で値の設定・取得を行えるというメリットもあります。

§1 プロパティの概要

まずはプロパティがどのようなものかを理解するためにいくつかの例を挙げて解説します。

§1.1 設定される値のチェック

ここでは次のようなクラスを使ってプロパティの役割や利点についてを見ていきます。

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

このようにプロパティを用いると、パブリックフィールドに対する値の設定・取得と同じ簡便さを維持しつつ、メソッドと同じように値のチェックなどの機能を持たせることができます。

§1.2 計算済みの値の取得

もうひとつ、次のような構造体でプロパティを使った別の例を見てみます。

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をプロパティにして値のチェック処理を追加することもできます。



§2 プロパティの構文

プロパティの値を設定・取得するメソッドに相当するプロシージャはプロパティプロシージャと呼ばれます。 プロパティプロシージャはクラス構造体モジュールで宣言することができます。 プロパティプロシージャの構文は次のようになっています。 VB6以前にもプロパティは存在していましたが、VB.NETにおけるプロパティプロシージャの構文はVB6以前でのものとは多少異なります。

VB.NETでのプロパティプロシージャの構文
Property プロパティ名() AsSet
    プロパティ値 = Value
  End Set
  Get
    Return プロパティ値
  End Get
End Property
VB6以前でのプロパティプロシージャの構文
Property Get プロパティ名() As 型
  プロパティ名 = プロパティ値
End Property

Property Let/Set プロパティ名(val As 型)
  プロパティ値 = val
End Property

プロパティの値を設定する部分はSetブロックSetアクセサあるいはsetterと呼ばれます。 同様に値を取得する部分はGetブロックGetアクセサあるいはgetterと呼ばれます。 この二つをまとめてアクセサと呼ぶこともあります。 Propertyキーワードで記述されるプロパティプロシージャの中にGetブロックおよびSetブロックを記述します。

VB6以前ではProperty GetProperty Let/Setのように取得と設定で別々のプロシージャを記述していましたが、VB.NETでは一つのプロシージャで記述するようになっています。 また、値の設定に関してSetLetの区別は廃止されていて、すべて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という引数名で参照しています。 次のコードはどちらも同じ結果となります。

暗黙の引数Valueを参照して値を設定する場合
Property Width() As Integer
  Set
    _Width = Value
  End Set
  Get
    Return _Width
  End Get
End Property
引数にnewValueという名前を指定して値を設定する場合
Property Width() As Integer
  Set(ByVal newValue As Integer)
    _Width = newValue
  End Set
  Get
    Return _Width
  End Get
End Property

§2.1 読み取り専用・書き込み専用

ここまでの例では読み取りおよび書き込みが可能なプロパティを挙げてきましたが、プロパティを読み取り専用または書き込み専用にすることも可能です。 読み取り専用のプロパティは値の取得のみが可能なプロパティ、書き込み専用のプロパティは値の設定のみが可能なプロパティとなります。 プロパティを読み取り専用とする場合はReadOnly、書き込み専用ではWriteOnlyキーワードを付加します。 また、読み取り専用プロパティではGetブロックのみ、書き込み専用プロパティではSetブロックのみを記述します。

読み取り専用プロパティ
ReadOnly Property プロパティ名() AsGet
    Return プロパティ値
  End Get
End Property
書き込み専用プロパティ
WriteOnly Property プロパティ名() AsSet
    プロパティ値 = Value
  End Set
End Property

読み取り専用は設定を許可しない場合、設定することが意味を持たない値の場合、他のフィールドの値から計算された値を返す場合などに多く使用されます。 一方書き込み専用のプロパティが必要になることは稀です。 書き込み専用のプロパティを用意するより、SetXXX()といったメソッドを用意することの方が多いためです。

読み取り専用のプロパティに値を設定しようとした場合、書き込み専用のプロパティから値を取得しようとした場合はコンパイルエラーとなります。

§2.2 アクセシビリティ

通常のメソッドやフィールドと同様に、プロパティにもアクセス修飾子を指定することができます。 例えば次のようにすればプロパティはPublicのアクセシビリティを持ちます。

Publicのアクセス範囲を持つプロパティ
Public Property Width() As Integer
  Set
    ' ...
  End Set
  Get
    ' ...
  End Get
End Property

さらに、VB8(VB2005)以降ではSetブロックとGetブロックのそれぞれに異なるアクセス修飾子を付けることもできるようになっています。

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のみが指定できます。

アクセス修飾子と公開範囲について詳しくはアクセス修飾子を参照してください。

§2.3 自動実装プロパティ

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

自動実装プロパティは値の取得・設定のみを行うプロパティを数多く公開する必要がある場合に便利な構文です。 一方で、SetGetに異なるアクセス修飾子を指定できない(常に同一のアクセス範囲となる)、読み取り専用・書き込み専用のプロパティは作成できない、といった制限もあります。

自動実装プロパティではプロパティの初期値を持たせることができます。 自動実装プロパティでは次のように初期化子を指定することができます。

自動実装プロパティに初期値を指定する
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

§3 値型のプロパティと参照型のプロパティ

プロパティには任意の型を指定できますが、プロパティの型が値型と参照型の場合で動作が異なり、一見すると予想に反する動作となることがあります。 この点について詳しくは値型と参照型 §.値型のプロパティ・インデクサで解説しています。 ジェネリックコレクション(1) List §.構造体要素の変更ジェネリックコレクション(2) Dictionary §.値に対する変更などもあわせてご覧ください。