構造体とは既存のデータ型を組み合わせた構造を持つ型のことです。 構造体では複数の値(データ型)を組み合わせることにより、それらに意味を持たせた一つの型を作成することができます。 VB6以前でユーザー定義型と呼ばれていたものは、VB.NETでは構造体という名称に変わり、クラスと同様に構造体がプロシージャ(メソッド)を持つことができるようになっているなど、機能も大幅に強化されています。
構造体の宣言
まずは構造体の宣言について、その構文を見てみます。 ここでは長方形の座標を扱うRECT構造体に相当する構造体を例に挙げています。 比較のため、同等の型をVB6のユーザー定義型、C言語の構造体として宣言した場合についても併記しています。
(上記のコードについて、VB6以前ではLong
が32ビット、VB.NETではInteger
が32ビットの整数を表すという違いがあります。)
VB.NETではType
〜End Type
ではなく、Structure
〜End Structure
を用いて構造体を宣言します。 また、VB.NETの構造体ではフィールド(構造体内の変数・メンバ変数)やプロシージャ(メソッド)を含むすべてのメンバにアクセシビリティを設定できるようになっています。 そのためメンバはDim
またはPublic
やPrivate
などのアクセス修飾子を用いて宣言します。 なお、Dim
で宣言した構造体のメンバはPublic
と同じアクセス範囲となります。 そのため、上記の例にあるフィールド(構造体内の変数)はすべてPublic
となります。
このように宣言した構造体を実際に使う場合は次のようになります。 構造体変数.メンバ名
の形式で構造体内の各メンバにアクセスできるようになっています。 このように、使用する場合の構文についてはVB6と概ね変わりありません。
With
ステートメントを使用することにより、メンバの参照時に構造体変数の記述を省略することもできます。
構造体を宣言する際の命名基準について、VB.NETでは構造体の場合もクラスと同様の命名方法、つまりすべて大文字からなる名前は付けないようにガイドラインで定められています。 (例えば、RECT
よりはRect
が推奨される) ただ、あくまで指針であるので、わかりやすさが維持される限りは自由に名前を付けることができます。
ちなみに、長方形の座標を扱う構造体はSystem.Drawing名前空間にRectangle構造体として既に.NET Frameworkに用意されています。 そのため、Rectangle構造体が使用できるのであれば上記のような構造体をわざわざ独自に宣言する必要はありません。
メソッド
VB.NETでの構造体の仕様はVB6のユーザー定義型よりもC++の構造体に近くなっていて、クラスに近い機能を持っています。 その一つとして、VB.NETの構造体はメソッドを持つことができるようになっています。
例えば次のサンプルでは、先ほどのRect構造体に対してメンバの値を設定するためのメソッドを追加しています。
このように、VB.NETでは構造体にメソッドを持たせることができます。 メソッド以外にも、コンストラクタやプロパティを構造体に追加することもできます。
なお、上記の例におけるMeキーワードは、構造体自身がメンバを参照するためのキーワードです。 このメソッドでのMe
は、メソッドの引数left
と構造体メンバのLeft
を区別するために用いています。 このような区別をする必要がなければMe
は不要です。 例えば次のように構造体メンバとは異なる引数名にすればMe
キーワードは不要となり、わざわざ明示する必要はなくなります。
値を返さないメソッドであるSub
プロシージャだけでなく、当然値を返すメソッドであるFunction
プロシージャを構造体に持たせることも可能です。
ただ、この場合はメソッドよりもプロパティにした方が設計的には優れているかもしれません。 プロパティについては後述します。
構造体とクラスの共通点・相違点
構造体とクラスは多くの共通する機能を持っていますが、一方で複製時のコストなど実行時のパフォーマンスに影響するような大きな違いもあります(値型と参照型)。 そういった違いを意識せずにクラスと構造体の選択を安易に行うと、一見すると不可思議なエラーに遭遇する場合もあります。 そのため、機能は似ていたとしても構造体とクラスは適切に使い分ける必要があります。
構造体とクラスの共通点・相違点に関してはクラスでの解説も合わせて参照してください。
コンストラクタ (初期値の設定)
構造体にNew
という名前のメソッドを作成すると、それはコンストラクタとして扱われます。 コンストラクタはNew
ステートメントで構造体変数を宣言するときに呼び出される特殊なメソッドで、主にメンバ変数の初期化を行うために用います。
次のコードは先ほどのRect構造体に、宣言時に初期化できるようなコンストラクタを追加した例です。
変数宣言時にこのコンストラクタを使用する場合は次のようにNew
キーワードを使用します。
クラスのコンストラクタとは異なり、構造体におけるコンストラクタは常に1つ以上の引数を取る必要があります。 構造体においては、引数のないコンストラクタは暗黙のコンストラクタとして自動的に用意されるため、そのようなコンストラクタを作成することはできません。
コンストラクタをもつ構造体の場合でも、作成したコンストラクタを使用せず初期化することが出来ます。 この場合、暗黙のコンストラクタが呼び出されます。 ここまでの例で明らかなように、必要がなければコンストラクタを用意しなくてもよく、またコンストラクタが無くても暗黙のコンストラクタによって構造体のインスタンスを作成することができます。
なお、暗黙のコンストラクタが呼び出される場合、構造体内の各フィールドは0など(参照型ならNothing)のデフォルト値に初期化されます。
型とそのデフォルト値については型の種類・サイズ・精度・値域 §.型のデフォルト値を参照してください。
New
キーワードを使用せずに単に変数として宣言した場合もこれと同じ動作となります。 言い換えると、明示的にコンストラクタを呼び出さない場合は、変数として宣言した時点で暗黙のコンストラクタにより初期化されるとも言えます。
オブジェクト初期化子 (初期化と値の設定)
コンストラクタのない構造体や、暗黙のコンストラクタを使って構造体を初期化する際、オブジェクト初期化子を使うと特定のメンバ変数に初期値を設定することができます。 オブジェクト初期化子はVB9(VB2008)で導入されたもので、次の例のようにWithキーワードを使って構造体の初期化と値の設定を同時に行うことができます。
このように、Newキーワードを使った変数宣言に続けてWith {.メンバ変数 = 値, ...}
と記述することで構造体の特定のメンバ変数に値を設定することができます。 オブジェクト初期化子は引数のあるコンストラクタと同時に使用することもできます。
オブジェクト初期化子が使用できないVB8(VB2005)以前の場合は、Withステートメントを使うことができます。 オブジェクト初期化子とWithステートメントによる値の設定はどちらも同等です。
ゼロクリア (再初期化)
既に値が代入・設定されている構造体変数を初期化するには、Nothing
を代入します。 Nothing
を代入することで、構造体内の各フィールドには0
など(参照型ならNothing
)のデフォルト値が設定された状態になります。 これにより、構造体に対してゼロクリアに相当する再初期化を行うことが出来ます。 (型とそのデフォルト値については型の種類・サイズ・精度・値域 §.型のデフォルト値を参照してください)
構造体変数をNothing
(何も参照していない状態・ヌル参照)にすることは出来ないことから、Nothing
を代入することで変数を再初期化するのは一見すると予想と反する動作に見える点で注意が必要です。 Nothing
の代入は、実際にはフィールドをすべてデフォルト値に設定するというよりは、暗黙のコンストラクタで初期化された値を代入して上書きすると言った方が適切でしょう。
次のように暗黙のコンストラクタを使って再初期化しても結果は同じで、両者は見た目・記述の違い程度でしかないため、コーディング規約や読みやすさなどを考慮して適切な方を選ぶとよいでしょう。
また、あらかじめ初期値に相当する値を定数として用意しておいて、それを代入するという方法も取れます。 .NET Frameworkでも、Rectangle.Empty、Point.EmptyやDecimal.Zeroなどといったフィールドがそのような方法にも使えるよう用意されています。 Nothing
の代入を行うより、こういったフィールドを使用する方がコードの意図がより明確にすることができます。
次の例は先のコードに追記してEmptyというフィールドを用意し、その値を代入することで変数を初期化しています。
プロパティ
プロパティはすでにVBを使ったことがある方であれば概要は分かると思います。 プロパティは値の取得・設定をメソッド形式で行う代わりに、メンバ変数と同じ記述で行えるようにするためのものです。 VB.NETでは構造体にもプロパティを持たせることができます。 プロパティの構文などについてはプロパティで解説しています。
例えば先に例を挙げたGetWidth()およびGetHeight()メソッドを読み取り専用プロパティとして書き換えると次のようになります。
次の例は、このようにして作成したプロパティを実際に使用する例です。
配列フィールド
VB.NETでは構造体内に固定長の配列フィールドを作成することができません。 例えばC言語で記述された以下のような構造体をVB.NETで記述することはできません。 配列フィールドにサイズを指定しようとしてもコンパイルエラーとなります。
また、配列フィールドに初期化子を指定して割り当てを行うこともできません。 初期化子を指定しようとしてもコンパイルエラーとなります。 従って、コンストラクタを使って初期化するなどしない限り、配列フィールドは常にNothing
で初期化されます。
こういった構造体内に配列フィールドを作成する場合の注意点などについては構造体と配列フィールドで個別に詳しく解説しているのでそちらを参照してください。 また、構造体内に固定長の領域を作成する方法などについてはフィールドのレイアウト・オフセットを参照してください。
バイト単位での操作
VB.NETでは任意の構造体とバイト配列を相互に変換したり、構造体を直接読み書きするメソッドが用意されていません。 そのため、構造体の内容をファイルに保存したりバイト配列から構造体データを読み込んだりする場合には、独自にコードを記述する必要があります。
そのような処理を実現する方法についてはBinaryReader・BinaryWriterでの構造体の読み書きやシリアライズの基本などで解説しています。