配列(array)とは同じデータ型の要素が集まったデータ構造で、固定長の集合、ベクトルです。 ここでは配列の宣言・作成方法と、配列を使った基本的な操作について見ていきます。 配列内にある要素の検索や、配列のリサイズといった操作については配列操作で解説します。
なお、ここでは次元のない配列(1次元配列)のみを扱います。 多次元配列については多次元配列・ジャグ配列で個別に解説します。 また可変長のデータ構造としてリストなどのコレクションなども存在しますが、ここでは扱いません。
配列とは
プログラム要素として見た場合の配列とは、複数の同じ種類(=同じ型)の値に名前を与えてひとまとめにして扱うものです。 複数の変数の場合は個々の値にそれぞれ名前が与えられますが、配列では値の集まりにひとつの名前(=配列の変数名)が与えられ、配列内の個々の値(要素)はインデックスによって区別します(インデックスは添え字とも呼ばれる)。
上記の例で言えば、user0
, user1
, user2
の各変数は名前から関連性が示唆されるものの独立した値として存在します。 一方users
は、インデックスによって個々の値が区別されると同時に、その値の集合全体にも名前が与えられていて、ひとまとめの値として存在します。
user0 | "Alice" |
---|
user1 | "Bob" |
---|
user2 | "Charlie" |
---|
users | |
---|---|
0 | "Alice" |
1 | "Bob" |
2 | "Charlie" |
配列の作成
言語によって構文に多少の違いはありますが、配列を作成するには、次のように格納される要素の型と格納できる要素数(配列の長さ)を指定して行います。 構文は似通っていますが、C#では格納できる要素数、VBではインデックスの最大値を指定するという間違いやすい相違点があるので、両言語を並行して扱う際は注意が必要です。
上記の例のように長さ(要素数)が0の配列(=要素が1つも格納されない配列)も作成することが出来ます。 長さ・要素数を0には出来ますが、当然マイナスにすることは出来ません。
長さが0の配列については後述の§.空の配列でも詳しく解説します。
配列の作成方法のバリエーションについては後述の§.配列の宣言と初期化を参照してください。
要素の参照
.NETにおける配列では、要素のインデックス(通番・要素の番号)は0を基準とします(zero-based)。 つまり、最初の要素のインデックスは0、その次の要素のインデックスは1、さらにその次は2…、となります。 以降の解説でも、0番目とは最初の要素を指すものとして説明していきます。 C#では配列の変数に続けてかぎ括弧 [ ]、VBでは丸括弧 ( )を使ってインデックスを指定します。
配列は固定長であり、配列を作成するときに格納できる要素数が決定されます。 配列の長さ(格納できる要素数)を超えるインデックスを指定すると実行時に例外IndexOutOfRangeExceptionがスローされます。 配列が自動的に拡張されるといったことは行われません。
つまり、JavaScript等の言語では認められている次のような操作は、.NETにおける配列ではサポートされません。
可変長の配列が必要な場合は、ジェネリックコレクションクラスの一つであるListクラスを使うことができます。 ただし、Listクラスでも上記のように途中のインデックスに要素を追加するようなことはできません。
既存の配列を拡張する必要がある場合はArray.Resizeメソッドを使うことができます。
長さの取得
実行時に配列の長さ(配列の要素数・サイズ)を取得するには、Lengthプロパティを参照します。
多次元配列の場合、Lengthプロパティは多次元配列内の全要素数を返します。 (多次元配列・ジャグ配列 §.次元ごとの長さ・次元数・要素数の取得 (Length/GetLength/Rank))
配列のバイト数を取得する場合は、Buffer.ByteLengthメソッドを使うことができます。 詳しくはバイト列操作 §.配列のバイト数の取得 (ByteLengthメソッド)を参照してください。
配列の宣言と初期化
ここまでで紹介した例では、配列変数の宣言と、配列の作成・代入を同時に行っていました。 ここでは配列の宣言と、配列の作成・初期化の方法を分けて見ていきます。
配列の宣言
まず、配列の宣言について見ていきます。 配列を格納する変数を宣言するには、次のようにします。
VBでは、丸括弧( )を変数名の後ろに付けた場合も、型名の後ろに付けた場合も、同じ配列変数として扱われます。 どちらも意味は同じです。
このようにして宣言した変数は、配列が格納できる変数が用意されるだけで、実際にはどの配列も参照していない(配列の実体を参照していない)状態となっています。 C#では未初期化の変数、VBではヌル参照の変数となります。 この状態から、配列を作成して変数に代入したり、他の変数が参照している配列やメソッドの戻り値として返される配列などを変数に代入したりする必要があります。
配列の初期化
続いて配列の初期化について見ていきます。 次のようにすることで、配列変数の宣言と同時に配列を作成して変数に代入することが出来ます。
このコードにおいて、arr1, arr2, arr3はすべて同じ長さで同じ内容が格納された配列となります。 arr4, arr5, arr6も同様に同じ内容が格納されています。 このコードのnew以降の部分は配列作成式、中括弧 { } の部分は配列初期化子と呼ばれます。 配列作成式で配列の長さを指定する場合、配列初期化子で記述されている要素数と合っていないとコンパイルエラーとなります。
配列初期化子を記述しない場合は、指定された長さの配列が作成されますが、作成される配列内の各要素にはデフォルト値が設定されます。
デフォルト値は型によって異なりますが、数値などの値型では0
や0
に相当する値、文字列型やクラスなどの参照型ではヌル参照(null/Nothing)となります。
型とデフォルト値の詳細については型の種類・サイズ・精度・値域 §.型のデフォルト値、型の種類については値型と参照型を参照してください。
配列初期化子を記述しない場合は、次のように変数に代入されている値を長さとして配列を作成することもできます。 この場合も、各要素にデフォルト値が設定された状態の配列が作成されます。
長さ0の配列
長さ0の配列(空の配列)の作成については後述の§.空の配列で解説します。
配列フィールドの宣言
ローカル変数の場合と同様、クラスや構造体のフィールドとしても配列を宣言することもできます。
ただし、構造体内の配列フィールドには初期化子を指定することはできません。 一方、クラスの場合は配列フィールドに初期化子を指定することができます。
構造体内の配列フィールドと初期化子については構造体と配列フィールドで詳しく解説しています。
配列の代入
配列は参照型です。 そのため、配列の変数に代入を行う場合、配列がまるごとコピーされて代入されるのではなく、配列への参照が代入されるだけとなる点に注意が必要です。
次の例のように、配列の変数に代入を行っても配列の実体は一つであるため、異なる変数を経由した変更でも実際に行われる変更は同一の配列に対するものとなります。
配列のコピーを行いたい場合はArray.CloneメソッドまたはArray.Copyメソッドを使います。
要素の列挙
配列内の各要素を列挙するには、for文とforeach文が使えます。 for文では配列のインデックスを使って各要素を参照するのに対し、foreach文では個別の変数によって列挙された各要素を参照します。
foreach文では常に配列の先頭から一つずつ列挙されるため、配列の末尾から先頭に向けて列挙したり、一つ飛ばしで列挙したりといったことは出来ません。 そういった列挙が必要な場合はforeach文ではなくfor文を使って記述する必要があります。
for文とforeach文の使い分けは概ね以下のようになると言えます。
- for文
- 列挙する際に要素の位置(インデックス)が重要となる場合
インデックスによって各要素に対して行う処理を振り分けたい場合(インデックスn以上の要素だけ参照したい、インデックスがnのときだけ処理を行いたいなど) - foreach文
- 列挙する際に要素の位置(インデックス)が重要でない場合
インデックスに関わらずすべての要素に同じ処理を行いたい場合
配列内から条件に合う要素を検索・取得したりするにはArrayクラスのFindメソッドやExistsメソッドなどを使うことができます。 また、配列内の要素の順番を逆にして列挙したい場合はReverseメソッドを使うことができます。
for文とforeach文による配列の列挙では、パフォーマンス上の差はほとんどありません。
foreach文での列挙は配列に実装されているIEnumerableインターフェイスから提供される機能によって実現しています。 配列とIEnumerableインターフェイスについては後述の§.配列のインターフェイス、IEnumerableインターフェイスと列挙についてはIEnumerable・IEnumeratorで詳しく解説しています。
要素の検索・ソート、配列全体への変更
配列内にある要素を検索する、ソートを行う、また配列のサイズを変更する、コピーする、配列全体の型変換を行うといった操作は、Arrayクラスに用意されているユーティリティメソッドを使うことで行うことができます。 Arrayクラスを使った配列の操作については配列操作で解説しています。
また、ソートに関しては個別に基本型のソートと昇順・降順でのソートでも詳しく解説しています。
空の配列
.NETでは空の配列を作成することができます。 空の配列とは、長さが0で要素が1つも格納されていないあるいは1つも格納できない配列です。
構文を用いた作成
空の配列(長さ0の配列)を作成したい場合は次のように記述することができます。
空の配列はヌル参照の状態とは明確に異なります。 空の配列は、配列の実体を参照しているがその配列にはなにも格納されていない状態になります。 ヌル参照では、配列自体を参照していない状態になります。
Array.Emptyメソッドを用いた作成
.NET Framework 4.6以降ではArray.Empty<T>メソッドを使うことでも空の配列を取得することができます。 それより前のバージョンでは、Emumerable.Emptyメソッドの戻り値に対してToArrayメソッドを呼び出すことで空の配列を作成することができます。
呼び出される回数が膨大で配列の生成コストが無視できないようなメソッドや、使用可能なメモリが極端にシビアな実行環境などでは、空の配列を都度作成・取得するよりも、あらかじめキャッシュしておいたインスタンスを使用するようにするか、Type.EmptyTypesのようにあらかじめ用意された空の配列を使用することでパフォーマンスを向上させることができます。
Array.Emptyメソッドが常にキャッシュされたインスタンスを返すかどうか、呼び出しの度に空の配列のインスタンスを作成するのかどうかについてはドキュメントには明記されていません。
一方、Emumerable.Emptyメソッドでは以下のように記載されているため常にキャッシュされた空のシーケンスを返すことが保証されています。
Empty<TResult>() メソッドは、TResult 型の空のシーケンスをキャッシュします。
Enumerable.Empty<TResult> メソッド ()
しかし、Enumerable.Empty<T>().ToArray()
の呼び出しでは、ToArrayメソッドが空のシーケンスから配列の作成を行うため呼び出しの度に空の配列のインスタンスが作成されます。
ヌル参照と空の配列・戻り値や初期値としての空の配列
配列を返すメソッドでは、null
/Nothing
よりも空の配列を返したほうがよい場合があります。 配列を返すメソッドがnullを返す場合、呼び出し側は戻り値がnullかどうかの判定を行った上で列挙などの操作を行う必要がありますが、空の配列を返すようにすれば呼び出し側はnull判定を行わずに戻り値の配列に操作を行うことができます。
上記の例では、ファイルが無かった場合にもnullや空の配列を返すようにすることもできますが、推奨できません。 例えば、上記のメソッドにエラー処理を追加してファイルの内容が異常だった場合にもnullを返すような実装にした場合、メソッドの呼び出し側は戻り値がnullとなった原因が何なのかを知ることができません。
このため、なんらかのエラーがあった場合には、nullや空の配列を返すよりも、ファイルが無かった場合はFileNotFoundException、ファイルの内容やフォーマットが異常だった場合はInvalidDataExceptionをスローするというように、状況にあった例外をスローして呼び出し側に原因を通知すべきです。
配列フィールドでも、初期値としてnull
/Nothing
の代わりに空の配列を指定しておくことができます。 メソッドの場合と同様、初期値として空の配列を指定しておくことでnull判定をせずに配列フィールドの参照ができるようになります。
一方この場合、空の配列よりもnull
/Nothing
の方が初期値として妥当な場合もあります。 例えば、このフィールドがデータベースからの参照やユーザー入力によって設定されるものであれば、まだ設定が行われていないことを表す目的でnullを初期値としておいたほうが妥当と考えることもできます。 (もちろん、未入力を表すフラグは別に用意し、初期値は空の配列にしておく、という選択もありえます。)
このように、空の配列を用いることでヌル判定やヌル参照の状況をさけることができる一方で、空の配列とヌル参照では意味が全く異なるため、空の配列とヌル参照を混同したり、単純に置き換えることはできません。 どちらがより妥当か十分検討する必要があります。
部分配列
Pythonなどの言語においては、配列内の区間を指定して部分配列を取り出す次のような構文(arr[from..to]
やarr[from:until]
など)が用意されています。
C# 8.0以降(.NET Core 3.0以降)では、これに相当する範囲構文を使用することができます。 範囲構文は、Rubyと類似した構文[n..m]
を使いますが、表す範囲はPython・JavaScriptと同じく[from..until]
であり、終端側が開区間となっている半開区間の範囲になります。
一方それ以前のバージョンのC#やVBではこれに相当する構文は用意されていません。 また、JavaScriptなどの言語におけるslice
のようなメソッドも用意されていません。
このような部分配列を切り出すような構文やメソッドは用意されていませんが、ArraySegment構造体を用いるとこれに似た操作を行うことができます。
部分配列を取得する方法や、配列の一部分をコピーして部分配列を作成する方法については部分配列を参照してください。
配列のインターフェイス
配列(厳密には配列の基底クラスであるArrayクラス)は、IEnumerable, ICollection, IListなどのインターフェイスを実装しています。 これにより、配列も一種のコレクションとして、他のコレクションと同等に扱うことができるようになっています。
例えば、IEnumerableまたはIEnumerable<T>を引数にとるメソッドでは配列を指定することができます。 String.Concatメソッド(.NET Framework 4以降)では、任意の型T
のIEnumerable<T>を引数にとることができます。 このメソッドに配列を指定することで、配列内の各要素の値を結合した文字列を取得することができます。
またWhereなどのLINQの各メソッドもIEnumerable<T>を引数にとりますが、配列はIEnumerable<T>を実装しているため配列に対してもLINQのメソッドを使用することができます。
配列がICollectionなどのインターフェイスを実装しているといっても、その機能のすべてがサポートされるわけではありません。 たとえば、配列をICollectionインターフェイスにキャストして操作することは可能ですが、ICollection.AddやICollection.Removeなどのメソッドを呼び出して配列のサイズを変更するような操作を行うことはできません。 このような操作を行おうとした場合にはNotSupportedExceptionがスローされます。
配列が実装するインターフェイスや他のコレクションとの特徴の違いについてはコレクションの種類と特徴 §.コレクションクラスの特徴を参照してください。
配列の使用と代替手段
ここでは配列が適用できる場合と、それが不適切となる場合、またその場合の代替手段やより適切な実装方法などについて解説します。
object配列
object型の配列では任意の型の値を格納することができますが、object配列が必要になる場面はそう多くなく、また必要になった場合でもそれはデータ構造を定めていないことによる不適切な実装である可能性があります。
こういった場合、格納するデータの構造をクラスや構造体で適切に定め、それに対応した配列型を使用すべきです。 安易にobject型配列を用いる前に、データ構造が適切に定められているか、既存の型を使ってデータを扱えないか検討してください。
可変長の引数
引数で配列を指定することにより、任意個の値をメソッドに渡すことができます。 この場合、あらかじめ引数に渡す値を配列としてまとめておく必要があります。
配列型の引数とは別に、params
キーワード(C#)・ParamArray
キーワード(VB)を使うことにより、引数を可変長とすることができます。 可変長の引数では、引数に渡す値は必ずしも配列にする必要はありません(配列を直接渡すこともできます)。 メソッド側では、可変長引数は通常の配列と同様に扱うことができます。
可変長引数は、必ずメソッドの一番最後の引数でなければなりません。 例えば、可変長引数のあとに通常の引数をとるようなメソッドを作成することはできません。
可変長引数の型をobjectにすることにより、任意の型の値を任意個とることができるようになります。 例えば、Console.WriteLineメソッドやString.Joinメソッドではobject型の可変長引数をとることができるようになっています。
複数の戻り値
メソッドの戻り値を配列にすることでメソッドから複数の値を返すことができます。 しかし、例えば意味の異なる値を配列でひとまとめにして返すようにすると、格納されている値の意味があいまいになり、また呼び出し側では戻り値を配列から展開する必要があります。
このような場合は、配列よりもoutパラメータ(VBではByRef引数)を使ったほうがより適切です。
outパラメータ・ByRef引数はメソッドの戻り値と併用することもできます。 実際、.NETで用意されている商と剰余を求めるMath.DivRemメソッドでは、商は戻り値として、剰余はoutパラメータで返されるようになっています。
Math.DivRemメソッドについては数学関数 §.積・商と剰余 (BigMul, DivRem)を参照してください。
これとは異なる方法で複数の値を返す手段として、Tuple型(タプル)を利用することもできます。 Tuple型は複数の値をひとまとめにするという点は配列と似ていますが、異なる型同士でもまとめることができる点で配列とは異なります。 これにより、配列では値を複数返す場合でも同じ型に限られるのに対し、Tuple型を用いれば型の異なる複数の値を返すことができます。
このように、配列と違ってTuple型では型の異なる値をひとまとめにできる利点がある一方、Tuple型では格納されている値を参照するのにItem1, Item2といったメンバ名で参照する必要があるため、配列同様に格納されている値の意味があいまいになるという欠点もあります。
比較用の参考として、JavaScriptなどいくつかのスクリプト言語では以下のようなコードを記述することができます。
C# 7.0以降、VB15.3以降では、上記のJavaScriptコードのような記述ができるタプルが構文としてサポートされています。 これにより、Tuple型を使わなくても、複数のスカラー値をタプルで返したり、タプルで返される戻り値を変数(スカラー値)に展開することができます。 基本的にはTuple型と同じですが、この構文では格納されている値に対して変数と同様に任意の名前をつけることができるようになっています。