クラスとは、簡単に言うと構造体のようにいくつかのデータを持ち、それらのデータに対して処理を行う手続き(メソッドもしくはプロシージャ)も合わせ持つ複合データ型です。 ここで言うクラス(Class)は「学級・教室」などの意味ではなく、「種類・分類・階級」の意味の方を表します。
VB.NETにおけるクラス
VB.NETにおけるクラスは多くの部分で構造体と類似していますが、両者の違いをまとめると次のようになります。
- クラスは参照型であり、構造体は値型である (値型と参照型)
- クラスはファイナライザ(デストラクタ)を実装できるが、構造体はできない (オブジェクトの破棄)
- クラスは継承を行うことができるが、構造体はできない
このような特徴はVB.NETに固有のものではなく、C#など他の.NET Frameworkの言語とも共通するものとなっています。
クラスの宣言
一つのcls
ファイルに一つのクラスを記述していたVB6以前とは異なり、VB.NETではClass
キーワードを使ってクラス宣言します。 宣言の例は次の通りです。
クラスの宣言はClass
キーワードを用いて宣言し、End Class
までがクラス宣言になります。 その間でフィールド(メンバ変数)、プロパティ、メソッドなどを宣言します。 フィールドなどクラスのメンバにはPublic
などのアクセシビリティを指定することができます。
また、構造体やモジュールと同様に、クラスも一つのファイルに任意個のクラスを宣言することができます。 クラスと構造体を一つのファイルでまとめて宣言することもできます。
インスタンス
構造体とは異なり、クラスはクラス型変数を宣言しただけでは使用できません。 インスタンスを作成して宣言した変数に代入することで初めてクラスを使えるようになります。
Classキーワードによって宣言したクラスはただの定義であり、それを実体化したものがインスタンスであり、インスタンスを作成することを「クラスをインスタンス化する」といいます。 クラスとは設計図であり、インスタンスとはその設計図を元に作られた個々の製品であるとイメージすればわかりやすいかもしれません。
クラスとクラス型変数、インスタンスの関係を次のコードを使って説明します。
このコードの概要を説明すると、まずIDとNameという二つのフィールドを持ったAccountクラスが宣言されています。 そして、このクラス型の変数alice
をMainプロシージャで宣言しています。 さらに、宣言した変数alice
に対して、IDフィールドとNameフィールドにそれぞれ値を割り当てています。
このコードは一見動作するように見えるかもしれませんが、実際にはIDフィールドに値を設定しようとした時点で次のような例外エラーが発生します。
このエラーメッセージは、変数alice
にはインスタンスへの参照が設定されていないために、IDフィールドを設定する対象のインスタンスが無いことが原因で発生しています。 このように、具体的なインスタンスを格納していない場合クラス変数には初期値としてNothingが代入されているため、そのような変数を使ってインスタンスに対して操作を行おうとすると例外NullReferenceExceptionがスローされます。
クラスはインスタンス化することで使用できるようになります。 インスタンスの作成にはNew
キーワードを使用します。 先ほどの例とは異なり、次のコードは問題なく動作します。
次のように、変数の宣言およびインスタンスの作成と変数への代入を一行で記述することもできます。
これをさらに簡略化した次のような構文もサポートされています。 どちらも同じ結果となりますが、一般的にはこの形式が多く使われているようです。
インスタンスのフィールドに値が代入されているかを確認すると次のようになります。
構造体との共通点
クラスと構造体は機能的にはさまざまな点が共通しています。 メソッド、プロパティ、コンストラクタなどの要素は構造体の場合とほとんど変わりません。 クラスでは構造体と異なり引数を取らないコンストラクタを作成できるようになっています。
参照型と値型
クラスと構造体の違いの一つに、値型であるか参照型であるかという違いがあります。 クラスは参照型で、構造体は値型です。
値型の変数は常にインスタンスを持ち、その値に対して直接アクセスするのに対して、参照型の変数ではインスタンスへの参照を通してインスタンスにアクセスします。 また、値型の変数に対して代入を行うとその値がコピーされるのに対して、参照型の変数に対して代入を行うと参照のみが代入され、実際のインスタンスはコピーされません。
この違いを明確にするコードを使って解説します。
この二つのコードにはクラスを使っているか構造体を使っているかの違いしかありませんが、実行結果の違い、参照型では代入後のa2
に対する変更がa1
にも影響している点に注目してください。
構造体(値型)では、代入によって代入元変数a1
のコピーが作成され代入先a2
に設定されるため、代入先と代入元はどちらも違うインスタンスとなります。 一方クラス(参照型)では、代入によって代入元変数a1
が参照しているインスタンスへの参照が代入先a2
に設定されるため、代入先と代入元はどちらも同じインスタンスを参照することになります。 このため、クラスの場合では代入によってa1
とa2
には同じインスタンスへの参照が設定されるため、a2
に対する変更はa1
に対して変更することと同じとなり、実行結果にあるようにどちらも同じ値が表示されることになります。
このように、クラスと構造体はどちらも同じような機能を持っていますが、一方で値型と参照型という明確な動作の違いもあります。 どちらを選ぶかによってパフォーマンスが大きくことなることもあるほか、List(Of T)で構造体のフィールド・プロパティを変更する場合のようにがわかりづらい問題が発生することもあるので、安易な判断を行わず両者の違いをよく理解して使い分ける必要があります。
共有メンバ
クラスのフィールド・プロパティ・メソッドなどすべてのメンバはインスタンスを作成することで初めて使用できるようになります。 一方、Shared
キーワードによってメンバを共有メンバにすると、全インスタンスに共通したメンバ(各インスタンスではなく直接クラスに属するメンバ)となり、インスタンスを作成せず使用できるようになります。 クラス外から共有メンバを参照する場合は、クラス名.メンバ名のように記述します。
アプリケーションのエントリポイントであるMain
メソッドをクラス内で宣言する場合もShared
で宣言します。 エントリポイントが呼び出される前にインスタンスを作成することはできないため、インスタンスを作成せずにMain
メソッドを呼び出せるようShared
を付ける必要があります。
インスタンスを作成せずにメソッド呼び出しや変数の参照ができるモジュールは、すべてのメンバがShared
(共有メンバ)となったクラスと見ることもできます。 一方、クラス自体にSharedキーワードを設定することはできないため、VBのクラスではC#の静的クラスに相当するものを作成することはできません。 VBではモジュールを使うことにより静的クラスに相当する機能を得ることができます。
インスタンスの破棄
New
によって作成したインスタンスはガベージコレクタによって管理され、不要と判断された時点で自動的に破棄されるため、基本的にインスタンス自体を明示的に破棄する必要はなく、また解放処理を記述する必要もありません。
しかし、インスタンスがアンマネージリソースなどガベージコレクタによる管理の対象外となるリソースを作成・使用している場合は、それを確実に解放する必要があります。 ガベージコレクタの管理対象外となるリソースを扱うクラスを作成する場合は、IDisposableインターフェイスを実装して明示的にリソースを解放できる手段を用意するしておくことが推奨されます。 また、そのようなクラスのインスタンスを使用する際においてはUsing
ステートメントを使うようにし、インスタンスを使用しおわった時点で確実に解放処理を呼び出させるようにします。
次のようにFinalize
メソッドをオーバーライドすることによりデストラクタを記述することもできますが、このメソッドを明示的に呼び出すことはできません。 デストラクタはガベージコレクタによって呼び出されます。 IDisposable
を実装するクラスでは、確実に1度はDispose
メソッドが呼び出されるようにデストラクタを記述することはありますが、それ以外の多くの場合ではデストラクタを実装する必要はありません。
インターフェイスの実装については後述します。
IDisposableインターフェイスとリソースの解放、デストラクタの適切な実装についてはオブジェクトの破棄で詳しく解説しています。
インスタンスの保存・復元
.NET Frameworkでは、シリアライズによってインスタンスの状態を保存・復元することができます。 シリアライズを行うことにより、インスタンスをバイナリ形式やXML形式でファイルに保存したり、ネットワークを経由して送受信したりすることができます。 シリアライズによるインスタンスの保存・復元では、クラスごとに保存・読み込み処理を記述する必要はなく、また属性を付与することによって保存・復元時の動作を簡単にカスタマイズすることもできます。
シリアライズについてはシリアライズの基本で詳しく解説しています。
継承
VBではクラスの継承を行うことができます。 継承はクラスのみでサポートされる機能で、構造体は継承を行うことはできません。 また、VBでは多重継承を行うことはできません。
クラスの継承
クラスを継承して派生クラスを作成するには、Inherits
キーワードを使用します。 VBでは多重継承はサポートされないため、Inherits
の後ろには常に基底クラスを一つだけ指定します。
Inherits
によって別のクラスを継承しているクラスを基底クラスとして更に継承を重ねることもできます。
オーバーライド
派生クラスで基底クラスのメソッドをオーバーライドする場合は、Overrides
キーワードを使用します。 ただし、オーバーライドする場合はオーバーライドを許可することをあらわすOverridable
キーワードが基底クラスのメソッドに付与されている必要があります。 Overridable
キーワードが付与されているメソッドは仮想メソッドとなります。 Overrides
キーワードが付与されているメソッドは暗黙的にOverridable
となるため、Overrides Overridable
のようにこれらのキーワードを記述する必要はありません。
抽象メソッド・抽象クラス
メソッドを抽象メソッドとする場合は、Overridable
の代わりにMustOverride
キーワードを指定します。 抽象メソッドを宣言する場合はメソッドの実装とEnd Sub
の記述を省略します。 またMustOverride
が付与されたメンバを含むクラスでは、そのクラスが抽象クラスであることを表すMustInherit
キーワードをクラス宣言に付与する必要があります。 抽象クラスの継承と抽象メソッドのオーバーライドは通常のクラス・仮想メソッドの場合と同様です。
ここまでの例では値を返さないメソッド(Sub
プロシージャ)を使ってきましたが、値を返すメソッド(Function
プロシージャ)やプロパティ(Property
)の場合も同様にキーワードを指定することでメンバの仮想化・抽象化・オーバーライドを行うことができます。
Overrides
などのキーワードとともにPublic
やProtected
などのキーワードを組み合わせて指定することにより、メンバのアクセシビリティ(公開範囲)も設定することもできます。 これらのキーワードについては詳しくはアクセス修飾子で解説しています。
継承の禁止
クラスにNotInheritable
キーワードを付与することにより、継承を禁止してシール(sealed, 封印)されたクラスを作成することができます。 NotInheritable
が付与されているクラスを継承しようとした場合はコンパイル時にエラーとなります。
基底クラスのメンバ参照
派生クラス側から基底クラスを参照するにはMyBase
キーワードを使います。 例えば、派生クラスで基底クラスのコンストラクタを呼び出す場合は次の例のようにMyBase.New(引数リスト...)
とします。 コンストラクタで基底クラスのコンストラクタを呼び出す場合は、コンストラクタでの他の処理よりも先に呼び出すようにする必要があります。
コンストラクタだけでなく、メソッド・フィールド・プロパティなどの場合も同様にMyBase
で基底クラスのメンバを参照することができます。 また、基底クラスを参照するMyBase
以外にも、インスタンス自身を表すMe
キーワード、自クラスのメンバを参照するMyClass
キーワードなどを使ってメンバの参照を行うことができます。
Me
, MyClass
, MyBase
の3つのキーワードについてはMe, MyClass, MyBaseで詳しく解説しています。
実行時の型判別
実行時にオブジェクトがあるクラスを継承しているか(型に互換性があるか)どうかを調べるにはTypeOf ... Is
を使用することができます。
TypeOf演算子・Is演算子についてはIs演算子・IsNot演算子・TypeOf演算子およびインスタンスや型が一致するか・インターフェイスやクラスから派生しているか判定するで詳しく解説しています。
インターフェイスの実装
VBではクラスにインターフェイスを実装することができます。 クラスの多重継承が許可されない代わりにインターフェイスは複数実装することができるため、インターフェイスを適切に実装することによってクラスに様々な機能を持たせることができます。
インターフェイスを実装するには、Implements
キーワードを使用してクラスの先頭で実装するインターフェイス名を指定します。 次に、インターフェイスメソッドの実装を記述します。 この際もImplements
キーワードを使用し、メソッド宣言の後ろにImplements インターフェイス名.メソッド名
といった記述を付記します。 このような実装をインターフェイスが持つメソッドの分だけ記述します。
以下の例はクラスにIDisposableインターフェイスを実装する例です。 IDisposableインターフェイスは引数なし・戻り値無しのメソッドDisposeを持っているので、これをクラス内で実装するようにします。
クラスは複数のインターフェイスを実装することができます。 次の例では、二つのインターフェイスI1とI2を実装するクラスを作成しています。 次の例のように、実装しようとするインターフェイスに同名のメソッドが含まれている場合は、実装となるメソッド名を変えることにより同名のインターフェイスメソッドを別々の名前で実装することができます。
.NET Frameworkには様々な役割を持つインターフェイスが用意されています。 代表的なものとして、オブジェクトの破棄を行う機能を提供するIDisposableインターフェイス(オブジェクトの破棄)、コレクションにFor Each
による列挙機能を持たせるIEnumerableインターフェイス(IEnumerable・IEnumerator)、オブジェクト同士の大小関係を比較する機能を提供するIComparableインターフェイス(大小関係の定義と比較)、オブジェクトをシリアライズする際の動作を制御するISerializableインターフェイス(BinaryFormatter・SoapFormatter §.シリアライズ動作の制御 (ISerializable))などがあります。 インターフェイスの具体的な実装例については、個々のドキュメントを参照してください。
入れ子
クラスでは別のクラスを入れ子にして宣言することができます。
入れ子にしたクラスにはアクセシビリティを設定できるため、クラス内でしか入れ子クラスを使用しない場合には入れ子クラスをPrivate
にするといったこともできます。 このようにクラスは型のコンテナとして機能するため、クラスだけでなく構造体・列挙体・モジュール・デリゲート・インターフェイスなどもクラス内に宣言することができます。
宣言の分割 (Partial)
VB2005以降では、Partial
キーワードを使用することによりクラスおよび構造体の宣言を分割することができます。 クラスのコードが長大になる場合や、自動生成部分と手動記述部分を分けて管理したい場合などには、Partial
キーワードを使ってクラス宣言を複数箇所に分割して全体を把握しやすくすることができます。 Partial
キーワードを使ったクラスの宣言は、個別のファイルに記述することも、同一のファイルに記述することもできます。
クラスライブラリ
プロジェクトを実行可能形式(.exe)ではなくライブラリ(.dll)としてビルドすることにより、クラスライブラリを作成することができます。 これにより、作成したクラスを別のプロジェクトでも使用できるようにすることができます。 また、このようにして作成したクラスライブラリはプロジェクトの参照に追加するだけで使用できるようになり、またVBだけでなくC#など.NET Frameworkの他の言語からも使用することができるようになっています。
クラスライブラリの作成方法などについてはクラスライブラリの作成で詳しく解説しています。