17 個の配列
17.1 全般
"配列" とは、算出されたインデックスを介してアクセスされる多くの変数を含むデータ構造です。 配列に含まれる変数は配列の要素とも呼ばれ、すべて同じ型であり、この型は配列の要素型と呼ばれます。
配列には、各配列要素に関連付けられているインデックスの数を決定するランクがあります。 配列のランクは、配列の次元とも呼ばれます。 ランクが 1 の配列は、 single 次元配列と呼ばれます。 ランクが 1 より大きい配列は、 多次元配列と呼ばれます。 特定のサイズの多次元配列は、多くの場合、2 次元配列、3 次元配列などと呼ばれます。 配列の各次元には、0 以上の整数の長さが関連付けられています。 次元の長さは配列の型の一部ではなく、配列型のインスタンスが実行時に作成されるときに確立されます。 ディメンションの長さは、そのディメンションのインデックスの有効な範囲を決定します。長さ N
のディメンションの場合、インデックスの範囲は 0
から N – 1
含めることができます。 配列内の要素の合計数は、配列内の各次元の長さの積です。 配列の 1 つ以上の次元の長さが 0 の場合、配列は空であると言われます。
配列の要素型は、それ自体を配列型 (§17.2.1) にすることができます。 このような配列の配列は多次元配列とは異なり、"ジャグ配列" を表すために使用できます。
例:
int[][] pascals = { new int[] {1}, new int[] {1, 1}, new int[] {1, 2, 1}, new int[] {1, 3, 3, 1} };
end の例
すべての配列型は参照型です (§8.2)。 配列の要素型には、値型や配列型など、任意の型を指定できます。
17.2 配列型
17.2.1 全般
配列型の文法の生成は、 §8.2.1 で提供されています。
配列型は、1 つ以上のrank_specifierが続くnon_array_typeとして書き込まれます。
non_array_typeは、それ自体がarray_typeではない任意の型です。
配列型のランクは、array_typeの左端のrank_specifierによって与えられます。rank_specifierは、配列が 1 のランクにrank_specifier内の ",
" トークンの数を加えた配列であることを示します。
配列型の要素型は、左端の rank_specifierを削除した結果の型です。
T[R]
形式の配列型は、ランクR
と配列以外の要素型T
を持つ配列です。T[R][R₁]...[Rₓ]
形式の配列型は、ランクR
と要素型T[R₁]...[Rₓ]
を持つ配列です。
実際には、rank_specifierは、最後の非配列要素型左から右に読み取られます。
例:
T[][,,][,]
の型は、int
の 2 次元配列の 3 次元配列の 1 次元配列です。 end の例
実行時に、配列型の値を null
したり、その配列型のインスタンスへの参照を指定したりできます。
注: §17.6 の規則に従って、値は共変配列型への参照である場合もあります。 end note
17.2.2 System.Array 型
System.Array
型は、すべての配列型の抽象基本型です。 配列型からSystem.Array
への暗黙的な参照変換 (§10.2.8) は、System.Array
によって実装された任意のインターフェイス型に存在します。 明示的な参照変換 (§10.3.5) は、 System.Array
と、 System.Array
によって実装された任意のインターフェイス型から任意の配列型に存在します。 System.Array
はそれ自体が array_typeではありません。 むしろ、すべてのarray_typeが派生するclass_typeです。
実行時に、 System.Array
型の値を null
したり、任意の配列型のインスタンスへの参照を指定したりできます。
17.2.3 配列とジェネリック コレクション インターフェイス
1 次元配列 T[]
は、インターフェイス System.Collections.Generic.IList<T>
(略してIList<T>
) とその基本インターフェイスを実装します。 したがって、 T[]
から IList<T>
とその基本インターフェイスへの暗黙的な変換があります。 さらに、 S
から T
への暗黙的な参照変換がある場合、 S[]
は IList<T>
を実装し、 S[]
から IList<T>
とその基本インターフェイスへの暗黙的な参照変換があります (§10.2.8)。 S
からT
への明示的な参照変換がある場合は、S[]
からIList<T>
とその基本インターフェイス (§10.3.5) への明示的な参照変換があります。
同様に、1 次元配列 T[]
では、インターフェイス System.Collections.Generic.IReadOnlyList<T>
(略してIReadOnlyList<T>
) とその基本インターフェイスも実装されます。 したがって、 T[]
から IReadOnlyList<T>
とその基本インターフェイスへの暗黙的な変換があります。 さらに、 S
から T
への暗黙的な参照変換がある場合、 S[]
は IReadOnlyList<T>
を実装し、 S[]
から IReadOnlyList<T>
とその基本インターフェイスへの暗黙的な参照変換があります (§10.2.8)。 S
からT
への明示的な参照変換がある場合は、S[]
からIReadOnlyList<T>
とその基本インターフェイス (§10.3.5) への明示的な参照変換があります。
例: 次に例を示します。
class Test { static void Main() { string[] sa = new string[5]; object[] oa1 = new object[5]; object[] oa2 = sa; IList<string> lst1 = sa; // Ok IList<string> lst2 = oa1; // Error, cast needed IList<object> lst3 = sa; // Ok IList<object> lst4 = oa1; // Ok IList<string> lst5 = (IList<string>)oa1; // Exception IList<string> lst6 = (IList<string>)oa2; // Ok IReadOnlyList<string> lst7 = sa; // Ok IReadOnlyList<string> lst8 = oa1; // Error, cast needed IReadOnlyList<object> lst9 = sa; // Ok IReadOnlyList<object> lst10 = oa1; // Ok IReadOnlyList<string> lst11 = (IReadOnlyList<string>)oa1; // Exception IReadOnlyList<string> lst12 = (IReadOnlyList<string>)oa2; // Ok } }
object[]
からIList<string>
への変換は暗黙的ではなく明示的な変換であるため、割り当てlst2 = oa1
はコンパイル時エラーを生成します。 キャスト(IList<string>)oa1
では、string[]
ではなくobject[]
oa1
参照するため、実行時に例外がスローされます。 ただし、キャスト (IList<string>)oa2
では、oa2
がstring[]
を参照しているため、例外はスローされません。end の例
S[]
からIList<T>
への暗黙的または明示的な参照変換がある場合は常に、IList<T>
とその基本インターフェイスからS[]
への明示的な参照変換もあります (§10.3.5)。
配列型S[]
IList<T>
を実装すると、実装されたインターフェイスのメンバーの一部が例外をスローする可能性があります。 インターフェイスの実装の正確な動作は、この仕様の範囲外です。
17.3 配列の作成
配列インスタンスは、 array_creation_expression (§12.8.16.5) または array_initializer (§17.7 を含むフィールドまたはローカル変数の宣言によって作成されます。 配列インスタンスは、パラメーター配列を含む引数リストの評価の一環として暗黙的に作成することもできます (§15.6.2.4)。
配列インスタンスが作成されると、各ディメンションのランクと長さが確立され、インスタンスの有効期間全体にわたって一定のままになります。 つまり、既存の配列インスタンスのランクを変更することも、その次元のサイズを変更することもできません。
配列インスタンスは常に配列型です。 System.Array
型は、インスタンス化できない抽象型です。
array_creation_expressionによって作成された配列の要素は、常に既定値 (§9.3) に初期化されます。
17.4 配列要素のアクセス
配列要素は、フォーム A[I₁, I₂, ..., Iₓ]
のelement_access式 (§12.8.11.2) を使用してアクセスされます。ここで、A
は配列型の式であり、各Iₑ
はint
型、uint
型、long
式、ulong
型の式、またはこれらの型の 1 つ以上に暗黙的に変換できます。 配列要素アクセスの結果は変数、つまりインデックスによって選択された配列要素です。
配列の要素は、 foreach
ステートメント (§13.9.5) を使用して列挙できます。
17.5 配列メンバー
すべての配列型は、 System.Array
型によって宣言されたメンバーを継承します。
17.6 配列共分散
A
とB
の 2 つのreference_typeについては、 暗黙的な参照変換 (§10.2.8) または明示的な参照変換 (§10.3.5) がA
からB
に存在する場合、配列型A[R]
から配列型B[R]
への同じ参照変換も存在し、R
は指定されたrank_specifier (ただし、両方の配列型では同じです)。 この関係は、 配列共分散と呼ばれます。 特に、配列の共分散は、B
からA
への暗黙的な参照変換が存在する場合、配列型A[R]
の値が実際には配列型B[R]
のインスタンスへの参照である可能性があることを意味します。
配列の共分散のため、参照型配列の要素への代入には、配列要素に割り当てられている値が実際に許可された型 (§12.21.2) であることを確認する実行時チェックが含まれます。
例:
class Test { static void Fill(object[] array, int index, int count, object value) { for (int i = index; i < index + count; i++) { array[i] = value; } } static void Main() { string[] strings = new string[100]; Fill(strings, 0, 100, "Undefined"); Fill(strings, 0, 10, null); Fill(strings, 90, 10, 0); } }
Fill
メソッドのarray[i]
への割り当てには、実行時チェックが暗黙的に含まれています。これにより、value
がnull
参照であるか、array
の実際の要素型と互換性のある型のオブジェクトへの参照であることが保証されます。Main
では、Fill
の最初の 2 つの呼び出しは成功しますが、3 番目の呼び出しでは、array[i]
への最初の割り当ての実行時にSystem.ArrayTypeMismatchException
がスローされます。 この例外は、ボックス化されたint
をstring
配列に格納できないために発生します。end の例
配列の共分散は、特に value_typeの配列には拡張されません。 たとえば、 int[]
を object[]
として扱うことが許可される変換は存在しません。
17.7 配列初期化子
配列初期化子は、フィールド宣言 (§15.5)、ローカル変数宣言 (§13.6.2)、配列作成式 (§12.8.16.5) で指定できます。
array_initializer
: '{' variable_initializer_list? '}'
| '{' variable_initializer_list ',' '}'
;
variable_initializer_list
: variable_initializer (',' variable_initializer)*
;
variable_initializer
: expression
| array_initializer
;
配列初期化子は、変数初期化子のシーケンスで構成され、"{
" トークンと "}
" トークンで囲み、",
" トークンで区切られます。 各変数初期化子は式か、多次元配列の場合は入れ子になった配列初期化子です。
配列初期化子が使用されるコンテキストによって、初期化される配列の型が決まります。 配列作成式では、配列型は初期化子の直前、または配列初期化子の式から推論されます。 フィールドまたは変数の宣言では、配列型は宣言されているフィールドまたは変数の型です。 フィールドまたは変数の宣言で配列初期化子を使用する場合、
int[] a = {0, 2, 4, 6, 8};
これは、同等の配列作成式の短縮形です。
int[] a = new int[] {0, 2, 4, 6, 8};
1 次元配列の場合、配列初期化子は、配列の要素型への暗黙的な変換を持つ式のシーケンスで構成されます (§10.2)。 この式は、インデックス 0 の要素から始まり、配列要素を順番に初期化します。 配列初期化子内の式の数によって、作成される配列インスタンスの長さが決まります。
例: 上記の配列初期化子は、長さ 5 の
int[]
インスタンスを作成し、次の値を使用してインスタンスを初期化します。a[0] = 0; a[1] = 2; a[2] = 4; a[3] = 6; a[4] = 8;
end の例
多次元配列の場合、配列初期化子は、配列内に次元があるのと同じ数のレベルの入れ子を持つ必要があります。 最も外側の入れ子レベルは左端の次元に対応し、最も内側の入れ子レベルは右端の次元に対応します。 配列の各次元の長さは、配列初期化子の対応する入れ子レベルの要素数によって決まります。 入れ子になった配列初期化子ごとに、要素の数は、同じレベルの他の配列初期化子と同じである必要があります。
例: 例:
int[,] b = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};
は、左端の次元の場合は長さが 5、右端の次元の場合は長さが 2 の 2 次元配列を作成します。
int[,] b = new int[5, 2];
次の値を使用して配列インスタンスを初期化します。
b[0, 0] = 0; b[0, 1] = 1; b[1, 0] = 2; b[1, 1] = 3; b[2, 0] = 4; b[2, 1] = 5; b[3, 0] = 6; b[3, 1] = 7; b[4, 0] = 8; b[4, 1] = 9;
end の例
右端以外のディメンションに長さ 0 を指定した場合、後続のディメンションも長さ 0 であると見なされます。
例:
int[,] c = {};
は、左端と右端の両方の次元の長さが 0 の 2 次元配列を作成します。
int[,] c = new int[0, 0];
end の例
配列作成式に明示的な次元の長さと配列初期化子の両方が含まれている場合、長さは定数式で、各入れ子レベルの要素数は対応する次元の長さと一致する必要があります。
例: いくつかの例を次に示します。
int i = 3; int[] x = new int[3] {0, 1, 2}; // OK int[] y = new int[i] {0, 1, 2}; // Error, i not a constant int[] z = new int[3] {0, 1, 2, 3}; // Error, length/initializer mismatch
ここで、
y
の初期化子では、ディメンションの長さの式が定数ではないため、コンパイル時エラーが発生し、初期化子の長さと要素の数が一致しないため、z
の初期化子はコンパイル時エラーになります。end の例
注: C# では、 array_initializerの末尾に末尾のコンマを使用できます。 この構文により、このようなリストからメンバーを追加または削除する柔軟性が得られ、そのようなリストのマシン生成が簡略化されます。 end note
ECMA C# draft specification