C#配列(array)の使い方完全ガイド|宣言・初期化・追加・ループ処理まで初心者向けに解説

はじめに

C#の配列(array)は、同じ型の複数のデータをまとめて管理するための基本的なデータ構造です。数値の一覧、商品名のリスト、座標データなど、複数の値を順番に扱う場面で利用されます。

配列を使うと、複数の変数を個別に用意する必要がなくなり、ループ処理によってすべての要素を効率よく操作できます。一方で、C#の配列は作成後に要素数を変更できないため、要素を頻繁に追加・削除する場合にはList<T>を使うなど、用途に応じた使い分けが必要です。

この記事では、C#配列の宣言・初期化・要素の取得・追加・削除・ループ処理・検索・並べ替え・多次元配列まで、初心者にもわかりやすく解説します。

1. C#の配列(array)とは

1-1. 配列の基本的な仕組み

配列とは、同じデータ型の値を複数まとめて格納できるデータ構造です。

たとえば、3人分の点数を個別の変数で管理すると、次のようになります。

C#
int score1 = 80;
int score2 = 90;
int score3 = 75;

配列を使えば、これらの値を1つの変数にまとめられます。

C#
int[] scores = { 80, 90, 75 };

scoresという1つの変数を通して、3つの整数を管理できます。

C#の配列には、基本的に同じ型の値だけを格納します。int[]には整数、string[]には文字列を格納します。

C#
string[] names = { "佐藤", "鈴木", "高橋" };

1-2. 配列を使うメリット

配列には、主に次のようなメリットがあります。

  • 同じ種類のデータをまとめて管理できる

  • インデックスを使って任意の要素へアクセスできる

  • for文やforeach文で繰り返し処理できる

  • 要素数が固定されているため、構造がわかりやすい

  • 連続したデータを効率よく扱える

たとえば、配列に格納した点数をすべて表示する場合、次のように記述できます。

C#
int[] scores = { 80, 90, 75 };

foreach (int score in scores)
{
Console.WriteLine(score);
}

要素数が増えても、同じループ処理をそのまま利用できます。

1-3. 配列と変数の違い

通常の変数は、基本的に1つの値を保持します。

C#
int score = 80;

一方、配列は複数の値をまとめて保持します。

C#
int[] scores = { 80, 90, 75 };

配列の各要素には、インデックスと呼ばれる番号を使ってアクセスします。

C#
Console.WriteLine(scores[0]); // 80
Console.WriteLine(scores[1]); // 90
Console.WriteLine(scores[2]); // 75

複数の関連する値を扱う場合は、個別の変数を増やすよりも配列を使用したほうが管理しやすくなります。

1-4. 配列とList<T>の違い

配列とList<T>は、どちらも複数の同じ型の値を管理できます。ただし、要素数の扱いが異なります。

配列は、作成時に決めた要素数を後から直接変更できません。

C#
int[] numbers = new int[3];

一方、List<T>は要素を自由に追加・削除できます。

C#
List<int> numbers = new List<int>();

numbers.Add(10);
numbers.Add(20);
numbers.Remove(10);

配列は、要素数が決まっているデータに適しています。List<T>は、処理中に要素数が変化するデータに適しています。

比較項目配列List<T>
要素数作成後は固定追加・削除できる
要素へのアクセスインデックスインデックス
要素数の取得LengthCount
要素の追加直接はできないAdd
要素の削除直接はできないRemoveRemoveAt
適した用途要素数が固定されたデータ要素数が変化するデータ

1-5. 配列の要素番号(インデックス)は0から始まる

C#の配列では、最初の要素のインデックスは0です。

C#
string[] fruits = { "りんご", "みかん", "ぶどう" };

Console.WriteLine(fruits[0]); // りんご
Console.WriteLine(fruits[1]); // みかん
Console.WriteLine(fruits[2]); // ぶどう

要素数が3の場合、使用できるインデックスは012です。

最後のインデックスは、常に次の値になります。

C#
配列.Length - 1

したがって、最後の要素は次のように取得できます。

C#
Console.WriteLine(fruits[fruits.Length - 1]); // ぶどう

2. C#で配列を宣言・初期化する方法

2-1. 配列を宣言する基本構文

C#で配列を宣言する基本構文は次のとおりです。

C#
データ型[] 変数名;

整数配列と文字列配列は、次のように宣言します。

C#
int[] numbers;
string[] names;

宣言しただけでは、配列の実体は作成されていません。ローカル変数の場合は、値を代入してから使用する必要があります。

C#
int[] numbers;
numbers = new int[3];

宣言と配列の作成は、同時に記述するのが一般的です。

C#
int[] numbers = new int[3];

2-2. 要素数を指定して配列を初期化する

new演算子を使い、角括弧の中に要素数を指定します。

C#
int[] numbers = new int[5];

このコードでは、5個の整数を格納できる配列が作成されます。

作成直後の各要素には、その型のデフォルト値が設定されます。

C#
Console.WriteLine(numbers[0]); // 0
Console.WriteLine(numbers[4]); // 0

文字列配列の場合、初期値はnullです。

C#
string[] names = new string[3];

Console.WriteLine(names[0] == null); // True

2-3. 初期値を指定して配列を作成する

配列の作成時に、各要素の初期値を指定できます。

C#
int[] numbers = new int[] { 10, 20, 30 };

文字列配列も同じ形式で作成できます。

C#
string[] colors = new string[] { "赤", "青", "緑" };

初期値を指定する場合は、指定した値の数から要素数が自動的に決まるため、角括弧の中に要素数を記述する必要はありません。

2-4. 宣言と初期化を同時に行う

配列は、データ型の後ろにあるnew データ型[]を省略して初期化できます。

C#
int[] numbers = { 10, 20, 30 };
string[] names = { "佐藤", "鈴木", "高橋" };

簡潔で読みやすいため、初期値が決まっている場合によく使われます。

ただし、宣言と代入を分ける場合は、次の書き方はできません。

C#
int[] numbers;

// コンパイルエラー
// numbers = { 10, 20, 30 };

宣言後に代入する場合は、newを使用します。

C#
int[] numbers;
numbers = new int[] { 10, 20, 30 };

2-5. varを使って配列を初期化する

varを使うと、右辺の値から配列の型を推論できます。

C#
var numbers = new[] { 10, 20, 30 };
var names = new[] { "佐藤", "鈴木", "高橋" };

この場合、numbersint[]namesstring[]として扱われます。

varを使う場合でも、配列の初期値は必要です。

C#
// 型を推論できないためコンパイルエラー
// var numbers;

また、配列内の値から共通の型を判断できなければコンパイルエラーになります。

C#
// 共通の配列型を推論できない場合がある
// var values = new[] { 1, "C#" };

型を明確にしたい場合は、通常の配列宣言を使います。

C#
object[] values = { 1, "C#", true };

2-6. 配列のデフォルト値を確認する

要素数だけを指定して配列を作成すると、各要素にはデフォルト値が設定されます。

代表的な型のデフォルト値は次のとおりです。

データ型デフォルト値
int0
double0.0
boolfalse
char'\0'
参照型null
int?などのNullable型null

次のコードで確認できます。

C#
int[] numbers = new int[2];
bool[] flags = new bool[2];
string?[] names = new string?[2];

Console.WriteLine(numbers[0]); // 0
Console.WriteLine(flags[0]); // False
Console.WriteLine(names[0] is null); // True

2-7. 空の配列を作成する

要素数が0の配列は、Array.Empty<T>()で作成できます。

C#
int[] numbers = Array.Empty<int>();
string[] names = Array.Empty<string>();

要素数は0です。

C#
Console.WriteLine(numbers.Length); // 0

次のように記述することもできます。

C#
int[] numbers = new int[0];

ただし、空の配列を表現する場合は、意図がわかりやすく、空配列のインスタンスを再利用できるArray.Empty<T>()が一般的です。

3. 配列の要素を取得・変更する方法

3-1. インデックスを指定して要素を取得する

配列の要素を取得するときは、変数名の後ろにインデックスを指定します。

C#
string[] fruits = { "りんご", "みかん", "ぶどう" };

string first = fruits[0];
string second = fruits[1];

Console.WriteLine(first); // りんご
Console.WriteLine(second); // みかん

インデックスは0から始まるため、先頭要素は[0]です。

3-2. インデックスを指定して要素を書き換える

配列の要素は、インデックスを指定して新しい値を代入できます。

C#
int[] numbers = { 10, 20, 30 };

numbers[1] = 100;

Console.WriteLine(numbers[1]); // 100

配列の型と異なる値は代入できません。

C#
int[] numbers = { 10, 20, 30 };

// コンパイルエラー
// numbers[0] = "C#";

3-3. Lengthプロパティで要素数を取得する

配列の要素数は、Lengthプロパティで取得できます。

C#
string[] names = { "佐藤", "鈴木", "高橋" };

Console.WriteLine(names.Length); // 3

Lengthは要素数を表すため、最後のインデックスとは異なります。

要素数が3の場合、最後のインデックスは2です。

C#
int lastIndex = names.Length - 1;

Console.WriteLine(names[lastIndex]); // 高橋

3-4. ^演算子で末尾から要素を指定する

C#では、^演算子を使って配列の末尾から要素を指定できます。

C#
int[] numbers = { 10, 20, 30, 40 };

Console.WriteLine(numbers[^1]); // 40
Console.WriteLine(numbers[^2]); // 30

^1は末尾から1番目、つまり最後の要素です。^2は末尾から2番目を表します。

^0は配列の末尾より1つ後ろの位置を表すため、要素の取得には使えません。

C#
// IndexOutOfRangeException
// Console.WriteLine(numbers[^0]);

3-5. 範囲演算子で配列の一部を取得する

範囲演算子..を使うと、配列の一部を新しい配列として取得できます。

C#
int[] numbers = { 10, 20, 30, 40, 50 };

int[] selected = numbers[1..4];

Console.WriteLine(string.Join(", ", selected)); // 20, 30, 40

開始位置は範囲に含まれ、終了位置は含まれません。1..4では、インデックス1、2、3の要素が取得されます。

開始位置や終了位置は省略できます。

C#
int[] firstThree = numbers[..3];
int[] fromThird = numbers[2..];
int[] withoutLast = numbers[..^1];

Console.WriteLine(string.Join(", ", firstThree)); // 10, 20, 30
Console.WriteLine(string.Join(", ", fromThird)); // 30, 40, 50
Console.WriteLine(string.Join(", ", withoutLast)); // 10, 20, 30, 40

配列に範囲演算子を使うと、新しい配列が作成される点には注意が必要です。

3-6. IndexOutOfRangeExceptionが発生する原因と対処法

存在しないインデックスへアクセスすると、IndexOutOfRangeExceptionが発生します。

C#
int[] numbers = { 10, 20, 30 };

// 使用可能なインデックスは0~2
// Console.WriteLine(numbers[3]);

よくある原因は、ループ条件で<=を使うことです。

C#
// 誤り
for (int i = 0; i <= numbers.Length; i++)
{
Console.WriteLine(numbers[i]);
}

inumbers.Lengthと同じ値になると、範囲外へアクセスします。条件には<を使います。

C#
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine(numbers[i]);
}

外部から受け取ったインデックスを使う場合は、範囲を確認してからアクセスします。

C#
int index = 2;

if (index >= 0 && index < numbers.Length)
{
Console.WriteLine(numbers[index]);
}

4. 配列をループ処理する方法

4-1. for文で配列の要素を順番に処理する

for文を使うと、インデックスを指定しながら配列を処理できます。

C#
int[] numbers = { 10, 20, 30 };

for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine(numbers[i]);
}

インデックスも一緒に表示できます。

C#
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine($"インデックス{i}: {numbers[i]}");
}

要素を書き換える場合にもfor文が適しています。

C#
for (int i = 0; i < numbers.Length; i++)
{
numbers[i] *= 2;
}

4-2. foreach文で配列の全要素を処理する

インデックスが不要な場合は、foreach文を使うと簡潔に記述できます。

C#
string[] names = { "佐藤", "鈴木", "高橋" };

foreach (string name in names)
{
Console.WriteLine(name);
}

型をvarにすることもできます。

C#
foreach (var name in names)
{
Console.WriteLine(name);
}

通常のforeach文の反復変数に別の値を代入して、配列要素を直接書き換えることはできません。

C#
foreach (int number in numbers)
{
// コンパイルエラー
// number = 0;
}

要素を書き換えたい場合は、for文を使います。

4-3. while文で配列を繰り返し処理する

while文でも配列を処理できます。

C#
int[] numbers = { 10, 20, 30 };

int i = 0;

while (i < numbers.Length)
{
Console.WriteLine(numbers[i]);
i++;
}

while文では、インデックスの初期化と更新を自分で記述します。更新処理を忘れると無限ループになるため注意が必要です。

4-4. インデックスが必要な場合のループ方法

要素番号を使う処理では、for文が基本です。

C#
string[] products = { "パソコン", "マウス", "キーボード" };

for (int i = 0; i < products.Length; i++)
{
Console.WriteLine($"{i + 1}. {products[i]}");
}

実行結果は次のようになります。

1. パソコン
2. マウス
3. キーボード

LINQのSelectを使い、値とインデックスを同時に取得することもできます。

C#
foreach (var item in products.Select((value, index) => new { value, index }))
{
Console.WriteLine($"{item.index}: {item.value}");
}

ただし、単純な配列処理ではfor文のほうがわかりやすい場合が多いでしょう。

4-5. 配列を逆順にループする方法

配列の末尾から先頭へ処理する場合は、インデックスを減らします。

C#
int[] numbers = { 10, 20, 30, 40 };

for (int i = numbers.Length - 1; i >= 0; i--)
{
Console.WriteLine(numbers[i]);
}

実行結果は次のとおりです。

40
30
20
10

LINQのReverseを使う方法もあります。

C#
foreach (int number in numbers.Reverse())
{
Console.WriteLine(number);
}

Array.Reverseとは異なり、LINQのReverseは元の配列の並び順を変更しません。

4-6. 条件に一致する要素だけを処理する方法

ループ内でif文を使うと、条件に一致する要素だけを処理できます。

C#
int[] numbers = { 1, 2, 3, 4, 5, 6 };

foreach (int number in numbers)
{
if (number % 2 == 0)
{
Console.WriteLine(number);
}
}

LINQのWhereを使う方法もあります。

C#
foreach (int number in numbers.Where(x => x % 2 == 0))
{
Console.WriteLine(number);
}

Whereを使う場合は、通常、ファイルの先頭に次の記述が必要です。

C#
using System.Linq;

5. 配列に要素を追加・挿入・削除する方法

5-1. C#の配列は作成後に要素数を変更できない

C#の配列は、作成した時点で要素数が固定されます。

C#
int[] numbers = new int[3];

この配列には3個の要素を格納できますが、4番目の要素を直接追加することはできません。

C#
// IndexOutOfRangeException
// numbers[3] = 40;

配列に要素を追加するには、より大きな配列を作成して既存の要素をコピーする必要があります。

5-2. Array.Resizeで配列の要素数を増やす

Array.Resizeを使うと、配列の要素数を変更したように見える処理を簡単に記述できます。

C#
int[] numbers = { 10, 20, 30 };

Array.Resize(ref numbers, 4);
numbers[3] = 40;

Console.WriteLine(string.Join(", ", numbers));
// 10, 20, 30, 40

実際には、元の配列そのものが拡張されるわけではありません。新しいサイズの配列が作成され、元の要素がコピーされたうえで、変数の参照先が置き換えられます。

配列を小さくすることもできます。

C#
Array.Resize(ref numbers, 2);

Console.WriteLine(string.Join(", ", numbers));
// 10, 20

小さくした場合、範囲から外れた要素は失われます。

5-3. 新しい配列を作成して要素を追加する

新しい配列を作成し、既存の要素をコピーしてから末尾へ値を追加できます。

C#
int[] source = { 10, 20, 30 };
int[] result = new int[source.Length + 1];

Array.Copy(source, result, source.Length);
result[^1] = 40;

Console.WriteLine(string.Join(", ", result));
// 10, 20, 30, 40

LINQのAppendを使う方法もあります。

C#
int[] result = source.Append(40).ToArray();

Appendは元の配列を変更せず、新しい配列を作成します。

5-4. 配列の途中に要素を挿入する

配列の途中へ要素を挿入する場合は、挿入位置の前後を新しい配列へコピーします。

C#
int[] source = { 10, 20, 40 };
int insertIndex = 2;
int insertValue = 30;

int[] result = new int[source.Length + 1];

Array.Copy(source, 0, result, 0, insertIndex);
result[insertIndex] = insertValue;
Array.Copy(
source,
insertIndex,
result,
insertIndex + 1,
source.Length - insertIndex
);

Console.WriteLine(string.Join(", ", result));
// 10, 20, 30, 40

追加や挿入を頻繁に行う場合は、配列ではなくList<T>を使うほうが簡単です。

C#
List<int> numbers = new List<int> { 10, 20, 40 };
numbers.Insert(2, 30);

5-5. 配列から指定した要素を削除する

配列の要素数は固定されているため、要素を直接削除することはできません。対象を除外した新しい配列を作成します。

値が20ではない要素だけを残す例は次のとおりです。

C#
int[] numbers = { 10, 20, 30, 20 };

int[] result = numbers
.Where(x => x != 20)
.ToArray();

Console.WriteLine(string.Join(", ", result));
// 10, 30

指定したインデックスの要素だけを削除する場合は、次のように記述できます。

C#
int[] numbers = { 10, 20, 30, 40 };
int removeIndex = 1;

int[] result = numbers
.Where((value, index) => index != removeIndex)
.ToArray();

Console.WriteLine(string.Join(", ", result));
// 10, 30, 40

5-6. 要素の追加・削除が多い場合はList<T>を使う

要素の追加・削除を繰り返す処理では、毎回新しい配列を作成するとコピー処理が何度も発生します。

このような場合はList<T>が適しています。

C#
List<string> names = new List<string>();

names.Add("佐藤");
names.Add("鈴木");
names.Insert(1, "高橋");
names.Remove("佐藤");

List<T>には、追加・挿入・削除のためのメソッドが用意されています。

C#
names.Add("田中");
names.Insert(0, "山田");
names.Remove("鈴木");
names.RemoveAt(0);

最終的な要素数が決まった段階で、配列へ変換することもできます。

C#
string[] nameArray = names.ToArray();

5-7. 配列とList<T>を相互変換する

配列からList<T>へ変換するには、ToListを使用します。

C#
int[] numbers = { 10, 20, 30 };

List<int> numberList = numbers.ToList();

コンストラクターを使う方法もあります。

C#
List<int> numberList = new List<int>(numbers);

List<T>から配列へ変換するには、ToArrayを使用します。

C#
List<int> numberList = new List<int> { 10, 20, 30 };

int[] numbers = numberList.ToArray();

どちらの変換でも新しいコレクションが作成されるため、変換後の要素を入れ替えても、元の配列やリストの要素には直接影響しません。ただし、要素自体が参照型の場合は、各要素が参照するオブジェクトを共有することがあります。

6. 配列を検索・並べ替え・加工する方法

6-1. Array.IndexOfで要素を検索する

Array.IndexOfを使うと、指定した値が格納されているインデックスを検索できます。

C#
string[] fruits = { "りんご", "みかん", "ぶどう" };

int index = Array.IndexOf(fruits, "みかん");

Console.WriteLine(index); // 1

対象が見つからなかった場合は-1が返されます。

C#
int index = Array.IndexOf(fruits, "バナナ");

if (index == -1)
{
Console.WriteLine("見つかりませんでした");
}

同じ値が複数ある場合は、最初に見つかった要素のインデックスが返されます。

6-2. Array.Findで条件に一致する要素を取得する

Array.Findは、条件に一致する最初の要素を取得します。

C#
int[] numbers = { 3, 8, 12, 5 };

int result = Array.Find(numbers, x => x >= 10);

Console.WriteLine(result); // 12

条件に一致する要素がない場合は、その型のデフォルト値が返されます。intの場合は0です。

そのため、0が有効なデータとして含まれる場合は、見つからなかった状態との区別に注意が必要です。存在確認だけが目的なら、Array.Existsを使用できます。

6-3. Array.FindAllで条件に一致する要素をすべて取得する

Array.FindAllは、条件に一致するすべての要素を新しい配列として返します。

C#
int[] numbers = { 1, 2, 3, 4, 5, 6 };

int[] evenNumbers = Array.FindAll(
numbers,
x => x % 2 == 0
);

Console.WriteLine(string.Join(", ", evenNumbers));
// 2, 4, 6

条件に一致する要素がない場合は、要素数0の配列が返されます。

6-4. Array.Existsで要素の存在を確認する

条件に一致する要素が1つでも存在するか確認するには、Array.Existsを使います。

C#
int[] numbers = { 10, 20, 30 };

bool exists = Array.Exists(numbers, x => x >= 20);

Console.WriteLine(exists); // True

特定の値が含まれているか確認する場合は、次のように記述できます。

C#
bool contains20 = Array.Exists(numbers, x => x == 20);

LINQのContainsAnyを使う方法もあります。

C#
bool contains20 = numbers.Contains(20);
bool hasLargeValue = numbers.Any(x => x >= 20);

6-5. Array.Sortで配列を昇順に並べ替える

Array.Sortを使うと、配列を昇順に並べ替えられます。

C#
int[] numbers = { 30, 10, 20 };

Array.Sort(numbers);

Console.WriteLine(string.Join(", ", numbers));
// 10, 20, 30

文字列配列も並べ替えられます。

C#
string[] names = { "Suzuki", "Sato", "Takahashi" };

Array.Sort(names);

Array.Sortは元の配列を直接変更します。元の並び順を残したい場合は、先にコピーを作成します。

C#
int[] sorted = numbers.ToArray();
Array.Sort(sorted);

LINQのOrderByを使用する方法もあります。

C#
int[] sorted = numbers.OrderBy(x => x).ToArray();

6-6. Array.Reverseで配列を逆順にする

Array.Reverseは、配列の要素順を反転します。

C#
int[] numbers = { 10, 20, 30 };

Array.Reverse(numbers);

Console.WriteLine(string.Join(", ", numbers));
// 30, 20, 10

降順に並べ替える場合は、昇順に並べた後で反転できます。

C#
Array.Sort(numbers);
Array.Reverse(numbers);

LINQを使用する場合は、次のように記述できます。

C#
int[] descending = numbers
.OrderByDescending(x => x)
.ToArray();

6-7. Array.Copyで配列をコピーする

Array.Copyを使うと、配列の要素を別の配列へコピーできます。

C#
int[] source = { 10, 20, 30 };
int[] destination = new int[source.Length];

Array.Copy(source, destination, source.Length);

Console.WriteLine(string.Join(", ", destination));
// 10, 20, 30

コピー元とコピー先の開始位置を指定することもできます。

C#
int[] source = { 10, 20, 30, 40, 50 };
int[] destination = new int[3];

Array.Copy(source, 1, destination, 0, 3);

Console.WriteLine(string.Join(", ", destination));
// 20, 30, 40

配列全体の簡単なコピーには、CloneToArrayも利用できます。

C#
int[] copy1 = (int[])source.Clone();
int[] copy2 = source.ToArray();

6-8. LINQで配列を絞り込み・変換する

LINQを使うと、配列の絞り込み、並べ替え、変換などを簡潔に記述できます。

C#
int[] numbers = { 1, 2, 3, 4, 5, 6 };

int[] result = numbers
.Where(x => x % 2 == 0)
.Select(x => x * 10)
.ToArray();

Console.WriteLine(string.Join(", ", result));
// 20, 40, 60

主なLINQメソッドは次のとおりです。

  • Where:条件に一致する要素を絞り込む

  • Select:各要素を別の値へ変換する

  • OrderBy:昇順に並べ替える

  • OrderByDescending:降順に並べ替える

  • Distinct:重複を取り除く

  • FirstOrDefault:最初の要素を取得する

  • Any:条件に一致する要素があるか確認する

  • Sum:合計値を求める

  • Average:平均値を求める

  • Max:最大値を求める

  • Min:最小値を求める

LINQの処理結果はIEnumerable<T>になることが多いため、配列として受け取りたい場合は最後にToArray()を使用します。

6-9. 文字列と配列をSplit・Joinで相互変換する

Splitを使うと、区切り文字を基準に文字列を分割して配列へ変換できます。

C#
string text = "C#,Java,Python";

string[] languages = text.Split(',');

foreach (string language in languages)
{
Console.WriteLine(language);
}

空白を取り除きたい場合は、Trimと組み合わせます。

C#
string text = "C#, Java, Python";

string[] languages = text
.Split(',')
.Select(x => x.Trim())
.ToArray();

配列を1つの文字列へ変換する場合は、string.Joinを使います。

C#
string[] languages = { "C#", "Java", "Python" };

string result = string.Join(", ", languages);

Console.WriteLine(result);
// C#, Java, Python

7. 多次元配列とジャグ配列の使い方

7-1. 二次元配列を宣言・初期化する

二次元配列は、行と列を持つ表形式のデータを扱う配列です。

C#
int[,] matrix = new int[2, 3];

この配列は、2行3列の要素を持ちます。

初期値を指定する場合は、次のように記述します。

C#
int[,] matrix =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};

各行の列数は同じでなければなりません。

7-2. 二次元配列の要素を取得・変更する

二次元配列では、行と列のインデックスを指定します。

C#
int[,] matrix =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};

Console.WriteLine(matrix[0, 0]); // 1
Console.WriteLine(matrix[1, 2]); // 6

値の変更も同じ形式です。

C#
matrix[0, 1] = 100;

Console.WriteLine(matrix[0, 1]); // 100

行と列のインデックスは、どちらも0から始まります。

7-3. 二重ループで二次元配列を処理する

二次元配列の全要素を処理する場合は、二重のfor文を使用します。

C#
int[,] matrix =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};

for (int row = 0; row < matrix.GetLength(0); row++)
{
for (int column = 0; column < matrix.GetLength(1); column++)
{
Console.Write($"{matrix[row, column]} ");
}

Console.WriteLine();
}

実行結果は次のとおりです。

1 2 3
4 5 6

要素の位置が不要なら、foreach文でも全要素を処理できます。

C#
foreach (int value in matrix)
{
Console.WriteLine(value);
}

ただし、foreach文だけでは行と列のインデックスを直接取得できません。

7-4. GetLengthで各次元の要素数を取得する

二次元配列の各次元の長さは、GetLengthで取得します。

C#
int[,] matrix = new int[2, 3];

Console.WriteLine(matrix.GetLength(0)); // 2
Console.WriteLine(matrix.GetLength(1)); // 3

GetLength(0)は1つ目の次元である行数、GetLength(1)は2つ目の次元である列数を返します。

Lengthは、すべての要素の合計数を返します。

C#
Console.WriteLine(matrix.Length); // 6

次元数はRankプロパティで取得できます。

C#
Console.WriteLine(matrix.Rank); // 2

7-5. 三次元以上の多次元配列を作成する

三次元配列は、カンマを2つ記述して宣言します。

C#
int[,,] data = new int[2, 3, 4];

要素へアクセスするには、3つのインデックスを指定します。

C#
data[0, 1, 2] = 100;

Console.WriteLine(data[0, 1, 2]); // 100

4次元以上の配列も作成できます。

C#
int[,,,] values = new int[2, 2, 2, 2];

ただし、次元数が増えるほどコードが複雑になります。データの意味に応じて、専用クラスや別のデータ構造も検討しましょう。

7-6. ジャグ配列を宣言・初期化する

ジャグ配列とは、配列を要素として持つ配列です。「配列の配列」とも呼ばれます。

C#
int[][] numbers = new int[3][];

内側の配列をそれぞれ作成します。

C#
numbers[0] = new int[] { 1, 2 };
numbers[1] = new int[] { 3, 4, 5 };
numbers[2] = new int[] { 6 };

宣言と初期化をまとめることもできます。

C#
int[][] numbers =
{
new int[] { 1, 2 },
new int[] { 3, 4, 5 },
new int[] { 6 }
};

内側の配列は、それぞれ異なる要素数を持てます。

7-7. 多次元配列とジャグ配列の違い

多次元配列は、各行の列数が同じ長方形の構造です。

C#
int[,] matrix = new int[3, 4];

ジャグ配列は、各行に相当する配列の要素数を変えられます。

C#
int[][] jagged =
{
new int[] { 1, 2 },
new int[] { 3, 4, 5 },
new int[] { 6 }
};

アクセス方法も異なります。

C#
int value1 = matrix[1, 2];
int value2 = jagged[1][2];

多次元配列ではインデックスをカンマで区切り、ジャグ配列では角括弧を続けて記述します。

7-8. 多次元配列とジャグ配列の使い分け

行数と列数が固定された表や盤面には、多次元配列が適しています。

C#
int[,] board = new int[8, 8];

行ごとに要素数が異なるデータには、ジャグ配列が適しています。

C#
int[][] monthlySales =
{
new int[] { 100, 120 },
new int[] { 90, 110, 130 },
new int[] { 80 }
};

すべての行が同じ長さで、行列として扱いたい場合は多次元配列を選びます。行ごとにデータ量が異なる場合や、内側の配列を個別に扱いたい場合はジャグ配列を選びましょう。

8. 配列をメソッドの引数・戻り値として使う方法

8-1. 配列をメソッドの引数として渡す

メソッドの引数に配列型を指定できます。

C#
static void PrintNumbers(int[] numbers)
{
foreach (int number in numbers)
{
Console.WriteLine(number);
}
}

呼び出し側では、配列をそのまま渡します。

C#
int[] values = { 10, 20, 30 };

PrintNumbers(values);

配列リテラルに相当する値を直接渡すこともできます。

C#
PrintNumbers(new int[] { 10, 20, 30 });

8-2. メソッドから配列を返す

戻り値の型に配列型を指定すると、メソッドから配列を返せます。

C#
static int[] CreateNumbers()
{
return new int[] { 10, 20, 30 };
}

呼び出し側では、戻り値を配列変数へ代入します。

C#
int[] numbers = CreateNumbers();

Console.WriteLine(string.Join(", ", numbers));

条件に一致する要素を配列として返すメソッドも作成できます。

C#
static int[] GetEvenNumbers(int[] numbers)
{
return numbers
.Where(x => x % 2 == 0)
.ToArray();
}

8-3. paramsを使って可変長引数を受け取る

paramsを使うと、任意の個数の引数を配列として受け取れます。

C#
static int Sum(params int[] numbers)
{
int total = 0;

foreach (int number in numbers)
{
total += number;
}

return total;
}

呼び出し側は、配列を明示的に作成せずに複数の値を渡せます。

C#
Console.WriteLine(Sum(10, 20));         // 30
Console.WriteLine(Sum(10, 20, 30)); // 60
Console.WriteLine(Sum()); // 0

既存の配列を渡すこともできます。

C#
int[] numbers = { 10, 20, 30 };

Console.WriteLine(Sum(numbers)); // 60

paramsを付けた引数は、通常、引数リストの最後に1つだけ指定します。

8-4. 配列が参照型であることによる注意点

C#の配列は参照型です。メソッドへ配列を渡すと、配列の参照が値渡しされます。

そのため、メソッド内で配列要素を書き換えると、呼び出し元の配列にも反映されます。

C#
static void ChangeFirst(int[] numbers)
{
numbers[0] = 999;
}

int[] values = { 10, 20, 30 };

ChangeFirst(values);

Console.WriteLine(values[0]); // 999

一方、メソッド内で引数変数に別の配列を代入しただけでは、通常、呼び出し元の変数は変更されません。

C#
static void ReplaceArray(int[] numbers)
{
numbers = new int[] { 100, 200, 300 };
}

int[] values = { 10, 20, 30 };

ReplaceArray(values);

Console.WriteLine(values[0]); // 10

呼び出し元の参照先自体を変更するには、refなどが必要ですが、意図しない変更を避けるため慎重に使用します。

8-5. 元の配列を変更せずに処理する方法

元の配列を変更したくない場合は、配列をコピーしてから処理します。

C#
static int[] SortWithoutChangingOriginal(int[] source)
{
int[] copy = source.ToArray();

Array.Sort(copy);

return copy;
}

使用例は次のとおりです。

C#
int[] numbers = { 30, 10, 20 };
int[] sorted = SortWithoutChangingOriginal(numbers);

Console.WriteLine(string.Join(", ", numbers));
// 30, 10, 20

Console.WriteLine(string.Join(", ", sorted));
// 10, 20, 30

値型の配列であれば、各要素の値がコピーされます。

参照型の配列では、ToArrayArray.Copyでコピーされるのは各オブジェクトへの参照です。オブジェクト自体まで複製する必要がある場合は、各要素を個別にコピーする処理が必要です。

9. C#の配列でよくあるエラーと注意点

9-1. 配列がnullの状態でアクセスする

配列変数がnullの状態で、要素やLengthへアクセスするとNullReferenceExceptionが発生します。

C#
int[]? numbers = null;

// NullReferenceException
// Console.WriteLine(numbers.Length);

アクセス前にnullを確認します。

C#
if (numbers is not null)
{
Console.WriteLine(numbers.Length);
}

null条件演算子を使うこともできます。

C#
int? length = numbers?.Length;

配列を返すメソッドでは、処理結果がない場合にnullを返すより、空の配列を返すほうが呼び出し側で扱いやすい場合があります。

C#
return Array.Empty<int>();

9-2. 配列の範囲外のインデックスを指定する

要素数が3の配列で使用できるインデックスは、0から2までです。

C#
int[] numbers = { 10, 20, 30 };

// IndexOutOfRangeException
// int value = numbers[3];

インデックスを変数で指定する場合は、次の条件を確認します。

C#
if (index >= 0 && index < numbers.Length)
{
Console.WriteLine(numbers[index]);
}

ループでは、終了条件をi < numbers.Lengthにします。

9-3. Lengthと最後のインデックスを混同する

Lengthは要素数であり、最後のインデックスではありません。

C#
int[] numbers = { 10, 20, 30 };

Console.WriteLine(numbers.Length); // 3

最後のインデックスはLength - 1です。

C#
Console.WriteLine(numbers[numbers.Length - 1]); // 30

末尾インデックス演算子を使うと、より簡潔に記述できます。

C#
Console.WriteLine(numbers[^1]); // 30

ただし、空の配列に対して最後の要素を取得しようとすると例外が発生します。

C#
int[] empty = Array.Empty<int>();

// 例外
// Console.WriteLine(empty[^1]);

事前に要素数を確認します。

C#
if (empty.Length > 0)
{
Console.WriteLine(empty[^1]);
}

9-4. 配列の代入とコピーを混同する

配列変数を別の変数へ代入しても、配列の内容はコピーされません。2つの変数が同じ配列を参照します。

C#
int[] array1 = { 10, 20, 30 };
int[] array2 = array1;

array2[0] = 999;

Console.WriteLine(array1[0]); // 999

独立した配列を作りたい場合は、内容をコピーします。

C#
int[] array1 = { 10, 20, 30 };
int[] array2 = array1.ToArray();

array2[0] = 999;

Console.WriteLine(array1[0]); // 10
Console.WriteLine(array2[0]); // 999

9-5. 異なる型の値を配列へ代入する

配列には、宣言した型に代入できる値だけを格納できます。

C#
int[] numbers = new int[3];

numbers[0] = 10;

// コンパイルエラー
// numbers[1] = "20";

文字列を整数へ変換してから代入する必要があります。

C#
numbers[1] = int.Parse("20");

異なるデータ型を1つの配列へ格納するには、object[]を使用できます。

C#
object[] values = { 10, "C#", true };

ただし、取り出した値を利用する際に型の確認やキャストが必要になるため、通常は同じ意味を持つデータを同じ型で管理します。

C#
foreach (object value in values)
{
Console.WriteLine($"{value}:{value.GetType().Name}");
}

9-6. 配列のサイズ変更を繰り返して処理が遅くなる

Array.Resizeを使うたびに、新しい配列の作成と要素のコピーが発生します。

C#
int[] numbers = Array.Empty<int>();

for (int i = 0; i < 10000; i++)
{
Array.Resize(ref numbers, numbers.Length + 1);
numbers[^1] = i;
}

このような処理は、要素数が増えるほど非効率になります。

追加を繰り返す場合は、List<T>を使用します。

C#
List<int> numbers = new List<int>();

for (int i = 0; i < 10000; i++)
{
numbers.Add(i);
}

int[] result = numbers.ToArray();

最終的な要素数が事前にわかっている場合は、最初から必要なサイズの配列を作る方法が効率的です。

C#
int[] numbers = new int[10000];

for (int i = 0; i < numbers.Length; i++)
{
numbers[i] = i;
}

9-7. 配列・List<T>・Span<T>の選び方を誤る

配列、List<T>Span<T>には、それぞれ適した用途があります。

配列は、要素数が固定されているデータに向いています。

C#
int[] rgb = { 255, 128, 0 };

List<T>は、要素を追加・削除するデータに向いています。

C#
List<string> tasks = new List<string>();
tasks.Add("資料作成");
tasks.Remove("資料作成");

Span<T>は、配列や連続したメモリ領域の一部を、余分な配列を作らずに一時的に処理したい場合に向いています。

C#
int[] numbers = { 10, 20, 30, 40, 50 };

Span<int> middle = numbers.AsSpan(1, 3);

middle[0] = 999;

Console.WriteLine(numbers[1]); // 999

Span<T>は高性能な処理に役立ちますが、利用できる場所や保持方法に制約があります。一般的なアプリケーションでは、まず配列とList<T>を適切に使い分け、必要な場面でSpan<T>を検討するとよいでしょう。

10. C#配列の実践的なサンプル

10-1. 数値配列の合計値と平均値を求める

ループを使って合計値を求める例です。

C#
int[] scores = { 80, 90, 70, 100 };

int total = 0;

foreach (int score in scores)
{
total += score;
}

double average = scores.Length > 0
? (double)total / scores.Length
: 0;

Console.WriteLine($"合計:{total}");
Console.WriteLine($"平均:{average}");

整数同士の割り算では小数部分が切り捨てられるため、doubleへキャストしています。

LINQを使うと簡潔に記述できます。

C#
int total = scores.Sum();
double average = scores.Length > 0
? scores.Average()
: 0;

空の配列に対してAverageを呼び出すと例外が発生するため、要素数を確認します。

10-2. 配列から最大値・最小値を取得する

ループを使う場合は、先頭要素を初期値として比較します。

C#
int[] numbers = { 30, 10, 50, 20 };

if (numbers.Length > 0)
{
int max = numbers[0];
int min = numbers[0];

foreach (int number in numbers)
{
if (number > max)
{
max = number;
}

if (number < min)
{
min = number;
}
}

Console.WriteLine($"最大値:{max}");
Console.WriteLine($"最小値:{min}");
}

LINQを使う場合は、MaxMinを使用します。

C#
if (numbers.Length > 0)
{
int max = numbers.Max();
int min = numbers.Min();

Console.WriteLine($"最大値:{max}");
Console.WriteLine($"最小値:{min}");
}

10-3. 重複した要素を取り除く

LINQのDistinctを使うと、重複を取り除いた新しい配列を作成できます。

C#
int[] numbers = { 10, 20, 10, 30, 20 };

int[] uniqueNumbers = numbers
.Distinct()
.ToArray();

Console.WriteLine(string.Join(", ", uniqueNumbers));
// 10, 20, 30

文字列配列にも利用できます。

C#
string[] names = { "佐藤", "鈴木", "佐藤" };

string[] uniqueNames = names
.Distinct()
.ToArray();

大文字と小文字を区別せずに重複を取り除く場合は、比較方法を指定できます。

C#
string[] values = { "C#", "c#", "Java" };

string[] uniqueValues = values
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();

10-4. 条件に一致する要素を別の配列へ抽出する

Whereを使うと、条件に一致する要素を抽出できます。

C#
int[] scores = { 45, 80, 60, 95, 30 };

int[] passedScores = scores
.Where(score => score >= 60)
.ToArray();

Console.WriteLine(string.Join(", ", passedScores));
// 80, 60, 95

Array.FindAllでも同様の処理ができます。

C#
int[] passedScores = Array.FindAll(
scores,
score => score >= 60
);

元の配列は変更されません。

10-5. 配列内の要素を置換する

配列の要素を直接置換する場合は、for文を使います。

C#
string[] statuses = { "未処理", "完了", "未処理" };

for (int i = 0; i < statuses.Length; i++)
{
if (statuses[i] == "未処理")
{
statuses[i] = "処理中";
}
}

Console.WriteLine(string.Join(", ", statuses));
// 処理中, 完了, 処理中

元の配列を変更せずに新しい配列を作る場合は、Selectを使います。

C#
string[] result = statuses
.Select(x => x == "未処理" ? "処理中" : x)
.ToArray();

10-6. CSV形式の文字列を配列として処理する

カンマ区切りの文字列を配列へ変換します。

C#
string csv = "100,200,300,400";

string[] values = csv.Split(',');

整数配列へ変換する場合は、Selectint.Parseを使えます。

C#
int[] numbers = csv
.Split(',')
.Select(x => int.Parse(x))
.ToArray();

入力値が不正な可能性がある場合は、int.TryParseを使って検証します。

C#
string csv = "100,200,abc,400";

List<int> numbers = new List<int>();

foreach (string value in csv.Split(','))
{
if (int.TryParse(value.Trim(), out int number))
{
numbers.Add(number);
}
}

int[] result = numbers.ToArray();

Console.WriteLine(string.Join(", ", result));
// 100, 200, 400

実際のCSVには、引用符で囲まれたカンマ、改行、エスケープなどが含まれることがあります。複雑なCSVファイルを扱う場合は、単純なSplitではなくCSV解析用ライブラリの利用を検討します。

10-7. 二次元配列で表形式のデータを扱う

商品ごとの月別売上を二次元配列で表す例です。

C#
int[,] sales =
{
{ 100, 120, 150 },
{ 80, 90, 110 },
{ 200, 180, 220 }
};

for (int product = 0; product < sales.GetLength(0); product++)
{
int total = 0;

for (int month = 0; month < sales.GetLength(1); month++)
{
total += sales[product, month];
}

Console.WriteLine($"商品{product + 1}の合計:{total}");
}

表として表示することもできます。

C#
for (int row = 0; row < sales.GetLength(0); row++)
{
for (int column = 0; column < sales.GetLength(1); column++)
{
Console.Write($"{sales[row, column],5}");
}

Console.WriteLine();
}

データに商品名や年月などの意味を持たせたい場合は、配列だけでなくクラスやレコードを組み合わせると、より読みやすいコードになります。

11. C#の配列に関するよくある質問

11-1. 配列の要素数を後から変更できますか

作成済みの配列自体の要素数は変更できません。

Array.Resizeを使うとサイズを変更したように記述できますが、内部では新しい配列が作成され、元の要素がコピーされます。

C#
int[] numbers = { 10, 20, 30 };

Array.Resize(ref numbers, 5);

要素数の変更を頻繁に行う場合は、List<T>を使用するほうが適しています。

11-2. 配列に新しい要素を追加する最適な方法は何ですか

追加が1回だけであれば、Array.ResizeやLINQのAppendを利用できます。

C#
int[] numbers = { 10, 20, 30 };

numbers = numbers.Append(40).ToArray();

追加を何度も行う場合は、List<T>を使用します。

C#
List<int> numbers = new List<int> { 10, 20, 30 };

numbers.Add(40);

最終的に配列が必要なら、最後に変換します。

C#
int[] result = numbers.ToArray();

11-3. 配列が空かどうかを判定するにはどうすればよいですか

要素数が0かどうかをLengthで確認します。

C#
int[] numbers = Array.Empty<int>();

if (numbers.Length == 0)
{
Console.WriteLine("空の配列です");
}

配列がnullの可能性もある場合は、両方を確認します。

C#
int[]? numbers = null;

if (numbers is null || numbers.Length == 0)
{
Console.WriteLine("nullまたは空の配列です");
}

パターンマッチングを使うと、次のようにも記述できます。

C#
if (numbers is not { Length: > 0 })
{
Console.WriteLine("nullまたは空の配列です");
}

11-4. 配列から特定の要素を削除できますか

配列から要素を直接削除することはできません。対象の要素を除外した新しい配列を作成します。

C#
int[] numbers = { 10, 20, 30, 20 };

numbers = numbers
.Where(x => x != 20)
.ToArray();

頻繁に削除する場合は、List<T>を使用します。

C#
List<int> numbers = new List<int> { 10, 20, 30 };

numbers.Remove(20);

11-5. 配列とList<T>はどちらを使うべきですか

要素数が固定されている場合は配列が適しています。

C#
string[] weekdays =
{
"月", "火", "水", "木", "金", "土", "日"
};

要素の追加や削除がある場合は、List<T>が適しています。

C#
List<string> members = new List<string>();

members.Add("佐藤");
members.Remove("佐藤");

外部APIの仕様やメソッドの引数・戻り値として配列が求められる場合は、配列を使います。処理中のデータ件数が変化する場合は、まずList<T>を選ぶとよいでしょう。

11-6. 配列を高速にコピーする方法はありますか

一般的な配列コピーには、Array.Copyを利用できます。

C#
int[] source = { 10, 20, 30 };
int[] destination = new int[source.Length];

Array.Copy(source, destination, source.Length);

配列全体を同じ型の配列へコピーするだけなら、Cloneも利用できます。

C#
int[] copy = (int[])source.Clone();

一部の要素をコピーする場合は、AsSpanCopyToを使用する方法もあります。

C#
int[] source = { 10, 20, 30, 40 };
int[] destination = new int[2];

source.AsSpan(1, 2).CopyTo(destination);

Console.WriteLine(string.Join(", ", destination));
// 20, 30

どの方法が最適かは、配列の型、サイズ、コピー範囲、実行回数によって異なります。通常の用途では、読みやすいArray.CopyToArrayで十分です。

11-7. Array.Empty<T>()とnew T[0]の違いは何ですか

どちらも要素数0の配列を返します。

C#
int[] array1 = Array.Empty<int>();
int[] array2 = new int[0];

new int[0]は、実行されるたびに新しい空配列を作成する可能性があります。

Array.Empty<int>()は、型ごとに共有された空配列を返すため、不要な配列生成を避けられます。

空の配列を返したり初期値として使ったりする場合は、通常、Array.Empty<T>()が適しています。

C#
static string[] GetNames()
{
return Array.Empty<string>();
}

11-8. 配列に異なるデータ型を格納できますか

通常の配列には、宣言した型に代入できる値だけを格納します。

C#
int[] numbers = { 10, 20, 30 };
string[] names = { "佐藤", "鈴木" };

object[]を使えば、異なる型の値を格納できます。

C#
object[] values =
{
100,
"C#",
true,
DateTime.Now
};

ただし、値を取り出す際には型を確認する必要があります。

C#
foreach (object value in values)
{
if (value is int number)
{
Console.WriteLine($"整数:{number}");
}
else if (value is string text)
{
Console.WriteLine($"文字列:{text}");
}
}

異なる種類のデータをまとめて扱う必要がある場合は、object[]よりも、目的に合ったクラスやレコードを定義するほうが型安全で管理しやすくなります。

まとめ

C#の配列(array)は、同じ型の複数の値をまとめて管理するための基本的なデータ構造です。

配列は、次のように宣言・初期化できます。

C#
int[] numbers = { 10, 20, 30 };

要素には0から始まるインデックスを使ってアクセスします。

C#
Console.WriteLine(numbers[0]);
numbers[1] = 100;

要素数はLengthで取得できます。

C#
Console.WriteLine(numbers.Length);

配列の全要素を処理する場合は、for文やforeach文を使用します。

C#
foreach (int number in numbers)
{
Console.WriteLine(number);
}

C#の配列は作成後に要素数を直接変更できません。追加や削除が多い場合は、List<T>を使用するのが基本です。

検索、並べ替え、加工には、ArrayクラスのメソッドやLINQを利用できます。

C#
int[] result = numbers
.Where(x => x >= 20)
.OrderBy(x => x)
.ToArray();

要素数が固定されているデータには配列、追加・削除を繰り返すデータにはList<T>、不要なコピーを避けて連続したデータの一部を扱う処理にはSpan<T>というように、目的に応じて使い分けることが重要です。