C#のListとは?使い方・追加・削除・検索を初心者向けにサンプルコードで徹底解説

はじめに

C#で複数のデータをまとめて扱うときに、特によく使われるのがList<T>です。Listを使うと、文字列や数値、独自に作成したクラスのオブジェクトなどを、1つの変数にまとめて保存できます。

配列と似ていますが、Listには要素を後から自由に追加・削除できるという大きな特徴があります。そのため、データ数が実行時まで分からない場合や、途中でデータが増減する処理に適しています。

この記事では、C#のListについて、宣言や初期化といった基本から、追加・削除・検索・並び替え・変換までを初心者向けに解説します。実際に動かせるサンプルコードも紹介するため、コードを書きながらListの使い方を確認してみてください。

1. C#のListとは?配列との違いを初心者向けに解説

1-1. List<T>の基本概要

C#のList<T>は、複数の要素を順番に格納できるコレクションです。

Tには、Listに格納するデータの型を指定します。たとえば、整数を格納する場合はList<int>、文字列を格納する場合はList<string>と記述します。

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

List<T>Tは「型パラメーター」と呼ばれます。Listを作成するときに型を指定することで、異なる型のデータが誤って追加されるのを防げます。

たとえば、List<int>に文字列を追加することはできません。

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

numbers.Add(10);
// numbers.Add("Hello"); // コンパイルエラー

このように、Listは型の安全性を保ちながら複数のデータを管理できます。

1-2. Listを使うメリット

Listを使う主なメリットは、要素数を柔軟に変更できることです。

配列は作成時に要素数を決める必要があります。

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

一方、Listは最初に要素数を決めなくても使用できます。

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

numbers.Add(10);
numbers.Add(20);
numbers.Add(30);
numbers.Add(40);

Listには要素を追加するAdd、削除するRemove、検索するFindなど、データ操作に便利なメソッドが用意されています。

Listを使うメリットをまとめると、次のとおりです。

  • 要素を後から追加できる

  • 不要な要素を削除できる

  • 要素数をCountで取得できる

  • 条件に合う要素を検索できる

  • 並び替えや変換が簡単にできる

  • 型を指定するため安全にデータを扱える

1-3. 配列とListの違い

配列とListは、どちらも複数のデータを順番に管理するために使います。ただし、要素数の扱いや操作方法に違いがあります。

比較項目配列List
宣言例int[] numbersList<int> numbers
要素数基本的に固定自由に増減できる
要素数の取得LengthCount
要素の追加新しい配列の作成が必要Addを使用
要素の削除直接削除できないRemoveなどを使用
インデックスアクセス可能可能
向いている場面要素数が決まっている処理要素数が変化する処理

配列の例は次のとおりです。

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

fruits[0] = "りんご";
fruits[1] = "みかん";
fruits[2] = "ぶどう";

Listの例は次のとおりです。

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

fruits.Add("りんご");
fruits.Add("みかん");
fruits.Add("ぶどう");
fruits.Add("バナナ");

保存するデータ数が決まっている場合は配列、データ数が増減する場合はListを選ぶのが基本です。

1-4. Listがよく使われる場面

Listは、次のような場面でよく使われます。

  • データベースから取得した複数のレコードを保持する

  • APIから受け取ったデータをまとめる

  • ユーザーが入力した項目を順番に保存する

  • 商品一覧やユーザー一覧を管理する

  • ゲーム内のアイテムやキャラクターを管理する

  • 条件に合うデータだけを抽出する

  • CSVファイルから読み込んだデータを保持する

たとえば、ユーザー名を入力されるたびに保存する処理では、人数を事前に決められません。このような場合にListが適しています。

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

userNames.Add("佐藤");
userNames.Add("鈴木");
userNames.Add("高橋");

2. C#でListを使うための準備

2-1. Listを使うために必要なusing

List<T>は、System.Collections.Generic名前空間に含まれています。

そのため、ソースコードの先頭に次のusingディレクティブを記述します。

C#
using System.Collections.Generic;

コンソール出力も行う場合は、Systemも使用します。

C#
using System;
using System.Collections.Generic;

LINQのWhereSelectOrderByなどを使う場合は、さらにSystem.Linqを追加します。

C#
using System;
using System.Collections.Generic;
using System.Linq;

最近の.NETプロジェクトでは、暗黙的なusingが有効になっており、明示的に記述しなくてもListを使える場合があります。ただし、どの名前空間の機能を使用しているのか理解するためにも、初心者のうちは必要なusingを把握しておきましょう。

2-2. Listの基本的な宣言方法

Listの基本的な宣言方法は次のとおりです。

C#
List<> 変数名 = new List<>();

整数を格納するListは、次のように宣言します。

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

文字列を格納するListは次のとおりです。

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

C# 9以降では、右辺の型を省略するターゲット型のnewも使用できます。

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

ただし、最初は型が明確に見える次の書き方から覚えると理解しやすいでしょう。

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

2-3. Listの初期化方法

Listは、作成と同時に初期値を設定できます。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

1行で記述することも可能です。

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

初期値を設定した後も、Addを使って要素を追加できます。

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

numbers.Add(40);

配列など、既存のコレクションからListを作ることもできます。

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

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

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

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

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

ToListを使う場合は、using System.Linq;が必要です。

2-4. int・string・classなど型別のList例

整数を格納するList<int>の例です。

C#
List<int> scores = new List<int>
{
80,
95,
70
};

文字列を格納するList<string>の例です。

C#
List<string> colors = new List<string>
{
"赤",
"青",
"緑"
};

小数を格納する場合はList<double>などを使用します。

C#
List<double> prices = new List<double>
{
120.5,
350.0,
980.75
};

独自クラスのオブジェクトもListに格納できます。

C#
class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Price { get; set; }
}
C#
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "ノート", Price = 200 },
new Product { Id = 2, Name = "ペン", Price = 100 }
};

実務では、データベースから取得した情報をList<Product>List<User>のような形で扱うケースが多くあります。

3. C#のListに要素を追加する方法

3-1. Addで要素を1つ追加する

Listに要素を1つ追加するには、Addメソッドを使用します。

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

fruits.Add("りんご");
fruits.Add("みかん");
fruits.Add("ぶどう");

追加された要素は、基本的にListの末尾に入ります。

C#
foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
}

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

りんご
みかん
ぶどう

List<int>でも使い方は同じです。

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

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

3-2. AddRangeで複数の要素を追加する

複数の要素をまとめて追加する場合は、AddRangeを使用します。

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

numbers.AddRange(new int[] { 30, 40, 50 });

追加後のListは、10、20、30、40、50となります。

別のListを追加することもできます。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん"
};

List<string> additionalFruits = new List<string>
{
"ぶどう",
"バナナ"
};

fruits.AddRange(additionalFruits);

Addは1要素、AddRangeは複数要素を追加すると覚えておきましょう。

3-3. Insertで指定位置に要素を追加する

Listの途中に要素を追加する場合は、Insertを使用します。

C#
List<string> fruits = new List<string>
{
"りんご",
"ぶどう"
};

fruits.Insert(1, "みかん");

第1引数には挿入位置のインデックス、第2引数には追加する値を指定します。

追加後の内容は次のとおりです。

りんご
みかん
ぶどう

複数要素を指定位置に追加する場合は、InsertRangeを使用できます。

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

numbers.InsertRange(1, new int[] { 20, 30 });

結果は10、20、30、40です。

3-4. 追加時によくあるエラーと対処法

Listへの追加でよくある問題の1つが、Listを初期化していない状態でAddを呼び出すことです。

C#
List<string>? names = null;

// names.Add("佐藤"); // NullReferenceException

変数がnullのままでは、Listのメソッドを使用できません。必ずListのインスタンスを作成します。

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

names.Add("佐藤");

Listに指定した型と異なる値を追加しようとすると、コンパイルエラーになります。

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

numbers.Add(10);
// numbers.Add("20"); // string型なので追加できない

文字列を整数に変換してから追加する必要があります。

C#
string input = "20";

if (int.TryParse(input, out int number))
{
numbers.Add(number);
}

Insertで範囲外の位置を指定した場合もエラーになります。

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

// numbers.Insert(5, 30); // ArgumentOutOfRangeException

Insertに指定できる位置は、0以上かつCount以下です。末尾への挿入であれば、インデックスにCountを指定できます。

C#
numbers.Insert(numbers.Count, 30);

4. C#のListから要素を取得・変更する方法

4-1. インデックス番号で要素を取得する

Listの要素は、配列と同じようにインデックス番号で取得できます。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

Console.WriteLine(fruits[0]);
Console.WriteLine(fruits[1]);
Console.WriteLine(fruits[2]);

インデックスは0から始まります。

インデックス
0りんご
1みかん
2ぶどう

先頭の要素はlist[0]、最後の要素はlist[list.Count - 1]で取得できます。

C#
string first = fruits[0];
string last = fruits[fruits.Count - 1];

Console.WriteLine(first);
Console.WriteLine(last);

ただし、空のListに対して最後の要素を取得しようとするとエラーになります。取得前に要素数を確認しましょう。

C#
if (fruits.Count > 0)
{
string last = fruits[fruits.Count - 1];
Console.WriteLine(last);
}

4-2. Countで要素数を確認する

Listの要素数は、Countプロパティで取得します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

Console.WriteLine(fruits.Count);

実行結果は3です。

配列ではLengthを使いますが、ListではCountを使う点に注意してください。

C#
int[] array = { 10, 20, 30 };
List<int> list = new List<int> { 10, 20, 30 };

Console.WriteLine(array.Length);
Console.WriteLine(list.Count);

Listが空かどうかは、次のように確認できます。

C#
if (fruits.Count == 0)
{
Console.WriteLine("Listは空です");
}

LINQを使用している場合は、Anyを使う方法もあります。

C#
if (!fruits.Any())
{
Console.WriteLine("Listは空です");
}

4-3. Listの要素を変更する

Listの要素を変更する場合は、変更したい位置のインデックスを指定して新しい値を代入します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

fruits[1] = "バナナ";

変更後のListは次のとおりです。

りんご
バナナ
ぶどう

クラスのListでは、取得したオブジェクトのプロパティを変更できます。

C#
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "ノート", Price = 200 },
new Product { Id = 2, Name = "ペン", Price = 100 }
};

products[0].Price = 250;

このコードでは、先頭の商品の価格が200から250に変更されます。

4-4. 範囲外アクセスのエラーに注意する

Listに存在しないインデックスを指定すると、ArgumentOutOfRangeExceptionが発生します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん"
};

// Console.WriteLine(fruits[2]); // ArgumentOutOfRangeException

要素数が2の場合、有効なインデックスは01です。

安全に取得するには、インデックスが範囲内か確認します。

C#
int index = 2;

if (index >= 0 && index < fruits.Count)
{
Console.WriteLine(fruits[index]);
}
else
{
Console.WriteLine("指定した位置に要素はありません");
}

なお、配列の範囲外アクセスでは一般的にIndexOutOfRangeExceptionが発生しますが、List<T>のインデクサーでは通常ArgumentOutOfRangeExceptionが発生します。

5. C#のListから要素を削除する方法

5-1. Removeで指定した値を削除する

指定した値をListから削除するには、Removeを使用します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

fruits.Remove("みかん");

削除後のListは次のとおりです。

りんご
ぶどう

Removeは、削除に成功するとtrue、対象が見つからなければfalseを返します。

C#
bool removed = fruits.Remove("りんご");

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> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

fruits.RemoveAt(1);

インデックス1の「みかん」が削除されます。

先頭を削除する場合は、次のように記述します。

C#
fruits.RemoveAt(0);

最後の要素を削除する場合は、Count - 1を指定します。

C#
if (fruits.Count > 0)
{
fruits.RemoveAt(fruits.Count - 1);
}

範囲外のインデックスを指定するとArgumentOutOfRangeExceptionが発生するため、必要に応じて事前に確認しましょう。

5-3. RemoveAllで条件に一致する要素を削除する

条件に一致する要素をすべて削除する場合は、RemoveAllを使用します。

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

numbers.RemoveAll(number => number >= 20);

このコードでは、20以上の要素がすべて削除されます。削除後のListは10、15です。

RemoveAllは削除した要素数を返します。

C#
int removedCount = numbers.RemoveAll(number => number % 2 == 0);

Console.WriteLine($"{removedCount}件削除しました");

文字列の条件も指定できます。

C#
List<string> names = new List<string>
{
"佐藤",
"鈴木",
"佐々木",
"高橋"
};

names.RemoveAll(name => name.StartsWith("佐"));

この場合、「佐藤」と「佐々木」が削除されます。

5-4. Clearですべての要素を削除する

Listの要素をすべて削除するには、Clearを使用します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

fruits.Clear();

Console.WriteLine(fruits.Count);

実行結果は0です。

Clearを実行しても、List変数そのものがnullになるわけではありません。そのため、削除後も同じListに要素を追加できます。

C#
fruits.Clear();
fruits.Add("バナナ");

6. C#のListで要素を検索する方法

6-1. Containsで要素が含まれるか確認する

Listに指定した値が含まれているか確認するには、Containsを使用します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

bool exists = fruits.Contains("みかん");

Console.WriteLine(exists);

実行結果はTrueです。

条件分岐で使う場合は、次のように記述します。

C#
if (fruits.Contains("りんご"))
{
Console.WriteLine("りんごが登録されています");
}

文字列の比較では、大文字と小文字が区別されます。

C#
List<string> names = new List<string>
{
"Alice",
"Bob"
};

Console.WriteLine(names.Contains("alice")); // False

大文字と小文字を無視して検索する場合は、Anyなどを使います。

C#
bool exists = names.Any(
name => string.Equals(
name,
"alice",
StringComparison.OrdinalIgnoreCase
)
);

6-2. IndexOfで要素の位置を検索する

指定した値が何番目にあるか調べるには、IndexOfを使用します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

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

Console.WriteLine(index);

実行結果は1です。

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

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

if (index >= 0)
{
Console.WriteLine($"インデックスは{index}です");
}
else
{
Console.WriteLine("見つかりませんでした");
}

同じ値が複数ある場合は、最初に見つかった位置が返されます。最後に見つかった位置を取得したい場合は、LastIndexOfを使用します。

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

int firstIndex = numbers.IndexOf(10);
int lastIndex = numbers.LastIndexOf(10);

Console.WriteLine(firstIndex); // 0
Console.WriteLine(lastIndex); // 2

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が返されます。

クラスのListから、特定のIDを持つオブジェクトを探すこともできます。

C#
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "ノート", Price = 200 },
new Product { Id = 2, Name = "ペン", Price = 100 }
};

Product? product = products.Find(item => item.Id == 2);

if (product != null)
{
Console.WriteLine(product.Name);
}

参照型のListで対象が見つからなかった場合、Findnullを返します。

一方、intなどの値型では、見つからなかった場合に既定値が返されます。intの既定値は0であるため、List内の0と「見つからなかった」を区別しにくい場合があります。その場合は、FindIndexやLINQを利用する方法を検討しましょう。

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

条件に一致するすべての要素を取得するには、FindAllを使用します。

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

List<int> results = numbers.FindAll(number => number >= 20);

resultsには、20、25、30が格納されます。

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

クラスのListにも使用できます。

C#
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "ノート", Price = 200 },
new Product { Id = 2, Name = "ペン", Price = 100 },
new Product { Id = 3, Name = "ファイル", Price = 300 }
};

List<Product> cheapProducts =
products.FindAll(product => product.Price <= 200);

6-5. LINQのWhere・FirstOrDefaultを使った検索方法

LINQを使うと、Listを柔軟な条件で検索できます。

条件に一致するすべての要素を取得するには、Whereを使用します。

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

List<int> results = numbers
.Where(number => number >= 20)
.ToList();

条件に一致する最初の要素を取得する場合は、FirstOrDefaultを使用します。

C#
Product? product = products
.FirstOrDefault(item => item.Id == 2);

検索結果が存在しない場合、参照型ではnullが返されるため、取得後に確認します。

C#
if (product != null)
{
Console.WriteLine(product.Name);
}
else
{
Console.WriteLine("商品が見つかりませんでした");
}

1件だけ存在することを前提にする場合は、SingleOrDefaultも使用できます。ただし、条件に一致する要素が複数あると例外が発生するため、用途を理解して使い分けましょう。

7. C#のListをループ処理する方法

7-1. foreachでListの要素を順番に処理する

Listの全要素を順番に処理する場合は、foreachが分かりやすく便利です。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
}

varを使って型をコンパイラーに推論させることもできます。

C#
foreach (var fruit in fruits)
{
Console.WriteLine(fruit);
}

クラスのListも同じように処理できます。

C#
foreach (Product product in products)
{
Console.WriteLine(
$"{product.Name}:{product.Price}円"
);
}

インデックスが不要で、すべての要素を順番に処理したい場合は、foreachが適しています。

7-2. for文でインデックスを使って処理する

インデックスを利用したい場合は、for文を使用します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

for (int i = 0; i < fruits.Count; i++)
{
Console.WriteLine($"{i}:{fruits[i]}");
}

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

0:りんご
1:みかん
2:ぶどう

for文では、Listの要素を直接変更できます。

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

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

処理後は20、40、60になります。

7-3. Listをループ中に削除するときの注意点

foreachでListを処理している途中に要素を追加・削除すると、通常はInvalidOperationExceptionが発生します。

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

// foreach (int number in numbers)
// {
// if (number >= 30)
// {
// numbers.Remove(number);
// }
// }

条件に一致する要素を削除するだけであれば、RemoveAllを使うのが簡単です。

C#
numbers.RemoveAll(number => number >= 30);

for文で削除する場合は、後ろから前へループすると安全です。

C#
for (int i = numbers.Count - 1; i >= 0; i--)
{
if (numbers[i] >= 30)
{
numbers.RemoveAt(i);
}
}

前から削除すると、削除後にインデックスが詰まり、次の要素を飛ばしてしまう可能性があります。

たとえば、次のコードは意図しない結果になることがあります。

C#
for (int i = 0; i < numbers.Count; i++)
{
if (numbers[i] >= 20)
{
numbers.RemoveAt(i);
}
}

削除を伴うループでは、RemoveAllまたは後ろ向きのfor文を使いましょう。

7-4. 実務でよく使うループ処理のサンプルコード

商品一覧から在庫がある商品のみを表示する例です。

C#
class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Price { get; set; }
public int Stock { get; set; }
}
C#
List<Product> products = new List<Product>
{
new Product
{
Id = 1,
Name = "ノート",
Price = 200,
Stock = 5
},
new Product
{
Id = 2,
Name = "ペン",
Price = 100,
Stock = 0
},
new Product
{
Id = 3,
Name = "ファイル",
Price = 300,
Stock = 2
}
};

foreach (Product product in products)
{
if (product.Stock <= 0)
{
continue;
}

Console.WriteLine(
$"{product.Name}:{product.Price}円、在庫{product.Stock}個"
);
}

合計金額を計算する例です。

C#
int totalPrice = 0;

foreach (Product product in products)
{
totalPrice += product.Price;
}

Console.WriteLine($"合計:{totalPrice}円");

LINQを使う場合は、Sumでも計算できます。

C#
int totalPrice = products.Sum(product => product.Price);

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>
{
"Charlie",
"Alice",
"Bob"
};

names.Sort();

Sortは元のList自体を書き換えるメソッドです。並び替え前の順番を残したい場合は、別のListを作成してから並び替えます。

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

sortedNumbers.Sort();

クラスのListを特定の項目で並び替えることもできます。

C#
products.Sort(
(product1, product2) =>
product1.Price.CompareTo(product2.Price)
);

8-2. Reverseで順番を反転する

Listの順番を反転するには、Reverseを使用します。

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

numbers.Reverse();

結果は30、20、10です。

Sortと組み合わせると、数値を降順に並べられます。

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

ただし、条件を指定した並び替えでは、LINQのOrderByDescendingを使う方が意図を読み取りやすい場合があります。

8-3. OrderBy・OrderByDescendingで条件付きソートする

LINQのOrderByを使うと、指定した条件で昇順に並び替えられます。

C#
List<Product> sortedProducts = products
.OrderBy(product => product.Price)
.ToList();

降順に並び替える場合は、OrderByDescendingを使います。

C#
List<Product> sortedProducts = products
.OrderByDescending(product => product.Price)
.ToList();

複数の条件を指定する場合は、ThenByまたはThenByDescendingを使用します。

C#
List<Product> sortedProducts = products
.OrderBy(product => product.Price)
.ThenBy(product => product.Name)
.ToList();

このコードでは、価格の昇順で並べた後、価格が同じ商品を名前の昇順で並べます。

OrderByは元のListを変更せず、新しい並び順を返します。一方、Sortは元のListそのものを並び替えます。

8-4. ToArrayでListを配列に変換する

Listを配列に変換するには、ToArrayを使用します。

C#
List<string> fruits = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

string[] fruitArray = fruits.ToArray();

反対に、配列をListに変換する場合は、コンストラクターまたはToListを使用します。

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

List<string> fruits1 =
new List<string>(fruitArray);

List<string> fruits2 =
fruitArray.ToList();

外部ライブラリのメソッドが配列を要求する場合や、要素数を固定したデータとして渡したい場合にToArrayが役立ちます。

9. C#のListでよく使う便利メソッド一覧

9-1. Existsで条件に一致する要素があるか確認する

Existsは、条件に一致する要素が1つでも存在するか確認するメソッドです。

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

bool exists = numbers.Exists(number => number >= 20);

Console.WriteLine(exists);

実行結果はTrueです。

クラスのListでも使用できます。

C#
bool hasExpensiveProduct =
products.Exists(product => product.Price >= 1000);

ExistsList<T>に用意されているメソッドです。

9-2. Anyで要素の有無を判定する

LINQのAnyは、Listに要素があるか、条件に一致する要素があるかを判定できます。

Listが空でないか確認する例です。

C#
if (products.Any())
{
Console.WriteLine("商品が登録されています");
}

条件を指定する例です。

C#
bool hasCheapProduct =
products.Any(product => product.Price <= 200);

Existsと似ていますが、AnyはList以外のさまざまなコレクションにも使用できます。LINQを使う処理では、Anyがよく利用されます。

9-3. Selectで要素を変換する

Selectは、Listの各要素を別の値や型に変換するときに使用します。

数値を2倍にする例です。

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

List<int> doubledNumbers = numbers
.Select(number => number * 2)
.ToList();

商品Listから商品名だけを取り出す例です。

C#
List<string> productNames = products
.Select(product => product.Name)
.ToList();

別のクラスに変換することもできます。

C#
class ProductSummary
{
public string Name { get; set; } = "";
public string DisplayPrice { get; set; } = "";
}
C#
List<ProductSummary> summaries = products
.Select(product => new ProductSummary
{
Name = product.Name,
DisplayPrice = $"{product.Price:N0}円"
})
.ToList();

9-4. Distinctで重複を削除する

重複した要素を取り除くには、LINQのDistinctを使用します。

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

List<int> uniqueNumbers = numbers
.Distinct()
.ToList();

結果は10、20、30です。

文字列にも使用できます。

C#
List<string> names = new List<string>
{
"佐藤",
"鈴木",
"佐藤"
};

List<string> uniqueNames = names
.Distinct()
.ToList();

クラスのListから特定のプロパティを基準に重複を削除する場合は、DistinctByを使用できます。

C#
List<Product> uniqueProducts = products
.DistinctBy(product => product.Id)
.ToList();

利用している.NETのバージョンでDistinctByが使えない場合は、GroupByを利用できます。

C#
List<Product> uniqueProducts = products
.GroupBy(product => product.Id)
.Select(group => group.First())
.ToList();

9-5. List操作で覚えておきたいメソッド早見表

メソッド・プロパティ用途
Add要素を1つ追加する
AddRange複数の要素を追加する
Insert指定位置に要素を追加する
InsertRange指定位置に複数要素を追加する
Count要素数を取得する
Contains指定した値が含まれるか確認する
IndexOf指定した値の位置を取得する
Find条件に一致する最初の要素を取得する
FindAll条件に一致する全要素を取得する
Exists条件に一致する要素があるか確認する
Remove指定した値を削除する
RemoveAt指定位置の要素を削除する
RemoveAll条件に一致する要素をすべて削除する
Clearすべての要素を削除する
SortListを並び替える
Reverse要素の順番を反転する
ToArrayListを配列に変換する
Where条件に一致する要素を抽出する
Select各要素を別の値や型に変換する
Any要素または条件一致の有無を確認する
Distinct重複を除いた結果を取得する
OrderBy昇順に並び替えた結果を取得する
OrderByDescending降順に並び替えた結果を取得する

10. C#のListを使った実践サンプルコード

10-1. 文字列リストを追加・削除・検索するサンプル

文字列のListに対して、追加・削除・検索を行うサンプルです。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
List<string> fruits = new List<string>
{
"りんご",
"みかん"
};

// 要素を追加
fruits.Add("ぶどう");
fruits.Add("バナナ");

// 要素を削除
bool removed = fruits.Remove("みかん");

if (removed)
{
Console.WriteLine("みかんを削除しました");
}

// 要素を検索
string? found =
fruits.Find(fruit => fruit.Contains("ぶ"));

if (found != null)
{
Console.WriteLine($"検索結果:{found}");
}

// 一覧を表示
Console.WriteLine("現在の果物一覧");

foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
}
}
}

10-2. 数値リストを集計するサンプル

数値のListから合計値、平均値、最大値、最小値を求めるサンプルです。

C#
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
static void Main()
{
List<int> scores = new List<int>
{
80,
95,
70,
88,
100
};

int total = scores.Sum();
double average = scores.Average();
int max = scores.Max();
int min = scores.Min();

Console.WriteLine($"合計:{total}");
Console.WriteLine($"平均:{average:F1}");
Console.WriteLine($"最高点:{max}");
Console.WriteLine($"最低点:{min}");

List<int> passedScores = scores
.Where(score => score >= 80)
.ToList();

Console.WriteLine("80点以上の点数");

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

AverageMaxMinは、空のListに対して実行すると例外が発生する場合があります。実務では、事前に要素があるか確認すると安全です。

C#
if (scores.Any())
{
double average = scores.Average();
Console.WriteLine(average);
}

10-3. クラスのListを作成して検索するサンプル

商品クラスのListを作成し、IDや価格で検索するサンプルです。

C#
using System;
using System.Collections.Generic;
using System.Linq;

class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Price { get; set; }
}

class Program
{
static void Main()
{
List<Product> products = new List<Product>
{
new Product
{
Id = 1,
Name = "ノート",
Price = 200
},
new Product
{
Id = 2,
Name = "ペン",
Price = 100
},
new Product
{
Id = 3,
Name = "ファイル",
Price = 300
}
};

Product? product = products
.FirstOrDefault(item => item.Id == 2);

if (product != null)
{
Console.WriteLine(
$"検索結果:{product.Name}、{product.Price}円"
);
}

List<Product> cheapProducts = products
.Where(item => item.Price <= 200)
.OrderBy(item => item.Price)
.ToList();

Console.WriteLine("200円以下の商品");

foreach (Product item in cheapProducts)
{
Console.WriteLine(
$"{item.Name}:{item.Price}円"
);
}
}
}

10-4. ToDoリスト風の簡単なサンプル

Listを使って、簡単なToDoリストを管理するサンプルです。

C#
using System;
using System.Collections.Generic;
using System.Linq;

class TodoItem
{
public int Id { get; set; }
public string Title { get; set; } = "";
public bool IsCompleted { get; set; }
}

class Program
{
static void Main()
{
List<TodoItem> todos = new List<TodoItem>();

// ToDoを追加
todos.Add(new TodoItem
{
Id = 1,
Title = "メールを確認する",
IsCompleted = false
});

todos.Add(new TodoItem
{
Id = 2,
Title = "資料を作成する",
IsCompleted = false
});

todos.Add(new TodoItem
{
Id = 3,
Title = "会議に参加する",
IsCompleted = false
});

// IDが1のToDoを完了にする
TodoItem? target = todos
.FirstOrDefault(todo => todo.Id == 1);

if (target != null)
{
target.IsCompleted = true;
}

// 未完了のToDoを取得
List<TodoItem> incompleteTodos = todos
.Where(todo => !todo.IsCompleted)
.ToList();

Console.WriteLine("未完了のToDo");

foreach (TodoItem todo in incompleteTodos)
{
Console.WriteLine(
$"{todo.Id}:{todo.Title}"
);
}

// IDが2のToDoを削除
todos.RemoveAll(todo => todo.Id == 2);

Console.WriteLine($"残りの件数:{todos.Count}");
}
}

このように、クラスのListを使用すると、複数の関連情報を1つのデータとして扱えます。

11. C#のListで初心者がつまずきやすいポイント

11-1. Listと配列の使い分けで迷う

Listと配列のどちらを使うべきか迷った場合は、要素数が変化するかどうかで判断すると分かりやすいでしょう。

要素数が最初から決まっていて、後から増減しない場合は配列が適しています。

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

要素の追加や削除が必要な場合はListが適しています。

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

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

判断に迷う場合は、次の基準を参考にしてください。

状況適した型
要素数が固定配列
要素数が増減するList
頻繁に追加・削除するList
外部APIが配列を要求する配列
データを検索・並び替えしたいListまたはLINQ
高いパフォーマンスが必要処理内容を測定して判断

11-2. NullReferenceExceptionが発生する原因

List変数がnullの状態でメソッドやプロパティを使うと、NullReferenceExceptionが発生します。

C#
List<string>? names = null;

// names.Add("佐藤");
// Console.WriteLine(names.Count);

Listを使用する前にインスタンスを作成します。

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

names.Add("佐藤");

プロパティとしてListを持つ場合は、宣言時に初期化しておくと安全です。

C#
class Team
{
public List<string> Members { get; set; }
= new List<string>();
}

使用時は次のように記述できます。

C#
Team team = new Team();

team.Members.Add("佐藤");

外部からnullが渡される可能性がある場合は、null合体演算子を使う方法もあります。

C#
List<string>? inputNames = null;

List<string> names =
inputNames ?? new List<string>();

11-3. IndexOutOfRangeExceptionが発生する原因

配列に対して存在しないインデックスを指定すると、IndexOutOfRangeExceptionが発生します。

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

// Console.WriteLine(numbers[2]);

一方、List<T>の範囲外インデックスを指定した場合は、通常ArgumentOutOfRangeExceptionが発生します。

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

// Console.WriteLine(numbers[2]);

どちらの場合も、インデックスが0以上で、要素数未満であることを確認する必要があります。

C#
int index = 1;

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

ループでは、条件をi <= numbers.Countと書かないように注意してください。

C#
// 間違い
// for (int i = 0; i <= numbers.Count; i++)

// 正しい
for (int i = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}

Countが3なら、最後の有効なインデックスは2です。そのため、条件はi < Countとします。

11-4. Listの中身が意図せず変更されるケース

Listは参照型です。別の変数に代入してもList全体が複製されるわけではなく、同じListを参照します。

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

List<int> list2 = list1;

list2.Add(30);

この場合、list1にも30が追加されています。

C#
Console.WriteLine(list1.Count); // 3

別のListとして複製したい場合は、コンストラクターやToListを使います。

C#
List<int> list2 = new List<int>(list1);

または次のように記述します。

C#
List<int> list2 = list1.ToList();

ただし、Listの要素がクラスの場合、この方法で複製されるのはListの入れ物だけです。中のオブジェクトは同じ参照を共有します。

C#
List<Product> copiedProducts =
new List<Product>(products);

copiedProducts[0].Price = 999;

この変更は、元のproducts[0]にも反映されます。

オブジェクトまで独立させたい場合は、各要素から新しいオブジェクトを作成します。

C#
List<Product> copiedProducts = products
.Select(product => new Product
{
Id = product.Id,
Name = product.Name,
Price = product.Price
})
.ToList();

11-5. パフォーマンス面で注意すべきこと

Listは便利ですが、操作内容によって処理コストが異なります。

末尾へのAddは一般的に高速です。一方、先頭や途中へのInsertRemoveAtでは、後ろの要素を移動する必要があります。

C#
numbers.Insert(0, 10);
numbers.RemoveAt(0);

大量の要素を何度も先頭に追加・削除する場合は、Queue<T>LinkedList<T>など、別のコレクションが適している可能性があります。

また、ContainsFindは基本的に先頭から順番に検索します。大量のデータからキーを使って繰り返し検索する場合は、Dictionary<TKey, TValue>の利用を検討しましょう。

追加する要素数がおおよそ分かっている場合は、初期容量を指定できます。

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

初期容量を指定すると、要素追加に伴う内部配列の拡張回数を減らせる場合があります。ただし、小規模な処理で過度に意識する必要はありません。実際に性能問題が発生した場合は、計測したうえで改善することが重要です。

12. C#のListに関するよくある質問

12-1. Listの最大要素数に制限はある?

Listに格納できる要素数には実質的な上限があります。上限は、実行環境、使用可能なメモリ、要素の型、.NETの実装などによって変わります。

Countやインデックスにはintが使われるため、理論上は非常に大きな数の要素を扱えます。しかし、実際には上限に到達する前にメモリ不足になることが一般的です。

大量データを扱う場合は、すべてをListに読み込むのではなく、次の方法も検討しましょう。

  • データを一定件数ずつ処理する

  • データベースで絞り込んでから取得する

  • IEnumerable<T>で遅延実行する

  • ファイルを1行ずつ読み込む

  • ページングを利用する

12-2. ListとDictionaryはどう使い分ける?

Listは、複数の要素を順番に管理する場合に適しています。

C#
List<string> names = new List<string>
{
"佐藤",
"鈴木",
"高橋"
};

Dictionaryは、キーと値の組み合わせでデータを管理する場合に適しています。

C#
Dictionary<int, string> users =
new Dictionary<int, string>
{
{ 1, "佐藤" },
{ 2, "鈴木" },
{ 3, "高橋" }
};

IDを指定して何度も検索する処理では、Dictionaryが便利です。

C#
string userName = users[2];

使い分けの目安は次のとおりです。

目的適したコレクション
順番にデータを管理するList
インデックスで取得するList
キーで素早く検索するDictionary
同じ値を複数保存するList
キーを重複させたくないDictionary

12-3. Listの重複要素を削除するには?

数値や文字列の重複を削除する場合は、Distinctを使用します。

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

numbers = numbers
.Distinct()
.ToList();

特定のプロパティを基準に重複を削除する場合は、DistinctByが便利です。

C#
products = products
.DistinctBy(product => product.Id)
.ToList();

重複を最初から許可したくない場合は、HashSet<T>を使用する方法もあります。

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

names.Add("佐藤");
names.Add("鈴木");
names.Add("佐藤");

2回目の「佐藤」は追加されません。

12-4. Listを空にするにはClearとnewのどちらがよい?

既存のListをそのまま使い続けながら、要素だけを削除したい場合はClearを使います。

C#
products.Clear();

変数に新しいListを割り当てたい場合は、newを使います。

C#
products = new List<Product>();

他の変数も同じListを参照している場合、両者の動作は異なります。

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

List<int> list2 = list1;

list1.Clear();

Console.WriteLine(list2.Count); // 0

Clearでは同じListの中身を削除するため、list2から見ても空になります。

一方、新しいListを代入した場合は、list2が参照する元のListには要素が残ります。

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

List<int> list2 = list1;

list1 = new List<int>();

Console.WriteLine(list1.Count); // 0
Console.WriteLine(list2.Count); // 2

基本的には、同じListを再利用するならClear、新しいListに置き換えるならnewと使い分けます。

12-5. Listは実務でどのくらい使われる?

Listは、C#の実務で非常によく使われるコレクションの1つです。

たとえば、次のような処理で利用されます。

  • データベースから取得した検索結果

  • Web APIのレスポンスデータ

  • 画面に表示する商品やユーザーの一覧

  • バッチ処理の対象データ

  • 入力されたフォーム項目

  • エラーやメッセージの一覧

  • ゲーム内のキャラクターやアイテム

  • ファイルから読み込んだ行データ

ASP.NET CoreやEntity Framework Coreを使う開発でも、複数レコードをList<T>として扱う場面があります。

C#
List<Product> products = await dbContext.Products
.Where(product => product.Price <= 1000)
.ToListAsync();

Listの基本操作に加えて、LINQによる検索・絞り込み・並び替えを理解しておくと、多くのC#プロジェクトで役立ちます。

まとめ

C#のList<T>は、複数のデータを順番に管理でき、要素を自由に追加・削除できるコレクションです。

Listを使用するには、格納するデータ型を指定して宣言します。

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

要素の追加にはAddAddRange、削除にはRemoveRemoveAtRemoveAllClearを使用します。

C#
names.Add("佐藤");
names.Remove("佐藤");

検索にはContainsIndexOfFindFindAllが利用できます。さらにLINQを使うと、WhereFirstOrDefaultAnySelectOrderByなどによって、より柔軟にデータを操作できます。

Listを使うときは、次の点に注意しましょう。

  • インデックスは0から始まる

  • 要素数はCountで確認する

  • Listを使う前にインスタンスを作成する

  • foreachの途中でListを変更しない

  • Listを別の変数に代入すると同じListを参照する

  • 大量データの繰り返し検索ではDictionaryなども検討する

まずはAddCount、インデックスアクセス、foreachRemoveの5つから覚えると、Listの基本的な操作ができるようになります。その後、FindやLINQを使った検索・並び替えを身につけることで、実務的なデータ処理にも対応できるようになるでしょう。