クラス(概要編)

注意:
この文書は以前「.NETでいきまっしょい!」で公開していたものですが、公開以降メンテナンスされていません。 今や古い情報となった内容が記載されている場合があるのでご注意ください。

クラスとは

 クラスとは、簡単に言うと構造体の様に任意数のデータ型を組み合わせた複合データ型で、それに加えてそれらのデータに対して処理を行う手続き(メソッド)を加えたもの、それがクラスです。 クラス(Class)は「学級・教室」などの意味ではなく、「階級・分類」の方の意味です。 VB.NETにおけるクラスの機能は構造体とほとんど変わりありませんが、その違いをまとめると次の通りです。
・クラスは参照型であり、構造体は値型である
・クラスは継承を行うことができるが、構造体はできない
・クラスはファイナライザ(デストラクタ)を実装できるが、構造体はできない
 このうち、もっとも重要なのが継承をサポートしているのがクラスだけである点です。 これらのことについてこれから順に説明していきます。

クラスの宣言方法

 VB6以前とは異なり、クラスは一つのclsファイルに記述するのではなく、Classキーワードを使って宣言します。 つまり、一つのファイルに任意個のクラスを宣言することができます。 宣言の例は次の通りです。
001
002
003
004
005
006
Class Person

    Public Age As Integer
    Public Name As String

End Class
 クラスの宣言はClassキーワードを用いて宣言し、End Classまでがクラス宣言になります。 その間でフィールド、プロパティ、メソッドなどを宣言します。

インスタンス

 クラスは構造体とは違ってクラス型変数を宣言しただけでは使用できません。 宣言した変数にインスタンスへの参照を代入することで初めて使える様になります。 インスタンスとは、実体化されたクラスのデータのことで、インスタンスを作成することを「クラスをインスタンス化する」ともいいます。 クラスとは設計図にすぎず、インスタンスとはその設計図を元に作られた製品であるとイメージすればわかりやすいかもしれません。 クラスとクラス型変数、インスタンスの関係を次のコードを使って説明します。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
Class Person

    Public Age As Integer
    Public Name As String

End Class

Module MainModule

    Sub Main()

        Dim suzuki As Person

        suzuki.Age = 20
        suzuki.Name = "鈴木"

    End Sub

End Module
 このコードの概要を説明すると、まずAgeとNameというフィールドを持ったPersonクラスが宣言されています。 そして、このクラス型の変数suzukiをMain()プロシージャで宣言しています。 さらに、宣言した変数suzukiに対して、AgeとNameフィールドに値を割り当てています。 このコードは一見動作するように見えますが、実際にはAgeフィールドに値を設定しようとした時点で次のような例外エラーが発生します。
例外エラー
ハンドルされていない例外 : System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
   at SampleConsoleApplication.MainModule.Main() in E:\Visual Basic .NET Program\SampleConsoleApplication\Module1.vb:line 14
 つまり、この変数にはインスタンスへの参照が設定されていないために、Ageフィールドを代入すべきインスタンスが見つからず、エラーになったわけです(実際にはこの変数にNothingが代入されている)。 このように、クラスは必ずインスタンス化して使用しなければなりません。 次のコードは実際に動作します。
001
002
003
004
005
006
007
008
009
010
011
Sub Main()

    Dim suzuki As Person

    ' 新たにPersonクラスのインスタンスを作成し、suzukiへ代入
    suzuki = New Person()

    suzuki.Age = 20
    suzuki.Name = "鈴木"

End Sub
 また、VB.NETでインスタンスを作成するには次のような形式も使用できます。
001
002
003
004
005
' 変数の宣言とインスタンスの作成を同時に行う
Dim suzuki As Person = New Person()

suzuki.Age = 20
suzuki.Name = "鈴木"
 VB.NETではこれをさらに省略した次の様な構文もサポートされています。
001
002
003
004
005
' 変数の宣言とインスタンスの作成を同時に行う
Dim suzuki As New Person()

suzuki.Age = 20
suzuki.Name = "鈴木"
 実際にインスタンスに値が代入されているかを確認してみます。
001
002
003
004
005
006
Dim suzuki As Person = New Person()

suzuki.Age = 20
suzuki.Name = "鈴木"

Console.WriteLine("名前:{0} 年齢:{1}", suzuki.Name, suzuki.Age)
出力結果
名前:鈴木 年齢:20

参照型と値型

 クラスと構造体の違いの一つに、値型であるか参照型であるかという違いがあります。 クラスは参照型で、構造体は値型です。 値型では常にインスタンスを持ち、その値に対して直接アクセスするのに対して、参照型では参照を通してインスタンスにアクセスします。 値型の変数に対して代入を行うと、その値がコピーされるのに対して、参照型の変数に対して代入を行うと、参照のみが代入され、実際のインスタンスはコピーされません。 この違いを明確にするコードを次に記述します。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
' 構造体(値型)
Structure ValPerson

    Public Name As String

End Structure

Module MainModule

    Sub Main()

        ' 値型
        Dim valPerson1 As ValPerson
        Dim valPerson2 As ValPerson

        valPerson1.Name = "鈴木"
        valPerson2.Name = "佐藤"

        ' それぞれのNameフィールドの値を表示
        Console.WriteLine("{0} {1}", valPerson1.Name, valPerson2.Name)

        ' valPerson1のコピーがvalPerson2に代入される
        valPerson2 = valPerson1

        ' valPerson1.Nameの値を変更
        valPerson1.Name = "山田"

        ' それぞれのNameフィールドの値を表示
        Console.WriteLine("{0} {1}", valPerson1.Name, valPerson2.Name)

    End Sub

End Module
出力結果
鈴木 佐藤
山田 鈴木
 このコードの流れを確認すると、まずvalPerson1とvalPerson2のNameフィールドにそれぞれ値を代入します。 この状態での実行結果は問題ないと思います。 つぎに、valPerson2にvalPerson1を代入します。 このとき、valPerson1の値のコピーがvalPerson2に代入されます。 その後valPerson1.Nameの値を新しい値にし、その時の出力結果が二行目の出力結果です。 このように、値型の代入では変数のコピーが作成され代入されます。 続いて、これと似たもので参照型の場合を見てみましょう。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
' クラス(参照型)
Class RefPerson

    Public Name As String

End Class

Module MainModule

    Sub Main()

        ' 参照型
        Dim refPerson1 As New RefPerson()
        Dim refPerson2 As New RefPerson()

        refPerson1.Name = "鈴木"
        refPerson2.Name = "佐藤"

        ' それぞれのNameフィールドの値を表示
        Console.WriteLine("{0} {1}", refPerson1.Name, refPerson2.Name)

        ' refPerson1が参照しているインスタンスへの参照がrefPerson2に代入される
        refPerson2 = refPerson1

        ' refPerson1.Nameの値を変更
        refPerson1.Name = "山田"

        ' それぞれのNameフィールドの値を表示
        Console.WriteLine("{0} {1}", refPerson1.Name, refPerson2.Name)

    End Sub

End Module
出力結果
鈴木 佐藤
山田 山田
 こちらも一行目の出力結果までは問題ないと思います。 その後、refPerson2にrefPerson1を代入すると、refPerson2にはrefPerson1が参照しているインスタンスへの参照が代入されます。 この時点でrefPerson1とrefPerson2は同時に同じインスタンスを参照していることになります。 その次の行でrefPerson1.Nameの値を変更していますが、これは「refPerson1が参照しているインスタンスのNameフィールドの値を変更する」ことになります。
 この状況でそれぞれのNameフィールドの値を表示してみると、いずれも同じインスタンスを参照しているので、それぞれ同じ値が表示されるわけです。 このように、値型と参照型では明確な動作の違いがあるので、良く理解しておく必要があります。

構造体との共通点

 クラスと構造体では根本的な違いはあるものの、いくつかの点で共通する部分があります。 メソッド、プロパティ、コンストラクタなどの要素は構造体と変わりません。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
Class Person

    ' 内部的に名前を保持するためのフィールドです
    Private m_Name As String

    ' 内部的に年齢を保持するためのフィールドです
    Private m_Age As Integer

    ' クラスでは引数を取らないコンストラクタを指定することもできます
    Sub New()

        m_Name = "(名無し)"
        m_Age = 0

    End Sub

    ' 引数を取るコンストラクタは構造体と同じです
    Sub New(ByVal name As String, ByVal age As Integer)

        m_Name = name
        m_Age = age

    End Sub

    ' プロパティの形式は構造体のものと同じです
    Property Name() As String
        Get
            Return m_Name
        End Get

        Set(ByVal Value As String)
            m_Name = Value
        End Set
    End Property

    Property Age() As Integer
        Get
            Return m_Age
        End Get

        Set(ByVal Value As Integer)
            m_Age = Value
        End Set
    End Property

    ' メソッドの形式も構造体と同じです
    Sub Introduce()

        Console.WriteLine("私は{0}, {1}歳です。", m_Name, m_Age)

    End Sub

End Class


Module MainModule

    Sub Main()

        ' コンストラクタにパラメータを指定しないでインスタンスを作成します
        Dim tanaka As New Person()

        ' プロパティを通して値を指定します
        tanaka.Name = "田中"
        tanaka.Age = 23

        ' まずPerson型の変数だけ作成します
        Dim saitou As Person

        ' コンストラクタにパラメータを指定してインスタンスを作成します
        saitou = New Person("斉藤", 21)

        ' コンストラクタに何も指定しないでインスタンスを作成します
        Dim nanashi As New Person()


        ' 同じメソッドを呼び出すことでクラスの値を表示します
        tanaka.Introduce()
        saitou.Introduce()
        nanashi.Introduce()

    End Sub

End Module
出力結果
私は田中, 23歳です。
私は斉藤, 21歳です。
私は(名無し), 0歳です。
 この例を見ていただければわかるとおり、プロパティ、メソッド、コンストラクタの要素は構造体のものと代わりありません。 ただ、コンストラクタについては引数を取らないコンストラクタを作成できることがクラスでの特徴になっています。 また、この例のようにコンストラクタもオーバーロードすることができます。