はじめに
C#のListクラスの使い方について簡潔にまとめております。
何か発見があり次第、今後も追加して行きます。
環境
当ページでは以下の環境で動作確認をしました。
- Windows 11
- csc.exe(Windows標準搭載のC#コンパイラ)
- Visual Studio 2022でも大丈夫です。
配列とListの使い分け
配列は固定サイズ、Listは動的サイズ(後からサイズを変更できる)というのが一般的な理解だと思います。
ですがこれらの使い分けはどんな場合なのか?ってことについてはあまり記されてないかと思います。
そこで投稿者の独断と偏見でこれらの使い分けを一通り記してみますので、ぜひ参考にしてみてくださいね。
〇:おススメ
△:おススメはしない(腕に自信があるなら問題ないかと...)
×:絶対やめとけ
項目 | 配列 | List |
---|---|---|
予めサイズが仕様書で決まってる? | 〇 | △ |
後からデータの追加がある? | × | 〇 |
ハードウェアのメモリの制限がある? | 〇 | △ |
パフォーマンス(処理速度)を求める? | 〇 | △ |
機械学習やディープラーニング? | △ | 〇 |
ゲーム開発? | △ | △ |
これだけだとちょっとピンとこないので、もうちょっとだけ詳しく使いどころを書きます。
-
配列の使いどころ
- データサイズが仕様書でガチガチに決められている
- プログラムの処理速度をできるだけ上げたい
- マイコン、Arduino、Raspberry Piといった低スペックなハードウェア環境で動かさなければならない
- 【勉強として】メモリの管理を自分で考えてコーディングしたい
-
Listの使いどころ
- メモリが16GB以上ある高性能なハードウェアで動かせれる
- 後で仕様変更があっても柔軟に対応できるようにしたい
- 機械学習やディープラーニングといった、メモリを湯水のように使いまくるプログラム
- 最近のC#では
ML.net
やPandas.net
、TensorFlow.net
といった機械学習向けのライブラリが充実してきました。
ゲーム開発では、ジャンルに応じて負荷があまりにも違うので何とも言えません。 例えばシューティング系や無双系といったNPCが無尽蔵にポコポコ出てくるようなゲームではListを使用すべきですし、配列だと難しい、あるいは不可能だと思います(実際に作ったことがないので憶測で言ってます)。 ですがRPGのような「今映っているフィールド上での敵数の上限が10匹くらいまで」とか、マインクラフトのように「200匹まで自然にMobをスポーンさせる」といったようにサイズを明確に決めていれば配列の方がSwitchやスマホといった低スペックな環境でも動かせれるようにできますね。
List と ArrayList
当ページではArrayListの説明は省き、Listの説明だけにします。
C#の動的配列の型にはListとArrayListの2つがあります。
これらの違いを簡単に記すと以下の表のとおりになります。
項目 | List | ArrayList(公式:非推奨) |
---|---|---|
データの型 | すべて統一 | バラバラでもOK |
データの読み出し | 特に意識しなくてよい | 型に合わせてキャストが必要 |
図で表すとこんな感じですね。
ArrayListはPythonのリストのように型を意識せずに使用できる面、非常に便利そうですが、Microsoft公式では以下のように使うことをおススメはしていません。
ArrayListには以下のデメリットがあるようです。 * パフォーマンスの低下(重くなる) * ソート(並べ替え)の処理は保証されず、ヘンテコな並び替えになる場合もある * ArrayListに要素を追加するときにメモリ再割り当てが発生し、必要以上のメモリ容量が要求される。 公式は非推奨としていますので、当ページではArrayListの説明は省略します。
まぁ一言でいえば「性能が悪くなる」ということです。
Listの初期化
変数宣言と同時に値・データを挿入する方法(初期化)についてです。
【初期化】一次元のList
一次元Listの初期化するサンプルプログラムになります。
using System;
using System.Collections.Generic; // Listクラスを使用するのに必要
class Program {
static void Main(string[] args) {
List<int> intL = new List<int>() {0, 1, 2, 3, 4};
List<string> strL = new List<string>() {"Zero", "One", "Two", "Three", "Four"};
// for 文で各要素を取得
for (int i = 0; i < intL.Count; i++) {
Console.WriteLine("intL[{0}]: {1}", i, intL[i]);
}
Console.WriteLine("----- ----- ----- -----");
// foreach 文で各要素を取得
foreach (string str in strL) {
Console.WriteLine("strL[{0}]: {1}", strL.IndexOf(str), str);
}
}
}
コンパイルと実行結果
* csc.exeによるコンパイル
csc.exe /target:exe Program.cs
> .\Program.exe
intL[0]: 0
intL[1]: 1
intL[2]: 2
intL[3]: 3
intL[4]: 4
----- ----- ----- -----
strL[0]: Zero
strL[1]: One
strL[2]: Two
strL[3]: Three
strL[4]: Four
Pythonを知っている人であれば、Pythonの一次元リストと比較すると分かりやすいかと思います。
Pythonのリストと比べてみる
List<int> intL = new List<int>() {0, 1, 2, 3, 4};
List<string> strL = new List<string>() {"Zero", "One", "Two", "Three", "Four"};
intL = [0, 1, 2, 3, 4]
strL = ["Zero", "One", "Two", "Three", "Four"]
【初期化】二次元のList
二次元Listの初期化するサンプルプログラムになります。
using System;
using System.Collections.Generic; // Listクラスを使用するのに必要
class Program {
static void Main(string[] args) {
List<List<int>> intLL = new List<List<int>>() {
new List<int>() {1, 2, 3},
new List<int>() {4, 5, 6},
new List<int>() {7, 8, 9}
}; // ここのセミコロンを忘れずに
List<List<string>> strLL = new List<List<string>>() {
new List<string>() {"One", "Two", "Three"},
new List<string>() {"Four", "Five", "Six"},
new List<string>() {"Seven", "Eight", "Nine"}
}; // ここのセミコロンを忘れずに
// for 文で各要素を取得
for (int i = 0; i < intLL.Count; i++) {
for (int j = 0; j < intLL[i].Count; j++) {
Console.WriteLine("intLL[{0}][{1}]: {2}", i, j, intLL[i][j]);
}
}
Console.WriteLine("----- ----- ----- ----- -----");
// foreach 文で各要素を取得
foreach (var list in strLL) {
foreach (var str in list) {
Console.WriteLine("strLL[{0}][{1}]: {2}", strLL.IndexOf(list), list.IndexOf(str), str);
}
}
}
}
コンパイルと実行結果
* csc.exeによるコンパイル
csc.exe /target:exe Program.cs
> .\Program.exe
intLL[0][0]: 1
intLL[0][1]: 2
intLL[0][2]: 3
intLL[1][0]: 4
intLL[1][1]: 5
intLL[1][2]: 6
intLL[2][0]: 7
intLL[2][1]: 8
intLL[2][2]: 9
----- ----- ----- ----- -----
strLL[0][0]: One
strLL[0][1]: Two
strLL[0][2]: Three
strLL[1][0]: Four
strLL[1][1]: Five
strLL[1][2]: Six
strLL[2][0]: Seven
strLL[2][1]: Eight
strLL[2][2]: Nine
Pythonを知っている人であれば、Pythonの二次元リストと比較すると分かりやすいかと思います。
Pythonのリストと比べてみる
* C# での二次元リストの初期化
List<List<int>> intLL = new List<List<int>>() {
new List<int>() {1, 2, 3},
new List<int>() {4, 5, 6},
new List<int>() {7, 8, 9}
}; // ここのセミコロンを忘れずに
List<List<string>> strLL = new List<List<string>>() {
new List<string>() {"One", "Two", "Three"},
new List<string>() {"Four", "Five", "Six"},
new List<string>() {"Seven", "Eight", "Nine"}
}; // ここのセミコロンを忘れずに
intLL = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
strLL = [["One", "Two", "Three"],
["Four", "Five", "Six"],
["Seven", "Eight", "Nine"]]
【初期化】三次元のList
三次元Listの初期化するサンプルプログラムになります。
using System;
using System.Collections.Generic; // Listクラスを使用するのに必要
class Program {
static void Main(string[] args) {
List<List<List<int>>> intLLL = new List<List<List<int>>>() {
new List<List<int>>() {
new List<int>() {01, 02, 03},
new List<int>() {04, 05, 06},
new List<int>() {07, 08, 09}
},
new List<List<int>>() {
new List<int>() {11, 12, 13},
new List<int>() {14, 15, 16},
new List<int>() {17, 18, 19}
},
new List<List<int>>() {
new List<int>() {21, 22, 23},
new List<int>() {24, 25, 26},
new List<int>() {27, 28, 29}
}
}; // ここのセミコロンを忘れずに
List<List<List<string>>> strLLL = new List<List<List<string>>>() {
new List<List<string>>() {
new List<string>() {"One", "Two", "Three"},
new List<string>() {"Four", "Five", "Six"},
new List<string>() {"Seven", "Eight", "Nine"}
},
new List<List<string>>() {
new List<string>() {"Eleven", "Twelve", "Thirteen"},
new List<string>() {"Fourteen", "Fifteen", "Sixteen"},
new List<string>() {"Seventeen", "Eighteen", "Nineteen"}
},
new List<List<string>>() {
new List<string>() {"Twenty-One", "Twenty-Two", "Twenty-Three"},
new List<string>() {"Twenty-Four", "Twenty-Five", "Twenty-Six"},
new List<string>() {"Twenty-Seven", "Twenty-Eight", "Twenty-Nine"}
}
}; // ここのセミコロンを忘れずに
// for 文で各要素を取得
for (int i = 0; i < intLLL.Count; i++) {
for (int j = 0; j < intLLL[i].Count; j++) {
for (int k = 0; k < intLLL[i][j].Count; k++) {
Console.WriteLine("intLLL[{0}][{1}][{2}]: {3}", i, j, k, intLLL[i][j][k]);
}
}
}
Console.WriteLine("----- ----- ----- ----- -----");
// foreach 文で各要素を取得
foreach (var LL in strLLL) {
foreach (var list in LL) {
foreach (var str in list) {
Console.WriteLine("strLLL[{0}][{1}][{2}]: {3}", strLLL.IndexOf(LL), LL.IndexOf(list), list.IndexOf(str), str);
}
}
}
}
}
コンパイルと実行結果
* csc.exeによるコンパイル
csc.exe /target:exe Program.cs
> .\Program.exe
intLLL[0][0][0]: 1
intLLL[0][0][1]: 2
intLLL[0][0][2]: 3
intLLL[0][1][0]: 4
intLLL[0][1][1]: 5
intLLL[0][1][2]: 6
intLLL[0][2][0]: 7
intLLL[0][2][1]: 8
intLLL[0][2][2]: 9
intLLL[1][0][0]: 11
intLLL[1][0][1]: 12
intLLL[1][0][2]: 13
intLLL[1][1][0]: 14
intLLL[1][1][1]: 15
intLLL[1][1][2]: 16
intLLL[1][2][0]: 17
intLLL[1][2][1]: 18
intLLL[1][2][2]: 19
intLLL[2][0][0]: 21
intLLL[2][0][1]: 22
intLLL[2][0][2]: 23
intLLL[2][1][0]: 24
intLLL[2][1][1]: 25
intLLL[2][1][2]: 26
intLLL[2][2][0]: 27
intLLL[2][2][1]: 28
intLLL[2][2][2]: 29
----- ----- ----- ----- -----
strLLL[0][0][0]: One
strLLL[0][0][1]: Two
strLLL[0][0][2]: Three
strLLL[0][1][0]: Four
strLLL[0][1][1]: Five
strLLL[0][1][2]: Six
strLLL[0][2][0]: Seven
strLLL[0][2][1]: Eight
strLLL[0][2][2]: Nine
strLLL[1][0][0]: Eleven
strLLL[1][0][1]: Twelve
strLLL[1][0][2]: Thirteen
strLLL[1][1][0]: Fourteen
strLLL[1][1][1]: Fifteen
strLLL[1][1][2]: Sixteen
strLLL[1][2][0]: Seventeen
strLLL[1][2][1]: Eighteen
strLLL[1][2][2]: Nineteen
strLLL[2][0][0]: Twenty-One
strLLL[2][0][1]: Twenty-Two
strLLL[2][0][2]: Twenty-Three
strLLL[2][1][0]: Twenty-Four
strLLL[2][1][1]: Twenty-Five
strLLL[2][1][2]: Twenty-Six
strLLL[2][2][0]: Twenty-Seven
strLLL[2][2][1]: Twenty-Eight
strLLL[2][2][2]: Twenty-Nine
Pythonを知っている人であれば、Pythonの三次元リストと比較すると分かりやすいかと思います。
リストの次元が増えれば増えるほど、Pythonの方が圧倒的にコーディングしやすいですね。Pythonのリストと比べてみる
* C# での三次元リストの初期化
List<List<List<int>>> intLLL = new List<List<List<int>>>() {
new List<List<int>>() {
new List<int>() {01, 02, 03},
new List<int>() {04, 05, 06},
new List<int>() {07, 08, 09}
},
new List<List<int>>() {
new List<int>() {11, 12, 13},
new List<int>() {14, 15, 16},
new List<int>() {17, 18, 19}
},
new List<List<int>>() {
new List<int>() {21, 22, 23},
new List<int>() {24, 25, 26},
new List<int>() {27, 28, 29}
}
}; // ここのセミコロンを忘れずに
List<List<List<string>>> strLLL = new List<List<List<string>>>() {
new List<List<string>>() {
new List<string>() {"One", "Two", "Three"},
new List<string>() {"Four", "Five", "Six"},
new List<string>() {"Seven", "Eight", "Nine"}
},
new List<List<string>>() {
new List<string>() {"Eleven", "Twelve", "Thirteen"},
new List<string>() {"Fourteen", "Fifteen", "Sixteen"},
new List<string>() {"Seventeen", "Eighteen", "Nineteen"}
},
new List<List<string>>() {
new List<string>() {"Twenty-One", "Twenty-Two", "Twenty-Three"},
new List<string>() {"Twenty-Four", "Twenty-Five", "Twenty-Six"},
new List<string>() {"Twenty-Seven", "Twenty-Eight", "Twenty-Nine"}
}
}; // ここのセミコロンを忘れずに
intLLL = [[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]],
[[11, 12, 13],
[14, 15, 16],
[17, 18, 19]],
[[21, 22, 23],
[24, 25, 26],
[27, 28, 29]]]
strLLL = [[["One", "Two", "Three"],
["Four", "Five", "Six"],
["Seven", "Eight", "Nine"]],
[["Eleven", "Twelve", "Thirteen"],
["Fourteen", "Fifteen", "Sixteen"],
["Seventeen", "Eighteen", "Nineteen"]],
[["Twenty-One", "Twenty-Two", "Twenty-Three"],
["Twenty-Four", "Twenty-Five", "Twenty-Six"],
["Twenty-Seven", "Twenty-Eight", "Twenty-Nine"]]]
やはりPythonは人間に優しかった。
Listに新要素の追加
List型の変数に値・データを追加する方法についてです。
【Add】要素を追加
List変数に要素を追加するのにAdd
メソッドを使用します。
Add
メソッドの書式は次の通り。
using System.Collections.Generic;
// T: 型(intやstringなど)
// item: 追加する要素(型は T と同じじゃないとエラー)
List<T>.Add(item)
一つ注意なのですが、Add
メソッドで要素を追加すると、元々のList変数の一番最後に追加されます。
図で表すとこんな感じ。
【要素の追加】一次元のList
using System;
using System.Collections.Generic; // Listクラスを使用するのに必要
class Program {
static void Main(string[] args) {
List<string> strL = new List<string>() {"Apple", "Meron", "Orange", "Pine"};
foreach (var str in strL) {
Console.WriteLine("strL[{0}]: {1}", strL.IndexOf(str), str);
}
Console.WriteLine("----- ----- ----- ----- -----");
// 末尾に追加
strL.Add("Peach");
foreach (var str in strL) {
Console.WriteLine("strL[{0}]: {1}", strL.IndexOf(str), str);
}
}
}
コンパイルと実行結果
* csc.exeによるコンパイル
csc.exe /target:exe Program.cs
> .\Program.exe
strL[0]: Apple
strL[1]: Meron
strL[2]: Orange
strL[3]: Pine
----- ----- ----- ----- -----
strL[0]: Apple
strL[1]: Meron
strL[2]: Orange
strL[3]: Pine
strL[4]: Peach
Pythonを知っている人であれば、Pythonの一次元リストと比較すると分かりやすいかと思います。
Pythonのリストと比べてみる
* C# での一次元リストの要素追加
strL.Add("Peach");
strL.append("Peach")
【要素の追加】二次元のList
using System;
using System.Collections.Generic; // Listクラスを使用するのに必要
class Program {
static void Main(string[] args) {
List<List<string>> strLL = new List<List<string>>() {
new List<string>() {"Apple", "Meron", "Orange", "Pine"},
new List<string>() {"Japan", "America", "Chaina", "Canada"},
new List<string>() {"Math", "Chemistry", "Histry", "Japanese"},
new List<string>() {"Wine", "Beer", "Sake", "Whiskey"}
};
foreach (var strL in strLL) {
foreach (var str in strL) {
Console.Write("strLL[{0}][{1}]: {2}\t", strLL.IndexOf(strL), strL.IndexOf(str), str);
}
Console.WriteLine("");
}
Console.WriteLine("----- ----- ----- ----- -----");
// 末尾に追加(一次元Listであることに注意)
strLL.Add(new List<string>() {"Red", "Yellow", "Blue", "Green"});
foreach (var strL in strLL) {
foreach (var str in strL) {
Console.Write("strLL[{0}][{1}]: {2}\t", strLL.IndexOf(strL), strL.IndexOf(str), str);
}
Console.WriteLine("");
}
}
}
コンパイルと実行結果
* csc.exeによるコンパイル
csc.exe /target:exe Program.cs
> .\Program.exe
strLL[0][0]: Apple strLL[0][1]: Meron strLL[0][2]: Orange strLL[0][3]: Pine
strLL[1][0]: Japan strLL[1][1]: America strLL[1][2]: Chaina strLL[1][3]: Canada
strLL[2][0]: Math strLL[2][1]: Chemistry strLL[2][2]: Histry strLL[2][3]: Japanese
strLL[3][0]: Wine strLL[3][1]: Beer strLL[3][2]: Sake strLL[3][3]: Whiskey
----- ----- ----- ----- -----
strLL[0][0]: Apple strLL[0][1]: Meron strLL[0][2]: Orange strLL[0][3]: Pine
strLL[1][0]: Japan strLL[1][1]: America strLL[1][2]: Chaina strLL[1][3]: Canada
strLL[2][0]: Math strLL[2][1]: Chemistry strLL[2][2]: Histry strLL[2][3]: Japanese
strLL[3][0]: Wine strLL[3][1]: Beer strLL[3][2]: Sake strLL[3][3]: Whiskey
strLL[4][0]: Red strLL[4][1]: Yellow strLL[4][2]: Blue strLL[4][3]: Green
Pythonを知っている人であれば、Pythonの一次元リストと比較すると分かりやすいかと思います。
Pythonのリストと比べてみる
* C# での二次元リストの要素追加
strLL.Add(new List<string>() {"Red", "Yellow", "Blue", "Green"});
strLL.append(["Red", "Yellow", "Blue", "Green"])
【要素の追加】三次元のList
using System;
using System.Collections.Generic; // Listクラスを使用するのに必要
class Program {
static void Main(string[] args) {
// Listならサイズが違ってても格納できる
List<List<List<int>>> strLLL = new List<List<List<int>>>() {
new List<List<int>>() {
new List<int>() {0},
new List<int>() {0, 1},
new List<int>() {0, 1, 2}
},
new List<List<int>>() {
new List<int>() {0, 1, 2, 3}
}
};
foreach (var strLL in strLLL) {
foreach (var strL in strLL) {
foreach (var str in strL) {
Console.Write("strLLL[{0}][{1}][{2}]: {3}\t", strLLL.IndexOf(strLL), strLL.IndexOf(strL), strL.IndexOf(str), str);
}
Console.WriteLine();
}
Console.WriteLine();
}
Console.WriteLine("----- ----- ----- ----- -----");
// 末尾に追加(二次元Listであることに注意)
strLLL.Add(new List<List<int>>() {
new List<int>() {0},
new List<int>() {0, 1},
new List<int>() {0, 1, 2},
new List<int>() {0, 1, 2, 3},
new List<int>() {0, 1, 2, 3, 4},
new List<int>() {0, 1, 2, 3, 4, 5},
new List<int>() {0, 1, 2, 3, 4, 5, 6}
});
foreach (var strLL in strLLL) {
foreach (var strL in strLL) {
foreach (var str in strL) {
Console.Write("strLLL[{0}][{1}][{2}]: {3}\t", strLLL.IndexOf(strLL), strLL.IndexOf(strL), strL.IndexOf(str), str);
}
Console.WriteLine();
}
Console.WriteLine();
}
}
}
コンパイルと実行結果
* csc.exeによるコンパイル
csc.exe /target:exe Program.cs
> .\Program.exe
strLLL[0][0][0]: 0
strLLL[0][1][0]: 0 strLLL[0][1][1]: 1
strLLL[0][2][0]: 0 strLLL[0][2][1]: 1 strLLL[0][2][2]: 2
strLLL[1][0][0]: 0 strLLL[1][0][1]: 1 strLLL[1][0][2]: 2 strLLL[1][0][3]: 3
----- ----- ----- ----- -----
strLLL[0][0][0]: 0
strLLL[0][1][0]: 0 strLLL[0][1][1]: 1
strLLL[0][2][0]: 0 strLLL[0][2][1]: 1 strLLL[0][2][2]: 2
strLLL[1][0][0]: 0 strLLL[1][0][1]: 1 strLLL[1][0][2]: 2 strLLL[1][0][3]: 3
strLLL[2][0][0]: 0
strLLL[2][1][0]: 0 strLLL[2][1][1]: 1
strLLL[2][2][0]: 0 strLLL[2][2][1]: 1 strLLL[2][2][2]: 2
strLLL[2][3][0]: 0 strLLL[2][3][1]: 1 strLLL[2][3][2]: 2 strLLL[2][3][3]: 3
strLLL[2][4][0]: 0 strLLL[2][4][1]: 1 strLLL[2][4][2]: 2 strLLL[2][4][3]: 3 strLLL[2][4][4]: 4
strLLL[2][5][0]: 0 strLLL[2][5][1]: 1 strLLL[2][5][2]: 2 strLLL[2][5][3]: 3 strLLL[2][5][4]: 4 strLLL[2][5][5]: 5
strLLL[2][6][0]: 0 strLLL[2][6][1]: 1 strLLL[2][6][2]: 2 strLLL[2][6][3]: 3 strLLL[2][6][4]: 4 strLLL[2][6][5]: 5 strLLL[2][6][6]: 6
Pythonを知っている人であれば、Pythonの一次元リストと比較すると分かりやすいかと思います。
Pythonのリストと比べてみる
* C# での三次元リストの要素追加
strLLL.Add(new List<List<int>>() {
new List<int>() {0},
new List<int>() {0, 1},
new List<int>() {0, 1, 2},
new List<int>() {0, 1, 2, 3},
new List<int>() {0, 1, 2, 3, 4},
new List<int>() {0, 1, 2, 3, 4, 5},
new List<int>() {0, 1, 2, 3, 4, 5, 6}
});
strLLL.append([[0],
[0, 1],
[0, 1, 2],
[0, 1, 2, 3],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5, 6]])