C# Listの使い方を初心者向けに解説|宣言・追加・削除・検索・ループ処理まで完全理解
はじめに
C#で複数のデータを扱うときに、よく利用されるのがList<T>です。List<T>を使うと、データの追加や削除、検索、並べ替えなどを簡単に行えます。
配列も複数のデータを管理できますが、作成後に要素数を変更しにくいという特徴があります。一方、Listは必要に応じて要素数を増減できるため、登録件数が変化するユーザー一覧や商品一覧などの管理に適しています。
この記事では、C#のListについて、宣言・初期化から追加、削除、検索、ループ処理、並べ替え、型変換まで初心者向けに解説します。
1. C#のListとは?初心者向けに基本を解説
1-1. Listは複数のデータをまとめて管理するコレクション
C#のListは、複数のデータを順番に格納できるコレクションです。
たとえば、複数の名前を管理する場合は、次のようにList<string>を使用します。
C#List<string> names = new List<string>();
names.Add("田中");
names.Add("佐藤");
names.Add("鈴木");
List<string>のstringは、Listに格納するデータの型を表しています。このように型を指定する仕組みをジェネリックと呼びます。
整数を格納する場合はList<int>、独自クラスのオブジェクトを格納する場合はList<クラス名>と記述します。
1-2. Listと配列の違い
配列とListは、どちらも複数のデータを管理できます。主な違いは、要素数を変更できるかどうかです。
配列は、作成時に要素数を決めます。
C#string[] names = new string[3];
names[0] = "田中";
names[1] = "佐藤";
names[2] = "鈴木";
この配列には、基本的に3件のデータしか格納できません。要素数を増やすには、より大きな配列を作成してデータを移し替える必要があります。
一方、ListはAddメソッドやRemoveメソッドを使って要素数を変更できます。
C#List<string> names = new List<string>();
names.Add("田中");
names.Add("佐藤");
names.Remove("田中");
要素数が変化しないデータには配列、追加や削除が発生するデータにはListが適しています。
1-3. Listを使うメリットと適した場面
Listには、次のようなメリットがあります。
要素を簡単に追加・削除できる
インデックスを使って要素を取得できる
検索や並べ替えに使えるメソッドが用意されている
LINQと組み合わせて柔軟なデータ処理ができる
独自クラスのオブジェクトも管理できる
Listが適しているのは、次のような場面です。
ユーザーの一覧を管理する
商品の検索結果を格納する
入力されたデータを順番に追加する
条件に応じてデータを削除する
データを並べ替えて画面に表示する
要素数が変化する可能性がある場合は、配列よりもListを選ぶと扱いやすくなります。
1-4. Listを利用するために必要なusingディレクティブ
List<T>はSystem.Collections.Generic名前空間に含まれています。
ソースコードの先頭に、次のusingディレクティブを記述します。
C#using System.Collections.Generic;
LINQのWhereやOrderBy、FirstOrDefaultなどを使用する場合は、次のusingディレクティブも必要です。
C#using System.Linq;
基本的なコンソールアプリでは、次のように記述します。
C#using System;
using System.Collections.Generic;
using System.Linq;
プロジェクトの設定によっては暗黙的なusingが有効になっており、明示的な記述が不要な場合もあります。
2. C#でListを宣言・初期化する方法
2-1. Listの基本的な宣言方法
Listの変数は、次の形式で宣言します。
C#List<データ型> 変数名;
文字列を格納するListを宣言する場合は、次のように記述します。
C#List<string> names;
ただし、この時点では変数を宣言しただけで、Listの実体は作成されていません。Addなどのメソッドを使用するには、インスタンスを生成する必要があります。
2-2. 宣言と同時にインスタンスを生成する方法
通常は、Listの宣言とインスタンスの生成を同時に行います。
C#List<string> names = new List<string>();
最近のC#では、右辺の型を省略するターゲット型のnewも使用できます。
C#List<string> names = new();
どちらも、文字列を格納できる空のListを作成しています。
初心者のうちは、型が明確に分かる次の書き方から覚えると理解しやすいでしょう。
C#List<string> names = new List<string>();
2-3. 初期値を指定してListを作成する方法
Listは、作成時に初期値を指定できます。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
1行で記述することも可能です。
C#List<int> numbers = new List<int> { 10, 20, 30 };
初期値が決まっている場合は、Listを作成してからAddを繰り返すよりも簡潔です。
C#List<int> numbers = new List<int>();
numbers.Add(10);
numbers.Add(20);
numbers.Add(30);
上記のコードと、初期値を指定するコードは、最終的に同じ内容のListを作成します。
2-4. int・stringなど型別の宣言例
整数を格納するListは、List<int>で宣言します。
C#List<int> scores = new List<int> { 80, 90, 75 };
文字列を格納するListは、List<string>で宣言します。
C#List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};
小数を格納する場合はList<double>を使用できます。
C#List<double> prices = new List<double>
{
100.5,
250.0,
980.8
};
真偽値を格納する場合はList<bool>です。
C#List<bool> results = new List<bool>
{
true,
false,
true
};
Listに格納できるのは、基本データ型だけではありません。後述する独自クラスのオブジェクトも格納できます。
2-5. varを使ってListを宣言する方法
右辺から型が明確に判断できる場合は、varを使えます。
C#var names = new List<string>();
この変数の型は、コンパイル時にList<string>として決まります。varを使っても、どのような型でも代入できる変数になるわけではありません。
次のコードでは、namesはList<string>なので、整数を追加できません。
C#var names = new List<string>();
names.Add("田中");
// names.Add(100); // コンパイルエラー
varはコードを簡潔にできますが、右辺を見ても型が分かりにくい場合は、型名を明示したほうが読みやすくなります。
2-6. 空のListとnullの違い
空のListは、インスタンスは存在しますが、要素が1件も入っていない状態です。
C#List<string> names = new List<string>();
Console.WriteLine(names.Count); // 0
空のListには、問題なく要素を追加できます。
C#names.Add("田中");
一方、nullはListのインスタンス自体が存在しない状態です。
C#List<string>? names = null;
nullの状態でAddを呼び出すと、NullReferenceExceptionが発生します。
C#List<string>? names = null;
// names.Add("田中"); // NullReferenceException
Listをすぐに使用する場合は、nullではなく空のListとして初期化するのが基本です。
C#List<string> names = new List<string>();
3. C#のListに要素を追加する方法
3-1. Addメソッドで要素を1件追加する
Listに要素を1件追加するには、Addメソッドを使用します。
C#List<string> names = new List<string>();
names.Add("田中");
names.Add("佐藤");
names.Add("鈴木");
追加された要素は、基本的にListの末尾に格納されます。
C#Console.WriteLine(names[0]); // 田中
Console.WriteLine(names[1]); // 佐藤
Console.WriteLine(names[2]); // 鈴木
Listに指定した型と異なる値は追加できません。
C#List<int> numbers = new List<int>();
numbers.Add(10);
// numbers.Add("20"); // コンパイルエラー
3-2. AddRangeメソッドで複数の要素をまとめて追加する
複数の要素をまとめて追加するには、AddRangeメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"佐藤"
};
names.AddRange(new[] { "鈴木", "高橋" });
別のListを追加することもできます。
C#List<string> groupA = new List<string>
{
"田中",
"佐藤"
};
List<string> groupB = new List<string>
{
"鈴木",
"高橋"
};
groupA.AddRange(groupB);
groupAには、次の4件が格納されます。
田中
佐藤
鈴木
高橋
3-3. Insertメソッドで指定位置に要素を追加する
Listの指定した位置に要素を追加するには、Insertメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"鈴木"
};
names.Insert(1, "佐藤");
第1引数は追加する位置、第2引数は追加する値です。
実行後のListは次のようになります。
田中
佐藤
鈴木
インデックス1に要素を追加したため、もともとインデックス1にあった「鈴木」は後ろへ移動します。
3-4. InsertRangeメソッドで指定位置に複数要素を追加する
指定した位置に複数の要素を追加するには、InsertRangeメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"高橋"
};
List<string> newNames = new List<string>
{
"佐藤",
"鈴木"
};
names.InsertRange(1, newNames);
実行後のListは次のようになります。
田中
佐藤
鈴木
高橋
大量の要素を先頭や途中へ追加すると、それより後ろの要素を移動する処理が発生します。追加位置を自由に選べるのは便利ですが、大量データでは処理コストにも注意が必要です。
3-5. Listに重複しない要素だけを追加する方法
Listは重複する値を格納できます。
C#List<string> names = new List<string>();
names.Add("田中");
names.Add("田中");
重複を避けて追加するには、Containsで存在を確認してからAddを実行します。
C#List<string> names = new List<string>
{
"田中",
"佐藤"
};
string newName = "田中";
if (!names.Contains(newName))
{
names.Add(newName);
}
条件付きで追加する処理を繰り返し使用する場合は、メソッドにまとめることもできます。
C#static void AddIfNotExists(List<string> list, string value)
{
if (!list.Contains(value))
{
list.Add(value);
}
}
大量のデータについて頻繁に重複確認を行う場合は、Listよりも重複を許可しないHashSet<T>が適している場合があります。
4. C#のListから要素を取得・変更する方法
4-1. インデックスを指定して要素を取得する
Listの要素は、インデックスを指定して取得できます。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
Console.WriteLine(names[0]); // 田中
Console.WriteLine(names[1]); // 佐藤
インデックスは0から始まります。3件の要素がある場合、有効なインデックスは0、1、2です。
4-2. Listの先頭・末尾の要素を取得する
先頭の要素は、インデックス0で取得できます。
C#string firstName = names[0];
末尾の要素は、Count - 1を指定して取得できます。
C#string lastName = names[names.Count - 1];
C#のインデックス演算子を使用すると、末尾の要素を次のように取得できます。
C#string lastName = names[^1];
ただし、空のListに対して先頭や末尾の要素を取得すると例外が発生します。取得前に要素が存在するか確認しましょう。
C#if (names.Count > 0)
{
Console.WriteLine(names[0]);
Console.WriteLine(names[^1]);
}
LINQを使用する場合は、FirstOrDefaultやLastOrDefaultも利用できます。
C#string? firstName = names.FirstOrDefault();
string? lastName = names.LastOrDefault();
4-3. インデックスを指定して要素を書き換える
Listの要素は、インデックスを指定して書き換えられます。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
names[1] = "高橋";
実行後のListは次のようになります。
田中
高橋
鈴木
存在しないインデックスへ値を代入することはできません。
C#// names[3] = "伊藤"; // 例外が発生
末尾に新しい要素を追加する場合は、インデックスではなくAddを使用します。
C#names.Add("伊藤");
4-4. Countプロパティで要素数を取得する
Listの現在の要素数は、Countプロパティで取得できます。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
Console.WriteLine(names.Count); // 3
Countは、ループの条件や空判定にも利用できます。
C#if (names.Count == 0)
{
Console.WriteLine("データがありません");
}
else
{
Console.WriteLine($"{names.Count}件のデータがあります");
}
4-5. 存在しないインデックスを指定した場合のエラー
Listの範囲外のインデックスを指定すると、ArgumentOutOfRangeExceptionが発生します。
C#List<string> names = new List<string>
{
"田中",
"佐藤"
};
// Console.WriteLine(names[2]); // 例外
要素数が2件の場合、有効なインデックスは0と1です。
安全に取得するには、インデックスが0以上かつCount未満であることを確認します。
C#int index = 1;
if (index >= 0 && index < names.Count)
{
Console.WriteLine(names[index]);
}
else
{
Console.WriteLine("インデックスが範囲外です");
}
5. C#のListから要素を削除する方法
5-1. Removeメソッドで指定した値を削除する
指定した値を削除するには、Removeメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
bool removed = names.Remove("佐藤");
Removeは削除に成功するとtrue、対象が見つからない場合はfalseを返します。
C#if (removed)
{
Console.WriteLine("削除しました");
}
else
{
Console.WriteLine("対象が見つかりませんでした");
}
同じ値が複数ある場合、Removeが削除するのは最初に見つかった1件だけです。
C#List<int> numbers = new List<int> { 10, 20, 10 };
numbers.Remove(10);
実行後の内容は20, 10です。
5-2. RemoveAtメソッドで指定位置の要素を削除する
指定したインデックスの要素を削除するには、RemoveAtメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
names.RemoveAt(1);
インデックス1の「佐藤」が削除されます。
範囲外のインデックスを指定すると例外が発生するため、必要に応じて事前に確認します。
C#int index = 1;
if (index >= 0 && index < names.Count)
{
names.RemoveAt(index);
}
5-3. RemoveAllメソッドで条件に一致する要素を削除する
条件に一致する要素をすべて削除するには、RemoveAllメソッドを使用します。
C#List<int> numbers = new List<int>
{
10,
20,
30,
40,
50
};
int removedCount = numbers.RemoveAll(number => number >= 30);
この例では、30以上の要素がすべて削除されます。
RemoveAllは、削除した要素数を返します。
C#Console.WriteLine(removedCount); // 3
文字列に対しても条件を指定できます。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"田辺"
};
names.RemoveAll(name => name.StartsWith("田"));
5-4. RemoveRangeメソッドで範囲を指定して削除する
連続する複数の要素を削除するには、RemoveRangeメソッドを使用します。
C#List<int> numbers = new List<int>
{
10,
20,
30,
40,
50
};
numbers.RemoveRange(1, 3);
第1引数は削除を開始するインデックス、第2引数は削除する要素数です。
この例では、インデックス1から3件、つまり20、30、40が削除されます。
実行後の内容は次のとおりです。
10
50
指定範囲がListの範囲を超えると例外が発生します。
5-5. Clearメソッドですべての要素を削除する
Listの全要素を削除するには、Clearメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
names.Clear();
Console.WriteLine(names.Count); // 0
Clearを実行しても、Listの変数自体がnullになるわけではありません。空のListになるため、その後も要素を追加できます。
C#names.Add("高橋");
5-6. ループ中に要素を削除するときの注意点
foreachでListを処理している途中に要素を削除すると、通常はInvalidOperationExceptionが発生します。
C#List<int> numbers = new List<int> { 10, 20, 30, 40 };
foreach (int number in numbers)
{
// numbers.Remove(number); // 例外の原因
}
条件に一致する要素を削除するだけなら、RemoveAllを使用するのが簡単です。
C#numbers.RemoveAll(number => number >= 30);
インデックスを使って削除する場合は、後ろから前へループします。
C#for (int i = numbers.Count - 1; i >= 0; i--)
{
if (numbers[i] >= 30)
{
numbers.RemoveAt(i);
}
}
前から削除するとインデックスがずれ、確認していない要素が発生する可能性があります。後ろから削除すれば、未処理部分のインデックスに影響しません。
6. C#のListから要素を検索する方法
6-1. Containsメソッドで値が存在するか確認する
指定した値がListに含まれているか確認するには、Containsメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
bool exists = names.Contains("佐藤");
Console.WriteLine(exists); // True
存在しない値を指定するとfalseが返ります。
C#Console.WriteLine(names.Contains("高橋")); // False
6-2. IndexOfメソッドで要素の位置を検索する
指定した値のインデックスを取得するには、IndexOfメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
int index = names.IndexOf("佐藤");
Console.WriteLine(index); // 1
対象が見つからない場合は-1が返ります。
C#int index = names.IndexOf("高橋");
if (index >= 0)
{
Console.WriteLine($"インデックスは{index}です");
}
else
{
Console.WriteLine("見つかりませんでした");
}
同じ値が複数存在する場合は、最初に見つかった位置が返されます。
6-3. Findメソッドで条件に一致する最初の要素を取得する
条件に一致する最初の要素を取得するには、Findメソッドを使用します。
C#List<int> numbers = new List<int>
{
10,
20,
30,
40
};
int result = numbers.Find(number => number >= 25);
Console.WriteLine(result); // 30
文字列の検索も可能です。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"田辺"
};
string? result = names.Find(name => name.StartsWith("田"));
この場合、最初に条件へ一致した「田中」が返ります。
6-4. FindAllメソッドで条件に一致する要素をすべて取得する
条件に一致するすべての要素を取得するには、FindAllメソッドを使用します。
C#List<int> numbers = new List<int>
{
10,
20,
30,
40,
50
};
List<int> results = numbers.FindAll(number => number >= 30);
resultsには、30、40、50が格納されます。
C#foreach (int result in results)
{
Console.WriteLine(result);
}
元のListは変更されません。
6-5. Existsメソッドで条件に一致する要素の有無を調べる
条件に一致する要素が1件でも存在するか確認するには、Existsメソッドを使用します。
C#List<int> scores = new List<int>
{
50,
65,
80
};
bool exists = scores.Exists(score => score >= 70);
Console.WriteLine(exists); // True
Existsは要素そのものではなく、存在するかどうかをboolで返します。
6-6. LINQのFirstOrDefaultで要素を検索する
LINQのFirstOrDefaultを使うと、条件に一致する最初の要素を取得できます。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"田辺"
};
string? result = names.FirstOrDefault(name => name.StartsWith("田"));
条件に一致する要素がない場合、参照型では通常nullが返ります。
C#if (result is not null)
{
Console.WriteLine(result);
}
else
{
Console.WriteLine("見つかりませんでした");
}
値型では既定値が返る点に注意が必要です。
C#List<int> numbers = new List<int> { 10, 20, 30 };
int result = numbers.FirstOrDefault(number => number >= 100);
Console.WriteLine(result); // 0
検索結果の0が「見つからなかった」ことを示すのか、実際に見つかった値なのかを区別できない場合があります。そのような場合は、Anyで存在を確認する方法や、nullable型へ変換する方法を検討します。
6-7. LINQのWhereで条件に一致する要素を抽出する
条件に一致する複数の要素を抽出するには、LINQのWhereを使用します。
C#List<int> numbers = new List<int>
{
10,
20,
30,
40,
50
};
List<int> results = numbers
.Where(number => number >= 30)
.ToList();
Whereの戻り値はIEnumerable<T>です。新しいListとして使用する場合は、最後にToListを付けます。
複数の条件も指定できます。
C#List<int> results = numbers
.Where(number => number >= 20 && number <= 40)
.ToList();
6-8. 検索結果が見つからない場合の扱い方
検索方法によって、見つからなかった場合の結果は異なります。
Containsはfalseを返すIndexOfは-1を返すFindは型の既定値を返すFindAllは空のListを返すExistsはfalseを返すFirstOrDefaultは型の既定値を返すFirstは例外を発生させる
参照型の検索では、nullチェックを行います。
C#string? result = names.FirstOrDefault(name => name == "高橋");
if (result is null)
{
Console.WriteLine("対象が見つかりませんでした");
}
複数件を検索した場合は、AnyやCountで結果を確認できます。
C#List<string> results = names
.Where(name => name.StartsWith("高"))
.ToList();
if (!results.Any())
{
Console.WriteLine("対象が見つかりませんでした");
}
7. C#のListをループ処理する方法
7-1. foreach文で全要素を順番に処理する
Listの全要素を順番に処理する場合は、foreach文が分かりやすく便利です。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
foreach (string name in names)
{
Console.WriteLine(name);
}
インデックスを使わず、要素を1件ずつ処理したい場合に適しています。
7-2. for文でインデックスを使って処理する
インデックスが必要な場合は、for文を使用します。
C#for (int i = 0; i < names.Count; i++)
{
Console.WriteLine($"{i}: {names[i]}");
}
出力結果は次のようになります。
0: 田中
1: 佐藤
2: 鈴木
要素の書き換えや、インデックスを利用した条件分岐にも向いています。
7-3. List.ForEachメソッドで処理する
ListにはForEachメソッドも用意されています。
C#names.ForEach(name =>
{
Console.WriteLine(name);
});
処理が1行だけなら、さらに短く記述できます。
C#names.ForEach(name => Console.WriteLine(name));
ただし、複雑な処理では通常のforeach文のほうが読みやすくなる場合があります。
7-4. 条件に一致する要素だけをループ処理する
Whereとforeachを組み合わせると、条件に一致する要素だけを処理できます。
C#List<int> scores = new List<int>
{
50,
70,
85,
40,
90
};
foreach (int score in scores.Where(score => score >= 70))
{
Console.WriteLine(score);
}
先に新しいListを作成する方法もあります。
C#List<int> passedScores = scores
.Where(score => score >= 70)
.ToList();
foreach (int score in passedScores)
{
Console.WriteLine(score);
}
一度だけ処理するなら、そのままWhereの結果をforeachで使用できます。
7-5. ループ処理中に要素を変更する方法
値型の要素を変更する場合は、for文でインデックスを指定して書き換えます。
C#List<int> numbers = new List<int>
{
10,
20,
30
};
for (int i = 0; i < numbers.Count; i++)
{
numbers[i] *= 2;
}
実行後は20、40、60になります。
foreachの反復変数へ直接代入することはできません。
C#foreach (int number in numbers)
{
// number *= 2; // 反復変数へ代入できない
}
独自クラスのオブジェクトでは、foreach内でプロパティを更新できます。
C#foreach (User user in users)
{
user.IsActive = true;
}
ただし、ループ中にList自体へ追加・削除する処理は避ける必要があります。
7-6. for・foreach・ForEachの使い分け
for、foreach、List.ForEachは、目的に応じて使い分けます。
foreachは、全要素を順番に読み取る処理に適しています。
C#foreach (string name in names)
{
Console.WriteLine(name);
}
forは、インデックスが必要な処理や、Listの要素を入れ替える処理に適しています。
C#for (int i = 0; i < names.Count; i++)
{
names[i] = names[i].ToUpper();
}
List.ForEachは、短く単純な処理を記述するときに便利です。
C#names.ForEach(Console.WriteLine);
可読性を優先する場合はforeach、インデックス操作が必要ならfor、簡潔な1行処理ならForEachを選ぶとよいでしょう。
8. C#のListを並べ替える方法
8-1. Sortメソッドで昇順に並べ替える
Listを昇順に並べ替えるには、Sortメソッドを使用します。
C#List<int> numbers = new List<int>
{
30,
10,
40,
20
};
numbers.Sort();
実行後の順序は次のとおりです。
10
20
30
40
文字列も並べ替えられます。
C#List<string> names = new List<string>
{
"C",
"A",
"B"
};
names.Sort();
Sortは元のListの順序を直接変更します。
8-2. Reverseメソッドで要素の順序を反転する
現在の要素の順序を反転するには、Reverseメソッドを使用します。
C#List<int> numbers = new List<int>
{
10,
20,
30
};
numbers.Reverse();
実行後は30、20、10になります。
降順に並べ替えたい場合は、Sortの後にReverseを実行できます。
C#numbers.Sort();
numbers.Reverse();
ただし、Reverseは並べ替えではなく、現在の順番を逆にするメソッドです。未整列のListにReverseを実行しても、数値の降順になるとは限りません。
8-3. LINQのOrderByで昇順に並べ替える
LINQのOrderByを使用すると、元のListを変更せずに昇順の結果を作成できます。
C#List<int> numbers = new List<int>
{
30,
10,
40,
20
};
List<int> sortedNumbers = numbers
.OrderBy(number => number)
.ToList();
sortedNumbersは10、20、30、40になりますが、元のnumbersの順序は変わりません。
8-4. LINQのOrderByDescendingで降順に並べ替える
降順に並べ替えるには、OrderByDescendingを使用します。
C#List<int> sortedNumbers = numbers
.OrderByDescending(number => number)
.ToList();
結果は40、30、20、10です。
オブジェクトのプロパティを基準に並べ替えることもできます。
C#List<Product> sortedProducts = products
.OrderByDescending(product => product.Price)
.ToList();
8-5. 複数の条件を指定して並べ替える
複数条件で並べ替える場合は、OrderByまたはOrderByDescendingの後にThenByやThenByDescendingを使用します。
C#List<Product> sortedProducts = products
.OrderBy(product => product.Category)
.ThenBy(product => product.Price)
.ToList();
このコードは、最初にカテゴリの昇順で並べ替え、カテゴリが同じ商品を価格の昇順で並べ替えます。
第2条件を降順にすることも可能です。
C#List<Product> sortedProducts = products
.OrderBy(product => product.Category)
.ThenByDescending(product => product.Price)
.ToList();
8-6. 元のListを変更する並べ替えと変更しない並べ替えの違い
SortとReverseは、元のListを直接変更します。
C#numbers.Sort();
一方、LINQのOrderByやOrderByDescendingは、並べ替えた結果を返します。元のListは変更しません。
C#List<int> sortedNumbers = numbers
.OrderBy(number => number)
.ToList();
元の順序を残す必要がない場合はSort、元の順序を保持したまま並べ替え結果を作りたい場合はOrderByが便利です。
9. C#のListを配列や文字列へ変換する方法
9-1. ToArrayメソッドでListを配列へ変換する
Listを配列へ変換するには、ToArrayメソッドを使用します。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
string[] nameArray = names.ToArray();
変換後の配列と元のListは別のデータです。配列側の要素を入れ替えても、元のListの要素配置は変わりません。
9-2. ToListメソッドで配列をListへ変換する
配列をListへ変換するには、LINQのToListを使用します。
C#string[] nameArray =
{
"田中",
"佐藤",
"鈴木"
};
List<string> names = nameArray.ToList();
コンストラクターへ配列を渡す方法もあります。
C#List<string> names = new List<string>(nameArray);
どちらの方法でも、新しいListが作成されます。
9-3. string.JoinでListの要素を文字列として連結する
Listの要素を区切り文字で連結するには、string.Joinを使用します。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
string result = string.Join(", ", names);
Console.WriteLine(result);
出力結果は次のとおりです。
田中, 佐藤, 鈴木
改行で連結することもできます。
C#string result = string.Join(Environment.NewLine, names);
整数のListも連結できます。
C#List<int> numbers = new List<int> { 10, 20, 30 };
string result = string.Join(",", numbers);
9-4. Splitで分割した文字列をListへ変換する
区切り文字を含む文字列をListへ変換するには、SplitとToListを組み合わせます。
C#string text = "田中,佐藤,鈴木";
List<string> names = text
.Split(',')
.ToList();
空白を取り除きたい場合は、SelectとTrimを組み合わせます。
C#string text = "田中, 佐藤, 鈴木";
List<string> names = text
.Split(',')
.Select(name => name.Trim())
.ToList();
空の要素を除外したい場合は、StringSplitOptions.RemoveEmptyEntriesを指定できます。
C#string text = "田中,,佐藤,鈴木";
List<string> names = text
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.ToList();
9-5. 異なる型のListへ変換する方法
異なる型のListへ変換するには、LINQのSelectを使用します。
文字列のListを整数のListへ変換する例は次のとおりです。
C#List<string> numberTexts = new List<string>
{
"10",
"20",
"30"
};
List<int> numbers = numberTexts
.Select(text => int.Parse(text))
.ToList();
入力内容が不正な可能性がある場合は、int.TryParseを使うと安全です。
C#List<int> numbers = new List<int>();
foreach (string text in numberTexts)
{
if (int.TryParse(text, out int number))
{
numbers.Add(number);
}
}
オブジェクトのListから特定のプロパティだけを取り出すこともできます。
C#List<string> productNames = products
.Select(product => product.Name)
.ToList();
10. C#のListでクラスのオブジェクトを管理する方法
10-1. 独自クラスを格納するListを作成する
Listには、独自クラスのオブジェクトを格納できます。
まず、商品を表すProductクラスを定義します。
C#public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
Productオブジェクトを格納するListは、次のように作成します。
C#List<Product> products = new List<Product>();
10-2. オブジェクトをListへ追加する
作成したオブジェクトは、Addメソッドで追加できます。
C#Product product = new Product
{
Id = 1,
Name = "キーボード",
Price = 5000m
};
products.Add(product);
オブジェクトの作成と追加を同時に行うこともできます。
C#products.Add(new Product
{
Id = 2,
Name = "マウス",
Price = 3000m
});
初期化時に複数の商品を指定することも可能です。
C#List<Product> products = new List<Product>
{
new Product
{
Id = 1,
Name = "キーボード",
Price = 5000m
},
new Product
{
Id = 2,
Name = "マウス",
Price = 3000m
}
};
10-3. プロパティを条件にオブジェクトを検索する
IDを条件に商品を検索する場合は、FirstOrDefaultを使用できます。
C#Product? product = products
.FirstOrDefault(product => product.Id == 2);
if (product is not null)
{
Console.WriteLine(product.Name);
}
価格が一定以上の商品をすべて取得する場合は、Whereを使用します。
C#List<Product> expensiveProducts = products
.Where(product => product.Price >= 4000m)
.ToList();
商品名に特定の文字が含まれるか検索することもできます。
C#List<Product> results = products
.Where(product => product.Name.Contains("マウス"))
.ToList();
10-4. プロパティを条件にオブジェクトを並べ替える
価格の昇順で商品を並べ替える場合は、OrderByを使用します。
C#List<Product> sortedProducts = products
.OrderBy(product => product.Price)
.ToList();
価格の降順で並べ替える場合は、OrderByDescendingを使用します。
C#List<Product> sortedProducts = products
.OrderByDescending(product => product.Price)
.ToList();
商品名で並べ替えることもできます。
C#List<Product> sortedProducts = products
.OrderBy(product => product.Name)
.ToList();
10-5. List内のオブジェクトを更新・削除する
対象のオブジェクトを検索し、プロパティを書き換えると更新できます。
C#Product? product = products
.FirstOrDefault(product => product.Id == 2);
if (product is not null)
{
product.Price = 3500m;
}
検索したオブジェクトを削除するには、Removeを使用します。
C#if (product is not null)
{
products.Remove(product);
}
条件に一致するオブジェクトをまとめて削除する場合は、RemoveAllを使用します。
C#products.RemoveAll(product => product.Price < 1000m);
11. C#のListでよく使う便利なプロパティとメソッド
11-1. Countで現在の要素数を確認する
Countプロパティは、Listに格納されている要素数を返します。
C#List<int> numbers = new List<int> { 10, 20, 30 };
Console.WriteLine(numbers.Count); // 3
要素の追加や削除に応じて、Countも変化します。
C#numbers.Add(40);
Console.WriteLine(numbers.Count); // 4
numbers.Remove(20);
Console.WriteLine(numbers.Count); // 3
11-2. Capacityで確保されている容量を確認する
Capacityプロパティは、内部的に確保されている要素数の容量を表します。
C#List<int> numbers = new List<int>();
Console.WriteLine(numbers.Count);
Console.WriteLine(numbers.Capacity);
Countは現在格納されている要素数、Capacityは再確保せずに格納できる容量です。
要素を追加して容量が不足すると、Listは内部領域を自動的に拡張します。大量の要素数が事前に分かっている場合は、初期容量を指定できます。
C#List<int> numbers = new List<int>(1000);
これは1000件の要素を追加した状態ではありません。Countは0のままで、1000件分の容量をあらかじめ確保しています。
11-3. Anyで要素が存在するか確認する
LINQのAnyを使うと、Listに要素が存在するか確認できます。
C#if (numbers.Any())
{
Console.WriteLine("要素があります");
}
条件に一致する要素が存在するか確認することもできます。
C#bool hasLargeNumber = numbers.Any(number => number >= 100);
単純にListが空か確認するだけなら、Countも使用できます。
C#bool isEmpty = numbers.Count == 0;
11-4. Allですべての要素が条件を満たすか確認する
すべての要素が条件を満たすか確認するには、LINQのAllを使用します。
C#List<int> scores = new List<int>
{
70,
80,
90
};
bool allPassed = scores.All(score => score >= 60);
Console.WriteLine(allPassed); // True
空のListに対してAllを実行するとtrueが返ります。要素が存在することも条件に含めたい場合は、Anyと組み合わせます。
C#bool allPassed = scores.Any()
&& scores.All(score => score >= 60);
11-5. Distinctで重複する要素を取り除く
重複する値を取り除いた新しいListを作るには、LINQのDistinctを使用します。
C#List<int> numbers = new List<int>
{
10,
20,
10,
30,
20
};
List<int> uniqueNumbers = numbers
.Distinct()
.ToList();
uniqueNumbersの内容は10、20、30です。
元のListは変更されません。元の変数へ再代入することもできます。
C#numbers = numbers.Distinct().ToList();
独自クラスでは、何を重複と判断するかを指定する必要があります。特定のプロパティで重複を除外する場合は、DistinctByを利用できます。
C#List<Product> uniqueProducts = products
.DistinctBy(product => product.Id)
.ToList();
11-6. TakeとSkipで取得範囲を指定する
先頭から指定件数を取得するには、Takeを使用します。
C#List<int> numbers = new List<int>
{
10,
20,
30,
40,
50
};
List<int> firstThree = numbers
.Take(3)
.ToList();
先頭から指定件数を飛ばすには、Skipを使用します。
C#List<int> remaining = numbers
.Skip(2)
.ToList();
SkipとTakeを組み合わせると、ページ単位のデータ取得にも利用できます。
C#int pageNumber = 2;
int pageSize = 10;
List<Product> pageItems = products
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
11-7. Sum・Average・Max・Minで集計する
数値のListは、LINQで簡単に集計できます。
C#List<int> scores = new List<int>
{
70,
80,
90
};
int total = scores.Sum();
double average = scores.Average();
int maximum = scores.Max();
int minimum = scores.Min();
結果は次のようになります。
合計: 240
平均: 80
最大: 90
最小: 70
オブジェクトの数値プロパティも集計できます。
C#decimal totalPrice = products.Sum(product => product.Price);
decimal averagePrice = products.Average(product => product.Price);
decimal highestPrice = products.Max(product => product.Price);
decimal lowestPrice = products.Min(product => product.Price);
空のListに対してAverage、Max、Minを実行すると例外になる場合があります。事前にAnyで確認すると安全です。
C#if (scores.Any())
{
Console.WriteLine(scores.Average());
}
12. C#のListを使う際によくあるエラーと対処法
12-1. System.Collections.Genericを指定していない
List<T>が見つからないというエラーが出る場合は、System.Collections.Genericが指定されているか確認します。
C#using System.Collections.Generic;
usingを追加せず、完全修飾名で記述することもできます。
C#System.Collections.Generic.List<string> names =
new System.Collections.Generic.List<string>();
通常はusingディレクティブを追加する方法が読みやすく便利です。
12-2. Listがnullのままメソッドを呼び出している
Listがnullの状態でメソッドやプロパティを使用すると、NullReferenceExceptionが発生します。
C#List<string>? names = null;
// names.Add("田中"); // 例外
使用前にインスタンスを作成します。
C#List<string> names = new List<string>();
names.Add("田中");
外部からnullが渡される可能性がある場合は、null合体代入演算子を使用できます。
C#names ??= new List<string>();
12-3. インデックスがListの範囲を超えている
存在しないインデックスを指定すると、ArgumentOutOfRangeExceptionが発生します。
C#List<string> names = new List<string>
{
"田中",
"佐藤"
};
// Console.WriteLine(names[2]); // 例外
インデックスが有効範囲内か確認します。
C#if (index >= 0 && index < names.Count)
{
Console.WriteLine(names[index]);
}
ループ条件は、i <= names.Countではなくi < names.Countにする点も重要です。
C#for (int i = 0; i < names.Count; i++)
{
Console.WriteLine(names[i]);
}
12-4. RemoveとRemoveAtを混同している
Removeは値を指定して削除し、RemoveAtはインデックスを指定して削除します。
C#List<int> numbers = new List<int>
{
10,
20,
30
};
numbers.Remove(20);
上記は、値が20の要素を削除します。
C#numbers.RemoveAt(1);
上記は、インデックス1の要素を削除します。
整数のListでは、値とインデックスの両方が整数なので混同しやすい点に注意しましょう。
12-5. foreachループ中に要素を追加・削除している
foreachの処理中にListへ要素を追加・削除すると、通常は例外が発生します。
C#foreach (string name in names)
{
// names.Add("新しい名前");
// names.Remove(name);
}
削除する場合は、RemoveAllを使用するか、後ろからforで処理します。
C#names.RemoveAll(name => name.StartsWith("田"));
ループしながら追加する必要がある場合は、追加予定の要素を別のListに保存し、ループ終了後に追加します。
C#List<string> newNames = new List<string>();
foreach (string name in names)
{
if (name == "田中")
{
newNames.Add("田辺");
}
}
names.AddRange(newNames);
12-6. Firstを使って要素が見つからず例外が発生する
LINQのFirstは、条件に一致する要素がないとInvalidOperationExceptionを発生させます。
C#// string name = names.First(name => name == "高橋");
見つからない可能性がある場合は、FirstOrDefaultを使用します。
C#string? name = names.FirstOrDefault(name => name == "高橋");
if (name is null)
{
Console.WriteLine("見つかりませんでした");
}
必ず1件存在することが処理上保証されている場合だけ、Firstを使用するとよいでしょう。
12-7. ToListを付け忘れて型が一致しない
LINQのWhereやOrderByの戻り値は、通常List<T>ではなくIEnumerable<T>です。
そのため、次の代入はコンパイルエラーになります。
C#// List<int> results = numbers.Where(number => number >= 20);
Listとして取得するには、最後にToListを付けます。
C#List<int> results = numbers
.Where(number => number >= 20)
.ToList();
Listへ変換する必要がなければ、変数の型をIEnumerable<int>にする方法もあります。
C#IEnumerable<int> results =
numbers.Where(number => number >= 20);
13. C#のListを使った実践サンプル
13-1. ユーザー名の一覧を追加・表示する
ユーザー名をListへ追加し、一覧表示するサンプルです。
C#using System;
using System.Collections.Generic;
List<string> userNames = new List<string>();
userNames.Add("田中");
userNames.Add("佐藤");
userNames.Add("鈴木");
Console.WriteLine($"ユーザー数: {userNames.Count}");
foreach (string userName in userNames)
{
Console.WriteLine(userName);
}
実行結果は次のとおりです。
ユーザー数: 3
田中
佐藤
鈴木
13-2. 商品一覧から条件に一致する商品を検索する
商品クラスを作成し、指定価格以下の商品を検索します。
C#using System;
using System.Collections.Generic;
using System.Linq;
List<Product> products = new List<Product>
{
new Product
{
Id = 1,
Name = "キーボード",
Price = 5000m
},
new Product
{
Id = 2,
Name = "マウス",
Price = 3000m
},
new Product
{
Id = 3,
Name = "モニター",
Price = 25000m
}
};
decimal maximumPrice = 5000m;
List<Product> results = products
.Where(product => product.Price <= maximumPrice)
.ToList();
foreach (Product product in results)
{
Console.WriteLine($"{product.Name}: {product.Price}円");
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
13-3. 点数一覧を並べ替えて平均値を計算する
点数を降順に並べ替え、平均値を計算する例です。
C#using System;
using System.Collections.Generic;
using System.Linq;
List<int> scores = new List<int>
{
75,
90,
60,
85,
70
};
List<int> sortedScores = scores
.OrderByDescending(score => score)
.ToList();
Console.WriteLine("点数一覧");
foreach (int score in sortedScores)
{
Console.WriteLine(score);
}
double average = scores.Average();
Console.WriteLine($"平均点: {average}");
13-4. 重複データを除外して新しいListを作成する
重複するデータをDistinctで除外します。
C#using System;
using System.Collections.Generic;
using System.Linq;
List<string> names = new List<string>
{
"田中",
"佐藤",
"田中",
"鈴木",
"佐藤"
};
List<string> uniqueNames = names
.Distinct()
.ToList();
foreach (string name in uniqueNames)
{
Console.WriteLine(name);
}
実行結果は次のとおりです。
田中
佐藤
鈴木
13-5. オブジェクトの一覧を検索・更新・削除する
ユーザーの一覧から対象を検索し、更新後に無効なユーザーを削除する例です。
C#using System;
using System.Collections.Generic;
using System.Linq;
List<User> users = new List<User>
{
new User
{
Id = 1,
Name = "田中",
IsActive = true
},
new User
{
Id = 2,
Name = "佐藤",
IsActive = true
},
new User
{
Id = 3,
Name = "鈴木",
IsActive = false
}
};
User? targetUser = users
.FirstOrDefault(user => user.Id == 2);
if (targetUser is not null)
{
targetUser.Name = "佐藤太郎";
targetUser.IsActive = false;
}
int removedCount = users.RemoveAll(user => !user.IsActive);
Console.WriteLine($"削除件数: {removedCount}");
foreach (User user in users)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsActive { get; set; }
}
14. C#のListに関するよくある質問
14-1. Listには異なる型の値を格納できる?
List<T>には、基本的に指定した型の値だけを格納します。
C#List<string> values = new List<string>();
values.Add("文字列");
// values.Add(100); // コンパイルエラー
List<object>を使うと異なる型を格納できます。
C#List<object> values = new List<object>
{
"文字列",
100,
true
};
ただし、取り出す際に型の確認や変換が必要になるため、通常は同じ種類のデータを同じListで管理するのがおすすめです。
異なる種類の情報をまとめたい場合は、独自クラスを定義すると安全に扱えます。
C#public class User
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
14-2. Listの最大要素数はいくつ?
Listの要素数には、実行環境、利用可能なメモリ、格納する型、ランタイム内部の配列サイズ制限などが影響します。
CountとCapacityはint型ですが、実際に格納できる件数はメモリなどの制約により、それより少なくなるのが一般的です。
大量のデータを扱う場合は、すべてを1つのListへ読み込まず、データを分割して処理する方法も検討します。
14-3. Listが空かどうかを判定するには?
Countを使って判定できます。
C#if (names.Count == 0)
{
Console.WriteLine("空です");
}
LINQのAnyも使用できます。
C#if (!names.Any())
{
Console.WriteLine("空です");
}
List自体がnullになる可能性がある場合は、nullも確認します。
C#if (names is null || names.Count == 0)
{
Console.WriteLine("データがありません");
}
14-4. Listから重複する要素を削除するには?
Distinctで重複を除外した新しいListを作成できます。
C#names = names
.Distinct()
.ToList();
独自クラスを特定のプロパティで重複判定する場合は、DistinctByを使用できます。
C#products = products
.DistinctBy(product => product.Id)
.ToList();
重複を最初から許可したくない場合は、HashSet<T>の利用も検討しましょう。
14-5. Listをコピーするには?
Listのコンストラクターへ元のListを渡すと、別のListを作成できます。
C#List<string> original = new List<string>
{
"田中",
"佐藤"
};
List<string> copied = new List<string>(original);
ToListでもコピーできます。
C#List<string> copied = original.ToList();
文字列や整数などでは、この方法で独立したListとして扱えます。
ただし、独自クラスのListをコピーした場合、List自体は別でも、中に格納されたオブジェクトは同じ参照を共有します。
C#List<Product> copiedProducts = products.ToList();
オブジェクトまで別々にしたい場合は、新しいオブジェクトを作成します。
C#List<Product> copiedProducts = products
.Select(product => new Product
{
Id = product.Id,
Name = product.Name,
Price = product.Price
})
.ToList();
14-6. ListとIEnumerableはどう使い分ける?
List<T>は、要素の追加・削除やインデックスによる取得が必要な場合に適しています。
C#List<string> names = new List<string>();
names.Add("田中");
names.Remove("田中");
IEnumerable<T>は、主にデータを順番に列挙するための型です。
C#IEnumerable<string> results =
names.Where(name => name.StartsWith("田"));
検索結果を読み取るだけならIEnumerable<T>のまま扱えます。追加・削除や複数回の参照が必要な場合は、ToListでListへ変換します。
C#List<string> results = names
.Where(name => name.StartsWith("田"))
.ToList();
メソッドの戻り値をIEnumerable<T>にすると、呼び出し側が必要に応じてListや配列へ変換できるため、柔軟性が高くなる場合があります。
14-7. ListとDictionaryはどう使い分ける?
Listは、複数の要素を順番に管理するコレクションです。
C#List<string> names = new List<string>
{
"田中",
"佐藤"
};
Dictionaryは、キーと値を組み合わせて管理します。
C#Dictionary<int, string> users =
new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" }
};
Console.WriteLine(users[2]); // 佐藤
順番に一覧処理したい場合や、同じ値を複数格納したい場合はListが適しています。
IDやコードなど、一意のキーを使って値をすばやく取得したい場合はDictionaryが適しています。
独自クラスの一覧をListで管理し、毎回IDを検索する方法もあります。
C#User? user = usersList.FirstOrDefault(user => user.Id == 100);
IDによる検索を非常に多く行う場合は、IDをキーにしたDictionaryを検討するとよいでしょう。
14-8. 大量のデータを扱うときに注意することは?
大量のデータをListで扱う場合は、メモリ使用量や処理回数に注意が必要です。
要素数が事前に分かっている場合は、初期容量を指定すると内部領域の再確保を減らせます。
C#List<int> numbers = new List<int>(100000);
先頭へのInsertやRemoveAt(0)を繰り返すと、後続要素を移動する処理が発生します。先頭から頻繁に追加・削除する用途では、Queue<T>やLinkedList<T>など別のコレクションが適している場合があります。
また、データベースから大量のデータを取得するときは、すべてをListへ読み込まず、SkipとTakeなどを使ったページングを検討します。
C#List<Product> pageItems = products
.Skip(100)
.Take(50)
.ToList();
検索を何度も行う場合は、Listを毎回先頭から調べるより、用途に応じてDictionary<TKey, TValue>やHashSet<T>を使うほうが効率的です。
まとめ
C#のList<T>は、複数のデータを柔軟に管理できるコレクションです。配列とは異なり、必要に応じて要素を追加・削除できるため、件数が変化するデータの管理に適しています。
Listを使用する基本的な流れは、次のとおりです。
C#List<string> names = new List<string>();
names.Add("田中");
names.Add("佐藤");
bool exists = names.Contains("田中");
foreach (string name in names)
{
Console.WriteLine(name);
}
names.Remove("佐藤");
要素の検索にはContains、Find、FirstOrDefault、Whereなどを使用し、並べ替えにはSort、OrderBy、OrderByDescendingなどを利用できます。
また、ToArrayやToListによる型変換、Distinctによる重複除外、SumやAverageによる集計も可能です。
まずは、次の操作から身につけると理解しやすくなります。
new List<T>()でListを作成するAddで要素を追加するインデックスで要素を取得・変更する
RemoveやRemoveAtで要素を削除するforeachで全要素を処理するWhereやFirstOrDefaultで検索するSortやOrderByで並べ替える
これらの基本操作を理解すれば、ユーザー一覧、商品一覧、検索結果、点数データなど、さまざまなデータ処理へC#のListを活用できます。

