連載
» 2017年03月08日 05時00分 UPDATE

.NET TIPS:Listの各要素を処理するには?[C#/VB]

コレクションの要素を処理するには、foreach/For Eachループ、ForEachメソッド、LINQの拡張メソッドを使用する方法がある。これらの使い方を紹介する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載目次

 最もよく使われるコレクションはList<T>(C#)/List(Of T)(VB)クラス(System.Collections.Generic名前空間)であろう(以降、型引数はC#での表記だけとさせていただく)。そのコレクションに含まれる各要素を処理するコードはどのように書けばよいだろうか? 本稿では3通りの方法を解説する。また、補足として条件に一致する要素をまとめて削除する方法も紹介する。

 なお、List<T>クラスは.NET Framework 2.0で導入されたものだが、本稿はそれ以降の内容も含んでいる。サンプルコードをそのまま試すには、Visual Studio 2015(またはそれ以降)が必要である。

List<T>コレクションの各要素を処理するには?

 特定の要素を処理する場合で、その要素のインデックスが分かっているときは、インデックスで要素を指定して直接処理できる。

 そうでないときは(全要素を順に処理するとき、あるいは、各要素をチェックしてみないと処理をするかどうか分からないとき)、次のような方法がある。

 まず、汎用的にはforeach(C#)/For Each(VB)ループを使う(次のコードの1番目)。また、同じことは、List<T>クラスのForEachメソッドでも可能だ(次のコードの2番目)。そして、処理結果を元のList<T>コレクションに書き戻すのではなく、別の新しいコレクションにまとめたいときにはLINQ拡張を使うとよい(次のコードの3番目)。

// 処理対象のList<T>コレクション
List<T> list = ……省略……;

// 【1番目】foreachループを使う
foreach (T item in list)
{
  ……itemを使った処理……
}

// 【2番目】ForEachメソッドを使う
list.ForEach(item =>
{
  ……itemを使った処理……
});

// 【3番目】LINQ拡張を使って新しいコレクションを得る
IEnumerable<T2> result // T2は元のTと同じ型でも違う型でもよい
  = list.Select(item => 
        {
          ……itemを使った処理の結果をT2型のオブジェクトで返す……
        });

' 処理対象のList(Of T)コレクション
Dim list As List(Of T) = ……省略……

' 【1番目】For Eachループを使う
For Each item As T In list
  ……itemを使った処理……
Next

' 【2番目】ForEachメソッドを使う
list.ForEach(
  Sub(item)
    ……itemを使った処理……
  End Sub)

' 【3番目】LINQ拡張を使って新しいコレクションを得る
Dim result As IEnumerable(Of T2) ' T2は元のTと同じ型でも違う型でもよい
  = list.Select(Function(item)
                  ……itemを使った処理の結果をT2型のオブジェクトで返す……
                End Function)

Listの各要素を処理する方法(上:C#、下:VB)

foreach/For Eachループを使う例

 実際にList<T>コレクションでforeach/For Eachループを使う例を確認してみよう。

 取り扱う要素としては、次のコードのような「SampleData」クラスを使う。整数型の「Number」プロパティと文字列型の「Value」プロパティを持っている。また、ToStringメソッドをオーバーライドしている。

public class SampleData
{
  public int Number { get; set; }
  public string Value { get; set; }
  public override string ToString() => $"{Number}:{Value}";
}

Public Class SampleData
  Public Property Number As Integer
  Public Property Value As String
  Public Overrides Function ToString() As String
    Return $"{Number}:{Value}"
  End Function
End Class

サンプルコードで使うデータの定義(上:C#、下:VB)
「Number」と「Value」は単純なプロパティだ。オーバーライドしているToStringメソッドは、「Number」と「Value」の値を「:」で連結して返す。
C#のこのToStringメソッドの書き方については、「.NET TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0]」をご覧いただきたい。
ToStringメソッドに出てくる先頭に「$」記号が付いた文字列については、「.NET TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]」の後半(「C# 6.0/VB 14で追加された補間文字列機能を使用する」)を見てほしい。

 上の「SampleData」クラスのオブジェクトを要素として持つList<SampleData>コレクションを、foreach/For Eachループで処理してみよう。処理内容は、「『Number』が奇数なら、『Value』を頭文字だけにする」というものだ。

 次のコードは、コンソールアプリの例だ。後のサンプルコードでも使うため、「DisplayItems<T>」メソッドを切り出してある。

using System.Collections.Generic;
using static System.Console;

class Program
{
  static void DisplayItems<T>(IEnumerable<T> collection)
    => WriteLine($"{string.Join(", ", collection)}");

  static void Main(string[] args)
  {
    List<SampleData> list = new List<SampleData>
    {
      new SampleData{ Number= 1, Value= "aaa" },
      new SampleData{ Number= 2, Value= "bbb" },
      new SampleData{ Number= 3, Value= "ccc" },
      new SampleData{ Number= 4, Value= "ddd" },
      new SampleData{ Number= 5, Value= "eee" },
    };

    // 汎用的にはforeachを使う
    foreach (SampleData d in list)
    {
      // 処理の例:「Number」が奇数なら、「Value」を頭文字だけにする
      if (d.Number % 2 == 1)
        d.Value = d.Value.Substring(0, 1); // 注:文字列長のチェックは省略した
    }
    DisplayItems(list);
    // 出力:1:a, 2:bbb, 3:c, 4:ddd, 5:e

#if DEBUG
    ReadKey();
#endif
  }
}

Imports System.Console

Module Module1
  Sub DisplayItems(Of T)(collection As IEnumerable(Of T))
    WriteLine($"{String.Join(", ", collection)}")
  End Sub

  Sub Main()
    Dim list As List(Of SampleData) = New List(Of SampleData) _
    From {
      New SampleData With {.Number = 1, .Value = "aaa"},
      New SampleData With {.Number = 2, .Value = "bbb"},
      New SampleData With {.Number = 3, .Value = "ccc"},
      New SampleData With {.Number = 4, .Value = "ddd"},
      New SampleData With {.Number = 5, .Value = "eee"}
    }

    ' 汎用的にはFor Eachを使う
    For Each d As SampleData In list
      ' 処理の例:「Number」が奇数なら、「Value」を頭文字だけにする
      If (d.Number Mod 2 = 1) Then
        d.Value = d.Value.Substring(0, 1) ' 注:文字列長のチェックは省略した
      End If
    Next
    DisplayItems(list)
    ' 出力:1:a, 2:bbb, 3:c, 4:ddd, 5:e

#If DEBUG Then
    ReadKey()
#End If
  End Sub
End Module

foreach/For Eachループで処理をする例(上:C#、下:VB)
list変数の初期化方法は、「.NET TIPS:構文:コレクションのインスタンス化と同時に要素を追加するには?[C#/VB]」をご覧いただきたい。
C#コードの冒頭から2行目にある「using static System.Console;」という書き方は、Visual Studio 2015からのものだ。詳しくは、「.NET TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]」をご覧いただきたい。同様な機能がVBには以前から備わっており、「.NET TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?」で解説している。
また、「Main」メソッド末尾にReadKeyメソッドを置く意味は、「.NET TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?」をご覧いただきたい。

List<T>クラスのForEachメソッドを使う例

 List<T>クラスにはForEachメソッドがある(LINQ拡張にはない)。これを使うと、上のforeach/For Eachループの部分は次のコードのようにも書ける。

// List<T>クラスでは、foreach構文の代わりにForEachメソッドも使える
List<SampleData> list = ……省略(前と同じ)……
list.ForEach(d =>
{
  if (d.Number % 2 == 1)
    d.Value = d.Value.Substring(0, 1); 
});
DisplayItems(list);
// 出力:1:a, 2:bbb, 3:c, 4:ddd, 5:e

' List<T>クラスでは、foreach構文の代わりにForEachメソッドも使える
Dim list As List(Of SampleData) = ……省略(前と同じ)……
list.ForEach(
  Sub(d)
    If (d.Number Mod 2 = 1) Then
      d.Value = d.Value.Substring(0, 1)
    End If
  End Sub)
DisplayItems(list)
' 出力:1:a, 2:bbb, 3:c, 4:ddd, 5:e

List<T>クラスのForEachメソッドで処理をする例(上:C#、下:VB)
ここではForEachメソッドの引数としてラムダ式を与えている。
ラムダ式はVisual Studio 2008(C# 3.0/VB 9.0)からの機能なので、それ以前の場合は処理を別のメソッドに切り出して、ForEachメソッドの引数にはそのメソッド名を与える。

LINQ拡張を使う例

 処理結果を別の新しいコレクションに格納したい場合は、LINQを使うとスマートだ。LINQのSelect拡張メソッドだけでも書ける。しかし、例えば、「奇数の場合だけ処理したい」というような場合には、Select拡張メソッドの中でif文を使うのではなく、LINQのWhere拡張メソッドを先に使って対象を絞り込むようにするとより分かりやすいコードになる。

 前出の例で変更した要素だけを含む新しいコレクションを作りたい場合、LINQ拡張を使うと次のコードのようになる。

// 処理結果を新しいコレクションに書き出したいときはLINQを使う
List<SampleData> list = ……省略(前と同じ)……
IEnumerable<SampleData> result 
  = list.Where(d => d.Number % 2 == 1)
        .Select(d => 
        {
          d.Value = d.Value.Substring(0, 1);
          return d;
        });
DisplayItems(result);
// 出力:1:a, 3:c, 5:e
// これまでの例と同じ結果を得る場合
//result = list.Select(d =>
//{
//  if (d.Number % 2 == 1)
//    d.Value = d.Value.Substring(0, 1);
//  return d;
//});
//DisplayItems(result);

' 処理結果を新しいコレクションに書き出したいときはLINQを使う
Dim list As List(Of SampleData) = ……省略(前と同じ)……
Dim result As IEnumerable(Of SampleData) _
  = list.Where(Function(d) d.Number Mod 2 = 1) _
        .Select(Function(d)
                  d.Value = d.Value.Substring(0, 1)
                  Return d
                End Function)
DisplayItems(result)
' 出力:1:a, 3:c, 5:e
' これまでの例と同じ結果を得る場合
'result = list.Select(
'  Function(d)
'    If d.Number Mod 2 = 1 Then
'      d.Value = d.Value.Substring(0, 1)
'    End If
'    Return d
'  End Function)
'DisplayItems(result)

LINQ拡張で処理をする例(上:C#、下:VB)
Select拡張メソッドだけでも書けるが、ここでは先にWhere拡張メソッドを使って処理対象の要素を絞り込んでいる。それによってForEachメソッドの例にあったif文が不要になった。また、処理対象を絞り込んでから処理をしていることが明確になっている。
なおC#では、冒頭に「using System.Linq;」が必要である。

補足:要素を削除するにはRemoveAllメソッド

 処理の結果によって要素を取り除きたいこともある。

 前述のような要素の内容の変更と、要素の削除とを同時に行うのはちょっと難しい(forループを末尾から回すことになる)。削除をするだけならば、List<T>クラスのRemoveAllメソッドが使える(次のコード)。

 List<T>コレクションから要素を削除する方法については、「.NET TIPS:リスト(List)の列挙中にリスト要素を削除するには?[C#、VB]」で詳しく解説している。

// RemoveAllメソッドで「Number」が偶数のものを削除する
List<SampleData> list = ……省略(前と同じ)……
list.RemoveAll(d => d.Number % 2 == 0);
DisplayItems(list);
// 出力:1:aaa, 3:ccc, 5:eee

' RemoveAllメソッドで「Number」が偶数のものを削除する
Dim list As List(Of SampleData) = ……省略(前と同じ)……
list.RemoveAll(Function(d) d.Number Mod 2 = 0)
DisplayItems(list)
' 出力:1:aaa, 3:ccc, 5:eee

List<T>クラスのRemoveAllメソッドで要素を削除する例(上:C#、下:VB)

まとめ

 List<T>コレクションの各要素を処理するには、foreachループ(C#)/For Eachループ(VB)を書くのが基本だ。List<T>クラスのForEachメソッドを使っても同様な結果が得られる。処理した結果を書き戻すのではなく別の新しいコレクションに格納する場合には、LINQ拡張を使うとよい。

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

.NET TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

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

メールマガジン登録

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