List<T>から複数の要素を削除する
C#のList<T>(ジェネリックリスト)は、Remove(T item)メソッド(指定した最初の1要素を削除)やRemoveAt(int index)メソッド(指定したインデックスの要素を削除)を提供しています。
しかし、「リストに含まれる0以下の数値をすべて削除したい」や、「ステータスがCompleted(完了)になっているタスクをすべて削除したい」といった、特定の条件(Condition)に一致するすべての要素を一度に削除したい場合があります。
この操作を安全かつ効率的に行うために、List<T>にはRemoveAllメソッドが用意されています。
RemoveAll(Predicate<T> match) メソッド
RemoveAllメソッドは、**述語(Predicate)**と呼ばれる「条件」を引数として受け取ります。
Predicate<T>(述語): これは、List<T>の各要素(T型)を受け取り、bool(true/false)を返すメソッド(またはラムダ式)です。- 動作:
RemoveAllはリストの全要素をチェックし、述語(ラムダ式)がtrueを返した要素をすべてリストから削除します。 - 戻り値: 削除された要素の総数(
int型)を返します。 - 重要な特性: このメソッドは、
List<T>インスタンスを**直接変更(in-place)**します。
コード例1:List<int>(数値)から条件で削除
List<int>(整数のリスト)から、特定の条件(例: 60点未満)に一致するすべての要素を削除する例です。
using System;
using System.Collections.Generic;
public class RemoveAllNumericExample
{
public static void Main()
{
// テストのスコアリスト
var scores = new List<int> { 88, 45, 95, 59, 70, 30, 100 };
Console.WriteLine("--- 削除前のリスト ---");
Console.WriteLine(string.Join(", ", scores));
// 述語(条件)として「60未満」のラムダ式を渡す
// (score => score < 60) が true になる要素 (45, 59, 30) が削除される
int removedCount = scores.RemoveAll(score => score < 60);
Console.WriteLine("\n--- 削除後のリスト ---");
Console.WriteLine(string.Join(", ", scores));
Console.WriteLine($"\n削除された要素の数: {removedCount}");
}
}
出力結果:
--- 削除前のリスト ---
88, 45, 95, 59, 70, 30, 100
--- 削除後のリスト ---
88, 95, 70, 100
削除された要素の数: 3
コード例2:List<T>(オブジェクト)から条件で削除
RemoveAllは、intのような単純な型だけでなく、自作のクラスオブジェクトに対しても強力に機能します。
using System;
using System.Collections.Generic;
// タスクを表すシンプルなクラス
public class TaskItem
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsCompleted { get; set; } // 完了したか
}
public class RemoveAllObjectExample
{
public static void Main()
{
var tasks = new List<TaskItem>
{
new TaskItem { Id = 1, Name = "デザイン設計", IsCompleted = true },
new TaskItem { Id = 2, Name = "機能実装", IsCompleted = false },
new TaskItem { Id = 3, Name = "ドキュメント作成", IsCompleted = true },
new TaskItem { Id = 4, Name = "コードレビュー", IsCompleted = false }
};
Console.WriteLine("--- 削除前のタスク数 ---");
Console.WriteLine($"合計: {tasks.Count} 件");
// 条件: 完了済み (IsCompleted == true) のタスクをすべて削除
int completedCount = tasks.RemoveAll(task => task.IsCompleted);
Console.WriteLine("\n--- 削除後の残存タスク ---");
Console.WriteLine($"削除された完了済みタスク数: {completedCount} 件");
// 残ったタスク (IsCompleted == false のもの) を表示
foreach (var task in tasks)
{
Console.WriteLine($"ID: {task.Id}, Name: {task.Name}");
}
}
}
出力結果:
--- 削除前のタスク数 ---
合計: 4 件
--- 削除後の残存タスク ---
削除された完了済みタスク数: 2 件
ID: 2, Name: 機能実装
ID: 4, Name: コードレビュー
注意点:foreachループ内でのRemoveの危険性
RemoveAllを知らない場合、foreachループを使って次のようなコードを書きがちですが、これは**InvalidOperationException**という実行時エラーを引き起こします。
// 悪い例:実行時エラーになります
List<int> scores = new List<int> { 10, -5, 20 };
try
{
foreach (var score in scores)
{
if (score < 0)
{
scores.Remove(score); // NG: ループ中にコレクションを変更
}
}
}
catch (InvalidOperationException e)
{
Console.WriteLine($"エラー: {e.Message}");
}
foreach(列挙)の実行中に、その対象であるコレクション(scores)を変更することは許可されていません。
RemoveAllは、このような問題を回避し、条件に基づく一括削除を安全かつ効率的に行うための正しい方法です。
まとめ
List<T>.RemoveAll(Predicate<T> match)は、C#のリスト操作において非常に強力なメソッドです。
foreachループとRemoveを組み合わせることで発生する**InvalidOperationExceptionを回避**できます。- ラムダ式(例:
x => x > 100)を使用して、複雑な条件で削除する要素を指定できます。 - メソッドは元のリストを直接変更し、削除した要素の数を
int型で返します。
