連載
» 2008年12月04日 05時00分 公開

.NET TIPS:リスト(List)の列挙中にリスト要素を削除するには?[C#、VB]

foreach文でリスト内の要素を列挙中に、いずれかの要素を削除しようとすると例外が発生して失敗する。そんな場合は、for文やRemoveAllメソッドを応用する。C#およびVBでの使い方や注意点を解説する。

[遠藤孝信,デジタルアドバンテージ]
「.NET TIPS」のインデックス

連載目次

 リスト(=System.Collections.Generic名前空間のList<T>クラスのオブジェクト)に対して、foreach文により要素を列挙している最中には、そのリストから要素を削除することはできない。これを行おうとするとInvalidOperation例外が発生する。以下はそのコード例である。

using System;
using System.Collections.Generic;

class Program {
  static void Main() {

    List<string> lines = new List<string>();
    lines.Add("Hello");
    lines.Add("World");
    lines.Add("Hello");
    lines.Add("Again");

    foreach (string s in lines) {

      // ここで要素に対する何かの処理

      if (s == "Hello") {
        lines.Remove(s); // 要素の削除
      }
    }
    // InvalidOperation例外が発生

    foreach (string s in lines) {
      Console.WriteLine(s);
    }
  }
}

Imports System
Imports System.Collections.Generic

Class Program
  Shared Sub main()

    Dim lines As New List(Of String)()
    lines.Add("Hello")
    lines.Add("World")
    lines.Add("Hello")
    lines.Add("Again")

    For Each s As String In lines

      ' ここで要素に対する何かの処理

      If s = "Hello" Then
        lines.Remove(s)  ' 要素の削除
      End If
      ' InvalidOperation例外が発生
    Next

    For Each s As String In lines
      Console.WriteLine(s)
    Next
  End Sub
End Class

InvalidOperation例外が発生するコード例(上:C#、下:VB)

 これは、要素の削除により、すべての要素を順に列挙するという動作が保証できなくなるためだ(要素の追加やソートも同様)。この挙動は、ほとんどのコレクション・クラスに共通する挙動である。

forループによる要素の削除

 上記のように、リスト内の各要素に対して何かの処理を行いながら、不要となる要素を削除したい場合には、for文とインデックス番号を使ってリストの要素を列挙していけばよい。

 ただし、末尾の要素から先頭方向に向かって処理していく必要がある*1。これは次のように記述できる。

using System;
using System.Collections.Generic;

class Program {
  static void Main() {

    List<string> lines = new List<string>();
    lines.Add("Hello");
    lines.Add("World");
    lines.Add("Hello");
    lines.Add("Again");

    for (int i = lines.Count - 1; i >= 0; i--) {

      // ここで要素に対する何かの処理

      if (lines[i] == "Hello") {
        lines.Remove(lines[i]); // 要素の削除
      }
    }

    foreach (string s in lines) {
      Console.WriteLine(s);
    }
    // 出力:
    // World
    // Again
  }
}

Imports System
Imports System.Collections.Generic

Class Program
  Shared Sub main()

    Dim lines As New List(Of String)()
    lines.Add("Hello")
    lines.Add("World")
    lines.Add("Hello")
    lines.Add("Again")

    For i As Integer = lines.Count - 1 To 0 Step -1

      ' ここで要素に対する何かの処理

      If lines(i) = "Hello" Then
        lines.Remove(lines(i))  ' 要素の削除
      End If
    Next

    For Each s As String In lines
      Console.WriteLine(s)
    Next
    ' 出力:
    ' World
    ' Again
  End Sub
End Class

for文によりループ内でリスト要素を削除するコード例(上:C#、下:VB)

*1 先頭から順に処理してもInvalidOperation例外は発生しないが、正しい結果とならないケースがあり得る。例えばリストの内容が、"Hello"、"Hello"、"World"の場合を考えてみてほしい。


RemoveAllメソッドによる要素の削除

 なお、単に特定の条件に一致する要素をリストから削除したいだけであれば、RemoveAllメソッドを使うと便利だ。

using System;
using System.Collections.Generic;

class Program {
  static void Main() {

    List<string> lines = new List<string>();
    lines.Add("Hello");
    lines.Add("World");
    lines.Add("Hello");
    lines.Add("Again");

    lines.RemoveAll(checkLine);

    foreach (string s in lines) {
      Console.WriteLine(s);
    }
    // 出力:
    // World
    // Again
  }

  static bool checkLine(string s) {
    return s == "Hello"; // この条件の要素をすべて削除
  }
}

Imports System
Imports System.Collections.Generic

Class Program
  Shared Sub main()

    Dim lines As New List(Of String)()
    lines.Add("Hello")
    lines.Add("World")
    lines.Add("Hello")
    lines.Add("Again")

    lines.RemoveAll(AddressOf checkLine)

    For Each s As String In lines
      Console.WriteLine(s)
    Next
    ' 出力:
    ' World
    ' Again
  End Sub

  Shared Function checkLine(ByVal s As String) As Boolean
    Return s = "Hello" ' この条件の要素をすべて削除
  End Function
End Class

RemoveAllメソッドを使用したコード例(上:C#、下:VB)

 ちなみに、この場合にはラムダ式を使うと、処理をもっと簡潔に記述できる。

using System;
using System.Collections.Generic;

class Program {
  static void Main() {

    List<string> lines = new List<string>();
    lines.Add("Hello");
    lines.Add("World");
    lines.Add("Hello");
    lines.Add("Again");

    lines.RemoveAll(s => s == "Hello");

    foreach (string s in lines) {
      Console.WriteLine(s);
    }
    // 出力:
    // World
    // Again
  }
}

Imports System
Imports System.Collections.Generic

Class Program
  Shared Sub main()

    Dim lines As New List(Of String)()
    lines.Add("Hello")
    lines.Add("World")
    lines.Add("Hello")
    lines.Add("Again")

    lines.RemoveAll(Function(s As String) s = "Hello")

    For Each s As String In lines
      Console.WriteLine(s)
    Next
    ' 出力:
    ' World
    ' Again
  End Sub
End Class

RemoveAllメソッドでラムダ式を使用したコード例(上:C#、下:VB)

 ただしラムダ式は、Visual Studio 2008(C# 3.0やVB 9.0)以降でのみ記述可能である。

利用可能バージョン:.NET Framework 2.0以降
カテゴリ:クラス・ライブラリ 処理対象:コレクション
使用ライブラリ:Listクラス(System.Collections.Generic名前空間)


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。