クラス(概要編)
注意:
この文書は以前「.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歳です。
|
この例を見ていただければわかるとおり、プロパティ、メソッド、コンストラクタの要素は構造体のものと代わりありません。 ただ、コンストラクタについては引数を取らないコンストラクタを作成できることがクラスでの特徴になっています。 また、この例のようにコンストラクタもオーバーロードすることができます。