C# Listに要素を追加する方法|Add・AddRangeの使い方とエラー対処まで解説
はじめに
C#で複数のデータを扱うときによく使われるのが、List<T>です。List<T>に要素を追加する基本的な方法は、1件ずつ追加するAddメソッドと、複数件をまとめて追加するAddRangeメソッドです。
var fruits = new List<string>();fruits.Add("りんご");fruits.AddRange(new[] { "みかん", "ぶどう" });
要素を指定位置に追加したい場合はInsert、複数の要素を途中に挿入したい場合はInsertRangeを使用します。
本記事では、C#のListに要素を追加する方法を、文字列・数値・オブジェクトの具体例とともに解説します。重複やnullを避ける方法、要素を追加できないときの原因、例外への対処法、パフォーマンスを意識した書き方まで確認していきましょう。
1. C#のListに要素を追加する基本
1-1. Listとは
List<T>は、同じ型の要素を順番に格納できるジェネリックコレクションです。配列と同じようにインデックスで要素へアクセスできますが、必要に応じて要素数を増減できる点が特徴です。
var names = new List<string>();names.Add("田中");names.Add("佐藤");
Console.WriteLine(names[0]); // 田中Console.WriteLine(names.Count); // 2
<T>には、Listへ格納する要素の型を指定します。
List<string> names; // 文字列List<int> numbers; // 整数List<Product> products; // Product型のオブジェクトList<T>を利用するには、通常は次の名前空間が必要です。
using System.Collections.Generic;プロジェクトで暗黙的なusingが有効になっている場合は、明示的に記述しなくても利用できることがあります。
1-2. Listと配列の違い
配列は、作成した時点で長さが決まります。
string[] fruits = new string[3];一方、ListはAddやRemoveによって要素数を変更できます。
var fruits = new List<string>();fruits.Add("りんご");fruits.Add("みかん");fruits.Remove("りんご");
主な違いは次のとおりです。
| 比較項目 | List | 配列 |
|---|---|---|
| 要素数 | 追加・削除できる | 作成後は固定 |
| 末尾への追加 | Addを使う | Addは使えない |
| 複数要素の追加 | AddRangeを使える | 新しい配列の作成などが必要 |
| 要素数の取得 | Count | Length |
| インデックスアクセス | 可能 | 可能 |
データ件数が変わる可能性がある場合は、配列よりもListが扱いやすいことが多いでしょう。
1-3. Listを宣言・初期化する方法
空のListを作成する基本的な書き方は次のとおりです。
List<string> fruits = new List<string>();型推論を使うと、より簡潔に記述できます。
var fruits = new List<string>();宣言と同時に要素を設定することも可能です。
var fruits = new List<string>{"りんご","みかん","ぶどう"};変数の宣言だけを行い、インスタンスを作成していない状態では、Addを呼び出せません。
List<string>? fruits = null;// NullReferenceExceptionの原因になるfruits.Add("りんご");
Listへ要素を追加する前に、new List<T>()などで初期化しておく必要があります。
1-4. Listに追加できる要素の型
List<T>には、型引数Tと互換性のある要素を追加できます。
var numbers = new List<int>();numbers.Add(10);numbers.Add(20);
List<int>へ文字列を追加することはできません。
var numbers = new List<int>();// コンパイルエラーnumbers.Add("10");
文字列の数値を追加したい場合は、事前に変換します。
var numbers = new List<int>();string input = "10";if (int.TryParse(input, out int value)){numbers.Add(value);}
基底クラスやインターフェースを型引数にした場合は、それらを継承または実装したオブジェクトを追加できます。
var animals = new List<Animal>();animals.Add(new Dog());animals.Add(new Cat());
2. AddメソッドでListに要素を1つ追加する方法
2-1. Addメソッドの基本構文
Addメソッドは、Listの末尾に要素を1件追加します。
list.Add(追加する要素);具体例は次のとおりです。
var fruits = new List<string>();fruits.Add("りんご");fruits.Add("みかん");
追加後のListは次の状態になります。
りんごみかんAddは戻り値を返しません。追加後の要素数が必要な場合は、Countプロパティを確認します。
fruits.Add("ぶどう");Console.WriteLine(fruits.Count); // 3
2-2. 文字列をListに追加する
文字列を追加する場合は、List<string>を使用します。
var names = new List<string>();names.Add("山田");names.Add("鈴木");names.Add("高橋");
追加された内容は、foreachで確認できます。
foreach (string name in names){Console.WriteLine(name);}空文字列も通常の文字列として追加できます。
names.Add("");空文字列や空白だけの文字列を除外したい場合は、string.IsNullOrWhiteSpaceを使います。
string? name = "佐藤";if (!string.IsNullOrWhiteSpace(name)){names.Add(name);}
2-3. 数値をListに追加する
整数を追加する場合は、List<int>を使用します。
var scores = new List<int>();scores.Add(80);scores.Add(95);scores.Add(70);
小数を扱う場合は、目的に応じてdoubleやdecimalを指定します。
var temperatures = new List<double>();temperatures.Add(25.5);temperatures.Add(26.8);
金額のように10進数で正確に扱いたい値には、decimalが適しています。
var prices = new List<decimal>();prices.Add(1200m);prices.Add(980.50m);
2-4. オブジェクトやクラスのインスタンスを追加する
独自クラスのインスタンスもListへ追加できます。
public class Product{public int Id { get; set; }public string Name { get; set; } = "";public decimal Price { get; set; }}ProductのListへ要素を追加する例は次のとおりです。
var products = new List<Product>();var product = new Product{Id = 1,Name = "キーボード",Price = 5000m};
products.Add(product);
オブジェクト初期化子を使って、直接追加することもできます。
products.Add(new Product{Id = 2,Name = "マウス",Price = 3000m});追加したオブジェクトは、List内にコピーされるのではなく、参照型の場合は同じインスタンスへの参照として格納されます。
var product = new Product{Id = 3,Name = "モニター",Price = 30000m};products.Add(product);
product.Price = 28000m;
Console.WriteLine(products[0].Price);
Listへ追加した後に元のオブジェクトを変更すると、Listから参照した内容にも変更が反映されます。
2-5. 条件に応じて要素を追加する
if文と組み合わせると、条件を満たす要素だけを追加できます。
var scores = new List<int>();int score = 85;if (score >= 60){scores.Add(score);}
入力値が有効な場合だけ追加する例は次のとおりです。
var ages = new List<int>();string input = "20";if (int.TryParse(input, out int age) && age >= 0){ages.Add(age);}
三項演算子で追加する値を切り替えることもできますが、追加するかどうかを判断する用途ではif文のほうが読みやすいことが一般的です。
2-6. ループ処理で複数の要素を追加する
forやforeachの中でAddを呼び出すと、複数の要素を1件ずつ追加できます。
var numbers = new List<int>();for (int i = 1; i <= 5; i++){numbers.Add(i);}
結果は次のとおりです。
1, 2, 3, 4, 5条件に一致する要素だけを追加することもできます。
var evenNumbers = new List<int>();for (int i = 1; i <= 10; i++){if (i % 2 == 0){evenNumbers.Add(i);}}
既存のコレクションから加工しながら追加する場合も、ループ処理が便利です。
var source = new[] { 1, 2, 3 };var doubled = new List<int>();foreach (int number in source){doubled.Add(number * 2);}
単純に既存コレクションの全要素を追加するだけであれば、AddRangeを使うと簡潔に記述できます。
3. AddRangeメソッドで複数の要素を一括追加する方法
3-1. AddRangeメソッドの基本構文
AddRangeメソッドは、複数の要素をListの末尾へまとめて追加します。
list.AddRange(追加するコレクション);配列を追加する例は次のとおりです。
var fruits = new List<string>();fruits.AddRange(new[] { "りんご", "みかん", "ぶどう" });
AddRangeに渡す値は、対象のListと互換性のある要素を列挙できるIEnumerable<T>である必要があります。
3-2. 別のListの要素を追加する
別のListが持つ要素を追加する場合は、そのListをAddRangeへ渡します。
var first = new List<string>{"りんご","みかん"};var second = new List<string>{"ぶどう","もも"};
first.AddRange(second);
firstの内容は次のようになります。
りんご, みかん, ぶどう, ももsecond自体は変更されません。また、Listを入れ子にするのではなく、second内の各要素がfirstへ追加されます。
自分自身を渡すことも可能ですが、元の要素が複製されます。
var numbers = new List<int> { 1, 2, 3 };numbers.AddRange(numbers);
// 1, 2, 3, 1, 2, 3
意図しない重複につながりやすいため、自分自身を追加していないか確認しましょう。
3-3. 配列の要素をListに追加する
配列はIEnumerable<T>として扱えるため、そのままAddRangeへ渡せます。
var numbers = new List<int> { 1, 2 };int[] additionalNumbers = { 3, 4, 5 };numbers.AddRange(additionalNumbers);
文字列配列も同様です。
var names = new List<string>();string[] newNames = { "田中", "佐藤", "鈴木" };names.AddRange(newNames);
要素の型が一致しない配列は追加できません。
var numbers = new List<int>();string[] values = { "1", "2" };// コンパイルエラー// numbers.AddRange(values);
型変換が必要な場合は、変換後の値を追加します。
var numbers = new List<int>();string[] values = { "1", "2", "3" };numbers.AddRange(values.Select(int.Parse));
入力内容が保証されない場合は、int.Parseではなくint.TryParseを使って不正な値を除外するほうが安全です。
3-4. IEnumerableの要素を追加する
AddRangeは、IEnumerable<T>を実装する列挙可能なデータを受け取れます。
IEnumerable<int> GetNumbers(){yield return 10;yield return 20;yield return 30;}var numbers = new List<int>();
numbers.AddRange(GetNumbers());
LINQで抽出・変換した結果も追加できます。
var source = new List<int> { 1, 2, 3, 4, 5 };var evenNumbers = new List<int>();evenNumbers.AddRange(source.Where(x => x % 2 == 0));
変換後の要素を追加する例は次のとおりです。
var names = new[] { "tanaka", "sato", "suzuki" };var upperNames = new List<string>();upperNames.AddRange(names.Select(name => name.ToUpperInvariant()));
AddRangeを呼び出した時点で、渡されたIEnumerable<T>が列挙され、取得された要素がListへ追加されます。
3-5. Addを繰り返す方法とAddRangeの違い
複数の要素は、Addを繰り返しても追加できます。
var numbers = new List<int>();numbers.Add(1);numbers.Add(2);numbers.Add(3);
AddRangeを使うと次のように書けます。
var numbers = new List<int>();numbers.AddRange(new[] { 1, 2, 3 });
既存コレクションの要素をそのまま追加する場合は、AddRangeのほうが意図を明確に表現できます。また、追加件数を把握できるコレクションでは、内部容量の拡張回数を抑えられる可能性があります。
一方、要素ごとに条件判定や変換、ログ出力などを行う場合は、Addをループ内で使用するほうが適しています。
foreach (int number in source){if (number > 0){numbers.Add(number * 2);}}単純な絞り込みや変換なら、LINQとAddRangeを組み合わせる方法もあります。
numbers.AddRange(source.Where(number => number > 0).Select(number => number * 2));3-6. AddRangeを使う際の注意点
AddRangeへnullを渡すと、ArgumentNullExceptionが発生します。
var numbers = new List<int>();IEnumerable<int>? source = null;// ArgumentNullExceptionnumbers.AddRange(source);
呼び出す前にnullを確認します。
if (source is not null){numbers.AddRange(source);}空のコレクションを渡してもエラーにはなりません。
var numbers = new List<int>();var empty = Array.Empty<int>();numbers.AddRange(empty);
参照型のListには、null要素を含むコレクションを追加できる場合があります。
var names = new List<string?>();names.AddRange(new string?[] { "田中", null, "佐藤" });
null要素を許可したくない場合は、追加前に除外します。
IEnumerable<string?> source = new string?[] { "田中", null, "佐藤" };var names = new List<string>();names.AddRange(source.OfType<string>());
4. Listの指定した位置に要素を追加する方法
4-1. Insertメソッドで指定位置に1件追加する
Insertメソッドを使うと、指定したインデックスへ要素を1件追加できます。
list.Insert(index, item);例として、インデックス1へ要素を追加します。
var fruits = new List<string>{"りんご","ぶどう"};fruits.Insert(1, "みかん");
結果は次のとおりです。
りんご, みかん, ぶどう追加位置以降にあった要素は、1つ後ろへ移動します。
4-2. InsertRangeメソッドで指定位置に複数追加する
InsertRangeメソッドを使うと、指定位置へ複数の要素を追加できます。
list.InsertRange(index, collection);具体例は次のとおりです。
var numbers = new List<int> { 1, 4, 5 };var additional = new[] { 2, 3 };numbers.InsertRange(1, additional);
結果は次のようになります。
1, 2, 3, 4, 5InsertRangeへnullを渡すとArgumentNullExceptionが発生します。
4-3. 先頭に要素を追加する
Listの先頭へ要素を追加する場合は、インデックス0を指定します。
var numbers = new List<int> { 2, 3, 4 };numbers.Insert(0, 1);
複数の要素を先頭へ追加する場合は、InsertRangeを使います。
var numbers = new List<int> { 3, 4 };var firstNumbers = new[] { 1, 2 };numbers.InsertRange(0, firstNumbers);
ただし、Listの先頭への追加では、既存要素を後ろへ移動する処理が必要です。先頭への追加を頻繁に行う用途では、LinkedList<T>やQueue<T>など、別のコレクションが適している場合があります。
4-4. 末尾に要素を追加する
末尾へ1件追加するだけであれば、通常はAddを使用します。
numbers.Add(10);Insertで末尾へ追加する場合は、インデックスにCountを指定します。
numbers.Insert(numbers.Count, 10);どちらも末尾への追加になりますが、目的が明確で簡潔なAddを使うのが一般的です。
複数の要素を末尾に追加する場合はAddRangeを使用します。
numbers.AddRange(new[] { 11, 12, 13 });4-5. インデックスを指定する際の注意点
InsertとInsertRangeに指定できるインデックスは、0以上Count以下です。
var numbers = new List<int> { 10, 20, 30 };// 有効numbers.Insert(0, 5);
// 有効:末尾へ追加numbers.Insert(numbers.Count, 40);
負の値やCountより大きな値を指定すると、ArgumentOutOfRangeExceptionが発生します。
// ArgumentOutOfRangeExceptionnumbers.Insert(-1, 0);// ArgumentOutOfRangeExceptionnumbers.Insert(numbers.Count + 1, 50);
要素を取得・更新するときのインデックスは0以上Count - 1以下ですが、挿入では末尾を表すCountも指定できます。この違いに注意しましょう。
5. Listの初期化と同時に要素を追加する方法
5-1. コレクション初期化子を使う
コレクション初期化子を使うと、Listの作成と要素追加をまとめて記述できます。
var fruits = new List<string>{"りんご","みかん","ぶどう"};内部的には、各要素に対してAddを呼び出せるコレクションで利用できる構文です。
後からAddで追加することもできます。
fruits.Add("もも");初期値がコード上で決まっている場合は、コレクション初期化子を使うと読みやすくなります。
5-2. 配列や既存コレクションから初期化する
List<T>のコンストラクターへ既存コレクションを渡すと、その要素を含む新しいListを作成できます。
string[] source = { "りんご", "みかん", "ぶどう" };var fruits = new List<string>(source);
別のListから作成することもできます。
var original = new List<int> { 1, 2, 3 };var copy = new List<int>(original);copyへ要素を追加しても、originalの要素数は変わりません。
copy.Add(4);Console.WriteLine(original.Count); // 3Console.WriteLine(copy.Count); // 4
ただし、要素が参照型の場合、List自体は別でも各要素が同じオブジェクトを参照する点に注意が必要です。
5-3. オブジェクトのListを初期化する
独自クラスのListも、コレクション初期化子で作成できます。
var products = new List<Product>{new Product{Id = 1,Name = "キーボード",Price = 5000m},new Product{Id = 2,Name = "マウス",Price = 3000m}};ターゲット型を利用できる状況では、new()を使って簡潔に書くこともできます。
List<Product> products = new(){new(){Id = 1,Name = "キーボード",Price = 5000m},new(){Id = 2,Name = "マウス",Price = 3000m}};5-4. C# 12のコレクション式を使う
C# 12以降では、角括弧を使ったコレクション式でListを初期化できます。
List<string> fruits = ["りんご", "みかん", "ぶどう"];空のListも簡潔に作成できます。
List<int> numbers = [];既存のコレクションを展開する場合は、スプレッド要素..を使用します。
int[] first = [1, 2];List<int> numbers = [.. first, 3, 4];別のListを含めることもできます。
List<string> first = ["りんご", "みかん"];List<string> all = [.. first, "ぶどう", "もも"];コレクション式はターゲット型を利用して変換されるため、次のように受け取り先の型を明示します。
List<int> numbers = [1, 2, 3];int[] array = [1, 2, 3];利用するには、プロジェクトのC#言語バージョンがC# 12以降である必要があります。
5-5. 初期化後に要素を追加する場合との使い分け
作成時点で要素が決まっている場合は、コレクション初期化子やコレクション式が適しています。
List<string> roles = ["Admin", "User", "Guest"];実行時の入力や処理結果に応じて追加する場合は、空のListを作成してからAddやAddRangeを使います。
var results = new List<int>();foreach (int value in source){if (value > 0){results.Add(value);}}
既存コレクションをそのままList化するだけなら、コンストラクターやToListも選択肢です。
var results = source.ToList();6. 重複やnullを避けてListに要素を追加する方法
6-1. Containsで重複を確認してから追加する
同じ値がすでに存在するか確認するには、Containsを使います。
var fruits = new List<string>{"りんご","みかん"};string newFruit = "りんご";
if (!fruits.Contains(newFruit)){fruits.Add(newFruit);}
これにより、同じ文字列が重複して追加されるのを防げます。
大文字と小文字を区別せずに比較したい場合は、Anyと文字列比較を組み合わせます。
string newFruit = "APPLE";bool exists = fruits.Any(fruit => string.Equals(fruit,newFruit,StringComparison.OrdinalIgnoreCase));
if (!exists){fruits.Add(newFruit);}
6-2. Anyで条件に一致する要素を確認する
オブジェクトの重複を特定のプロパティで判定する場合は、LINQのAnyが便利です。
var products = new List<Product>();var newProduct = new Product{Id = 1,Name = "キーボード",Price = 5000m};if (!products.Any(product => product.Id == newProduct.Id)){products.Add(newProduct);}
複数のプロパティを条件にすることもできます。
bool exists = products.Any(product =>product.Name == newProduct.Name &&product.Price == newProduct.Price);if (!exists){products.Add(newProduct);}
データベースの主キーに相当するような一意な値がある場合は、その値を使って重複を判定すると処理の意図が明確になります。
6-3. nullをチェックしてから追加する
参照型の値がnullでない場合だけ追加するには、is not nullを使用します。
var names = new List<string>();string? name = GetName();if (name is not null){names.Add(name);}
空文字列も除外する場合は、次のようにします。
if (!string.IsNullOrWhiteSpace(name)){names.Add(name);}オブジェクトの場合も同様です。
Product? product = FindProduct();if (product is not null){products.Add(product);}
6-4. null条件演算子を使って安全に追加する
List自体がnullになる可能性がある場合は、null条件演算子?.を使えます。
List<string>? names = GetNames();names?.Add("田中");
namesがnullの場合、Addは実行されません。例外を避けられる一方で、要素が追加されなかったことに気づきにくくなる可能性があります。
Listが必ず必要な処理では、nullの場合に初期化したほうが適切です。
names ??= new List<string>();names.Add("田中");プロパティとしてListを持つ場合は、宣言時に初期化しておくと安全です。
public class Order{public List<Product> Products { get; } = new();}6-5. 重複を許可しない場合にHashSetを検討する
Listは重複する要素を格納できます。重複を許可しないことが主要な要件であれば、HashSet<T>の利用を検討しましょう。
var names = new HashSet<string>();names.Add("田中");names.Add("佐藤");names.Add("田中");
HashSet<T>.Addは、追加に成功した場合はtrue、すでに同じ要素が存在する場合はfalseを返します。
bool added = names.Add("田中");if (!added){Console.WriteLine("すでに登録されています。");}
大文字と小文字を区別しない文字列の集合も作成できます。
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);順序やインデックスアクセスが重要ならList、重複排除や集合演算が重要ならHashSet<T>というように使い分けます。
7. Listへの要素追加で発生する主なエラーと対処法
7-1. NullReferenceExceptionが発生する原因と対処法
List変数がnullの状態でAddを呼び出すと、NullReferenceExceptionが発生します。
List<string>? names = null;names.Add("田中");
Listを初期化してから追加します。
var names = new List<string>();names.Add("田中");後から値が設定される可能性がある場合は、null合体代入演算子を利用できます。
names ??= new List<string>();names.Add("田中");クラスのフィールドやプロパティでは、宣言時に初期化する方法も有効です。
public List<string> Names { get; } = new();7-2. ArgumentNullExceptionが発生する原因と対処法
AddRangeやInsertRangeへnullのコレクションを渡すと、ArgumentNullExceptionが発生します。
var numbers = new List<int>();IEnumerable<int>? source = null;numbers.AddRange(source);
追加元を事前に確認します。
if (source is not null){numbers.AddRange(source);}nullを空の列挙として扱う方法もあります。
numbers.AddRange(source ?? Enumerable.Empty<int>());C# 12以降で型が明確な場合は、空のコレクション式を使う書き方もできます。
numbers.AddRange(source ?? []);7-3. ArgumentOutOfRangeExceptionが発生する原因と対処法
InsertやInsertRangeで無効なインデックスを指定すると、ArgumentOutOfRangeExceptionが発生します。
var numbers = new List<int> { 1, 2, 3 };numbers.Insert(10, 4);
挿入位置が0以上Count以下であることを確認します。
int index = 2;if (index >= 0 && index <= numbers.Count){numbers.Insert(index, 4);}
インデックスをListの範囲内に収めたい場合は、Math.Clampを使う方法もあります。
int safeIndex = Math.Clamp(index, 0, numbers.Count);numbers.Insert(safeIndex, 4);
ただし、不正なインデックスを自動補正すると不具合を隠す可能性があります。入力値を補正すべきか、例外として扱うべきかは処理の要件に応じて判断してください。
7-4. 型が一致せずコンパイルエラーになる場合
List<T>へ追加できるのは、Tと互換性のある値です。
var numbers = new List<int>();// コンパイルエラーnumbers.Add("100");
文字列を整数へ変換してから追加します。
string input = "100";if (int.TryParse(input, out int number)){numbers.Add(number);}
オブジェクトの型が異なる場合は、継承関係やインターフェースを確認します。
var dogs = new List<Dog>();// CatがDogを継承していなければ追加できない// dogs.Add(new Cat());
異なる種類のオブジェクトを同じListで扱う場合は、共通の基底クラスやインターフェースを使用します。
var animals = new List<IAnimal>();animals.Add(new Dog());animals.Add(new Cat());
7-5. foreach中の追加でInvalidOperationExceptionが発生する場合
同じListをforeachで列挙している最中に要素を追加すると、InvalidOperationExceptionが発生します。
var numbers = new List<int> { 1, 2, 3 };foreach (int number in numbers){numbers.Add(number * 10);}
追加対象を別のListへ保存し、ループ終了後にまとめて追加します。
var numbers = new List<int> { 1, 2, 3 };var additions = new List<int>();foreach (int number in numbers){additions.Add(number * 10);}
numbers.AddRange(additions);
スナップショットを作成して列挙する方法もあります。
foreach (int number in numbers.ToList()){numbers.Add(number * 10);}インデックスを使ったforループも利用できますが、追加によってCountが増え続けると無限ループになる可能性があります。
int originalCount = numbers.Count;for (int i = 0; i < originalCount; i++){numbers.Add(numbers[i] * 10);}
処理開始時の件数を保存しておくことが重要です。
7-6. 読み取り専用コレクションに追加できない場合
IReadOnlyList<T>やReadOnlyCollection<T>は、利用側から直接要素を追加するためのコレクションではありません。
IReadOnlyList<string> names = new List<string>{"田中","佐藤"};// Addメソッドは利用できない// names.Add("鈴木");
変更が必要な場合は、書き込み可能なListを新しく作成します。
var editableNames = names.ToList();editableNames.Add("鈴木");
List<T>.AsReadOnlyで作成した読み取り専用ビューは、元のListが変更されると表示内容にも反映されます。
var names = new List<string> { "田中" };var readOnlyNames = names.AsReadOnly();names.Add("佐藤");
Console.WriteLine(readOnlyNames.Count); // 2
読み取り専用ビューをキャストして変更しようとすると、NotSupportedExceptionが発生する場合があります。
7-7. 固定長配列にAddメソッドを使えない場合
配列にはAddメソッドがありません。
int[] numbers = { 1, 2, 3 };// コンパイルエラー// numbers.Add(4);
要素を追加したい場合は、Listへ変換します。
var numberList = numbers.ToList();numberList.Add(4);
最後に配列へ戻すこともできます。
int[] result = numberList.ToArray();配列のサイズを変更するArray.Resizeもあります。
int[] numbers = { 1, 2, 3 };Array.Resize(ref numbers, numbers.Length + 1);numbers[^1] = 4;
追加や削除を繰り返す用途では、通常はListのほうが扱いやすいでしょう。
8. Listに要素を追加できない場合のチェックポイント
8-1. Listが正しく初期化されているか確認する
最初に、Listがnullになっていないか確認します。
List<string>? names = null;次のようにインスタンスを作成している必要があります。
var names = new List<string>();デバッグ時には、追加直前の変数を確認します。
if (names is null){throw new InvalidOperationException("namesが初期化されていません。");}names.Add("田中");
メソッドの引数としてListを受け取る場合は、nullチェックを追加できます。
void AddName(List<string> names, string name){ArgumentNullException.ThrowIfNull(names);names.Add(name);
}
8-2. 追加する値の型が一致しているか確認する
Listの型引数と、追加する値の型を確認します。
var numbers = new List<int>();このListへ追加できるのは、基本的にintとして扱える値です。
numbers.Add(10);入力値が文字列なら、型変換が必要です。
string input = "10";if (int.TryParse(input, out int value)){numbers.Add(value);}
List<BaseClass>へ派生クラスのインスタンスは追加できますが、List<DerivedClass>へ基底クラスのインスタンスはそのまま追加できません。
8-3. 追加処理が条件分岐内で実行されているか確認する
エラーが発生していなくても、条件を満たしていないためAddが実行されていない場合があります。
int score = 50;if (score >= 60){scores.Add(score);}
ブレークポイントやログを使い、条件分岐へ入っているか確認します。
if (score >= 60){Console.WriteLine("追加処理を実行します。");scores.Add(score);}else{Console.WriteLine($"条件不一致: score={score}");}returnやcontinueによって、追加処理まで到達していないケースにも注意してください。
foreach (int value in source){if (value < 0){continue;}results.Add(value);
}
8-4. LINQのAppendが元のListを変更しない点に注意する
LINQのAppendは、元のListを直接変更しません。要素を末尾に追加した新しい列挙シーケンスを返します。
var numbers = new List<int> { 1, 2, 3 };numbers.Append(4);
Console.WriteLine(numbers.Count); // 3
元のListを変更したい場合は、Addを使います。
numbers.Add(4);Appendの結果から新しいListを作成する場合は、戻り値を受け取ってToListを呼び出します。
numbers = numbers.Append(4).ToList();複数の要素を追加した新しいシーケンスを作る場合は、Concatを利用できます。
numbers = numbers.Concat(new[] { 4, 5 }).ToList();元のListを変更するのか、新しいコレクションを作成するのかを明確に使い分けましょう。
8-5. 非同期処理や複数スレッドによる競合を確認する
List<T>は、複数スレッドから同時に書き込むことを前提としたコレクションではありません。
var numbers = new List<int>();Parallel.For(0, 1000, i =>{numbers.Add(i);});
このようなコードでは、要素の欠落や例外など、予測できない結果につながる可能性があります。
単純な対策として、ロックを使って追加処理を同期できます。
var numbers = new List<int>();var syncObject = new object();Parallel.For(0, 1000, i =>{lock (syncObject){numbers.Add(i);}});
用途によっては、ConcurrentBag<T>やConcurrentQueue<T>などのスレッドセーフなコレクションを検討します。
var numbers = new ConcurrentBag<int>();Parallel.For(0, 1000, i =>{numbers.Add(i);});
非同期処理であっても、必ず複数スレッドになるとは限りません。しかし、同じListを複数の処理から同時に変更する可能性があるなら、共有状態の設計を見直す必要があります。
9. Listへ効率よく要素を追加するためのポイント
9-1. AddとAddRangeを使い分ける
要素を1件だけ追加する場合はAddを使用します。
numbers.Add(10);既存のコレクションから複数件をまとめて追加する場合は、AddRangeが適しています。
numbers.AddRange(new[] { 20, 30, 40 });要素ごとに複雑な処理が必要な場合は、ループ内でAddを使います。
foreach (int value in source){if (value > 0){numbers.Add(ConvertValue(value));}}処理内容を自然に表現できるメソッドを選ぶことが、可読性と保守性の向上につながります。
9-2. Capacityを指定して再割り当てを減らす
List<T>は内部的に要素を保持するための領域を持っています。その容量を表すのがCapacity、実際に格納されている要素数を表すのがCountです。
追加予定件数が事前に分かっている場合は、コンストラクターで初期容量を指定できます。
var numbers = new List<int>(1000);for (int i = 0; i < 1000; i++){numbers.Add(i);}
既存のListについて、最低限必要な容量を確保する場合はEnsureCapacityを使用できます。
var numbers = new List<int>();numbers.EnsureCapacity(1000);
初期容量は要素数ではありません。次のListのCountは0です。
var numbers = new List<int>(1000);Console.WriteLine(numbers.Count); // 0Console.WriteLine(numbers.Capacity); // 1000以上
容量を指定しただけでは、要素そのものは追加されません。
9-3. 大量データ追加時のパフォーマンスを意識する
大量のデータを追加する場合は、次の点を意識します。
追加件数が分かるなら初期容量を指定する
既存コレクションをまとめて追加するなら
AddRangeを使う不要な
ToListや配列変換を繰り返さないループ内で重い変換や検索を繰り返さない
重複チェックが多い場合は
HashSet<T>も検討する
例えば、Listに対して毎回Containsを実行しながら大量の要素を追加すると、要素数が増えるほど検索コストが大きくなります。
foreach (int value in source){if (!numbers.Contains(value)){numbers.Add(value);}}重複排除が目的なら、一時的にHashSet<T>を使う方法があります。
var uniqueNumbers = new HashSet<int>(numbers);foreach (int value in source){uniqueNumbers.Add(value);}
numbers = uniqueNumbers.ToList();
ただし、最適な方法はデータ量や実行頻度、順序の要件によって異なります。性能上の問題が実際にある場合は、計測したうえで改善しましょう。
9-4. 複数スレッドではConcurrentBagなどを検討する
複数スレッドから同時に追加する場合は、System.Collections.Concurrent名前空間のコレクションを検討します。
順序を重視しない収集にはConcurrentBag<T>を利用できます。
var results = new ConcurrentBag<int>();Parallel.ForEach(source, item =>{int result = Process(item);results.Add(result);});
先入れ先出しの順序が必要なら、ConcurrentQueue<T>が候補です。
var queue = new ConcurrentQueue<int>();queue.Enqueue(1);queue.Enqueue(2);
キーと値を管理する場合は、ConcurrentDictionary<TKey, TValue>を利用できます。
Listをlockで保護する方法もありますが、ロック範囲や処理量によっては並列処理の利点が小さくなることがあります。
9-5. 用途に適したコレクションを選ぶ
すべての用途でListが最適とは限りません。
| 要件 | 主な候補 |
| 順序を保持し、インデックスでアクセスする | List<T> |
| 要素数が固定 | 配列 |
| 重複を許可しない | HashSet<T> |
| キーから値を検索する | Dictionary<TKey, TValue> |
| 先入れ先出し | Queue<T> |
| 後入れ先出し | Stack<T> |
| 先頭や末尾への挿入・削除が多い | LinkedList<T> |
| 複数スレッドから追加する | ConcurrentBag<T>など |
| 作成後に内容を変更しない | 不変コレクション |
データの順序、重複、検索方法、変更頻度、スレッドセーフ性を考慮して選択しましょう。
10. C#のListへの要素追加に関するよくある質問
10-1. Addで複数の要素を一度に追加できる?
List<T>.Addが追加できるのは、基本的に1回につき1要素です。
var numbers = new List<int>();numbers.Add(1);numbers.Add(2);
複数の要素をまとめて追加したい場合は、AddRangeを使います。
numbers.AddRange(new[] { 1, 2, 3 });ただし、List<int[]>のように配列自体を1要素として格納するListでは、Addで配列を1件追加できます。
var groups = new List<int[]>();groups.Add(new[] { 1, 2, 3 });
この場合、整数が3件追加されるのではなく、整数配列が1件追加されます。
10-2. Listの先頭に要素を追加するには?
Insertのインデックスに0を指定します。
var numbers = new List<int> { 2, 3 };numbers.Insert(0, 1);
複数件を先頭に追加する場合はInsertRangeを使います。
numbers.InsertRange(0, new[] { -1, 0 });先頭への追加を大量に繰り返すと既存要素の移動が発生するため、用途によっては別のコレクションも検討してください。
10-3. 重複しない要素だけを追加するには?
単純な値であれば、Containsで確認してから追加します。
if (!numbers.Contains(value)){numbers.Add(value);}オブジェクトを特定のプロパティで判定する場合は、Anyを使います。
if (!products.Any(product => product.Id == newProduct.Id)){products.Add(newProduct);}重複を常に禁止したい場合は、HashSet<T>が適しています。
var values = new HashSet<int>();values.Add(1);values.Add(1);
10-4. 条件に一致する要素だけを追加するには?
ループ内で条件を確認し、該当する要素をAddします。
var results = new List<int>();foreach (int number in source){if (number >= 0){results.Add(number);}}
LINQのWhereとAddRangeを組み合わせる方法もあります。
results.AddRange(source.Where(number => number >= 0));条件に加えて値を変換する場合は、Selectを組み合わせます。
results.AddRange(source.Where(number => number >= 0).Select(number => number * 2));10-5. LINQのAppendとList.Addの違いは?
List.Addは、元のListを直接変更します。
var numbers = new List<int> { 1, 2, 3 };numbers.Add(4);
Enumerable.Appendは、追加後の要素を列挙できる新しいIEnumerable<T>を返し、元のListは変更しません。
IEnumerable<int> result = numbers.Append(4);元のListを変更したい場合はAdd、元のシーケンスを変更せず新しい列挙結果を作りたい場合はAppendを使用します。
10-6. Listに別のListを追加するには?
別のListが持つ全要素を追加する場合は、AddRangeを使用します。
var first = new List<int> { 1, 2 };var second = new List<int> { 3, 4 };first.AddRange(second);
追加後のfirstは次のようになります。
1, 2, 3, 4List<List<int>>へ別のListそのものを1要素として追加する場合は、Addを使います。
var groups = new List<List<int>>();var numbers = new List<int> { 1, 2, 3 };groups.Add(numbers);
AddRangeは要素を展開して追加し、Addは渡したList自体を1要素として追加する点が異なります。
10-7. foreachの実行中にListへ追加するには?
列挙中のList自体を変更するのではなく、追加する要素を別のListへ保存します。
var numbers = new List<int> { 1, 2, 3 };var additions = new List<int>();foreach (int number in numbers){additions.Add(number * 10);}
numbers.AddRange(additions);
元のListのコピーを列挙する方法もあります。
foreach (int number in numbers.ToList()){numbers.Add(number * 10);}インデックスを使う場合は、ループ開始時の要素数を保存します。
int originalCount = numbers.Count;for (int i = 0; i < originalCount; i++){numbers.Add(numbers[i] * 10);}
追加後のCountをループ条件にすると処理が終わらなくなる可能性があるため、注意が必要です。
まとめ
C#のListへ要素を追加する基本的な方法は、AddとAddRangeです。1件だけ追加する場合はAdd、配列や別のListなどから複数件をまとめて追加する場合はAddRangeを使用します。
var numbers = new List<int>();numbers.Add(1);numbers.AddRange(new[] { 2, 3, 4 });
指定した位置へ追加する場合は、InsertまたはInsertRangeを使用します。
numbers.Insert(0, 0);numbers.InsertRange(2, new[] { 10, 20 });要素を追加できない場合は、次の点を確認しましょう。
Listが
nullになっていないかListが正しく初期化されているか
追加する値の型が一致しているか
Insertのインデックスが有効かAddRangeへnullを渡していないかforeachで列挙中のListを変更していないか読み取り専用コレクションを操作していないか
LINQの
AppendをAddと混同していないか複数スレッドから同時に変更していないか
重複を防ぐ場合はContainsやAnyを使い、重複を許可しないことが重要ならHashSet<T>も候補になります。大量の要素を追加する場合は、AddRangeやCapacity、EnsureCapacityを適切に利用すると、不要な内部領域の再確保を減らせる可能性があります。
Listの追加方法だけでなく、データの順序、重複の可否、追加件数、スレッドセーフ性まで考慮し、用途に適したメソッドとコレクションを選択することが大切です。

