配列をクラスや構造体のフィールドとして格納することは出来ますが、.NET Frameworkにおいて構造体内に配列のフィールドを用意する場合は、C言語等での構造体とは異なる次の点を意識しておく必要があります。
- (他の種類のフィールドと同様に)構造体内にある配列のフィールドには初期化子を指定できない
- 配列は参照型である
- 配列のフィールドを固定長にすることはできない
このそれぞれについて詳しく見ていきます。
配列フィールドと初期化
次のコードでは、行数と列数、各セルのデータをそれぞれフィールドで持つマトリックスを表す構造体Matrixを作成しています。 配列をフィールドに持つ構造体の扱いは言語によって若干異なりますが、いずれも初期化子によって配列フィールドに初期値を設定することは出来ず、また初期化されていないフィールドを参照しようとするとエラーとなります。
そのため、構造体内にある配列フィールドを参照する場合は、次のように事前に配列を確保して代入しておくようにする必要があります。
もしくは、引数をとるコンストラクタを用意しておき、構造体を使用する場合はそれを使って初期化するようにします。
当然、このようにコンストラクタを用意していても、それを使わずに構造体を初期化した場合は、配列フィールドには何も設定されておらずヌル参照の状態となります。 例えば、次の例では構造体に暗黙的に用意されるコンストラクタ(デフォルトコンストラクタ)を使って変数を初期化していますが、初期化の結果配列フィールドにはヌル参照が設定されるため、実行するとNullReferenceExceptionがスローされます。
さらに、デフォルトコンストラクタは暗黙的に用意されるため、引数のないコンストラクタを構造体に実装することもできません。 構造体に引数のないコンストラクタを実装するとコンパイルエラーとなります。 従って、配列をフィールドに持ち、かつヌル参照以外に初期化させたい場合は、デフォルトコンストラクタを使用した初期化をしないようにするか、構造体ではなくコンストラクタによる初期化を強制できるクラスを使用するようにする必要があります。
その他、C#では固定長の配列フィールドを使うことによりnull以外に初期化された配列フィールドを宣言することができます。
構造体の代入と配列フィールドのコピー
構造体自体は値型ですが、配列は参照型であるため構造体内における配列フィールドも当然参照型です(値型と参照型)。 構造体変数は代入によって各フィールドがコピーされますが、その結果配列フィールドが参照する配列はコピー元とコピー先で同一のインスタンスとなります。 そのため、コピー元配列もしくはコピー先配列への変更は相互に影響します。
このように、構造体変数では代入によって配列フィールドのインスタンスとその内容までコピーされるわけではない(簡易コピーが行われる)という点に注意が必要です。
配列を含め参照型をフィールドに持つ型をどのように複製するかという点についてはオブジェクトの複製で詳しく解説していますが、ここではその一つの例として、コピーコンストラクタを用意して複製を可能にする方法について紹介します。 この例で使用しているArray.Cloneメソッドは配列を複製して同じ内容の配列を作成するメソッドです。 詳しくは配列操作 §.複製 (Clone)で解説しています。
固定長の配列フィールド
構造体内の配列フィールドに初期値を設定できないのと同様、配列フィールドを固定長にすることも出来ません。 例えば、C言語で記述した次のような構造体を考えます。
これと同様の構造体を作成することを考えた場合、次のようなフィールド宣言はいずれもコンパイルエラーとなります。
C#では、このような配列フィールドを宣言するためにfixedステートメントを使うことが出来ます。
ただし、fixedステートメントを使う構造体はunsafeな構造体でなければならず、また固定長配列のフィールドを参照する場合はunsafeコンテキスト内で行わなければなりません。 また、配列の型もプリミティブ型のうちbyte, short, int, long, sbyte, ushort, uint, ulong, char, float, double, boolのいずれかに限定されます。 従って構造体内に固定長の構造体配列フィールドを宣言することも出来ません。
さらに、fixedステートメントでは1次元の固定長配列のみが宣言でき、2次元以上の固定長配列は宣言することが出来ません。 固定長配列を宣言するにはコード中でunsafeコンテキストを使用するため、コンパイルオプション(/unsafe+)でアンセーフコードの使用を許可する必要があります。
VBではunsafeやfixedのようなステートメントが用意されていないため、構造体内に固定長の配列フィールドを宣言することは出来ません。 ただ、VBFixedArrayAttribute属性でフィールドをマークすることで配列フィールドが固定長であるように扱われることを期待することも出来ますが、ここではその詳細については触れません。
fixedステートメントで固定長配列の宣言を行う以外にも、別の方法で固定長配列の代用を行うことも出来ます。 次の例では、固定長配列を宣言する代わりに、配列を同型の複数フィールドに展開して宣言しています。 これは愚直なやり方ですが、そもそも配列は同型の値が複数個集まったものであり、このようにすることで確保されるフィールドのサイズが固定長となることを保証できるという点や、またアンセーフコードを一切使わないという点でも、この方法は有効な方法です。
このようにして複数のフィールドを固定長配列の代用とした場合、フィールド変数が多くなり扱いづらくなりますが、配列となるフィールド群を別途共用体として構成することでより簡便に扱えるようにも出来ます。 共用体を構成する方法についてはフィールドのレイアウト・オフセット §.共用体の実装で解説しています。
固定長の配列フィールドを扱う場合は、同時にバイト配列やポインタと構造体の相互変換も行うことがありますが、そういった方法についてはBinaryReader・BinaryWriterでの構造体の読み書きで解説しています。