§1 List

System.Collections.Generic.Listクラス配列に非常によく似た機能を持つクラスですが、格納できる要素の数が固定である配列とは異なり、動的に要素の数を増減できるという特徴を持っています。

ListクラスはSystem.Collections.ArrayListに相当するジェネリックコレクションですが、ただ単にジェネリックなArrayListというわけではなく、ArrayListと比べるとより高度な操作が可能になっています。

以下、まずはListクラスを使った基本的な操作について見ていきます。

§1.1 Listの作成、要素の取得・設定・追加

List内の要素を参照するには、配列の場合と同様にインデックスを指定します。 これにより、List内に格納されている値を取得したり、値の設定(上書き)をすることができます。 Listを作成する場合、初期状態で要素が格納された状態で作成することも、また空のListを作成しておいて後から要素を追加することもできます。

Listを作成する・List内の要素を取得・設定する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 初期状態で5つの要素があるstring型のListを作成
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // インデックス0と3の要素を参照して表示
    Console.WriteLine("list[0] -> {0}", list[0]);
    Console.WriteLine("list[3] -> {0}", list[3]);

    // インデックス1の要素に値を設定
    list[1] = "Frank";

    Console.WriteLine("list[1] -> {0}", list[1]);
  }
}
実行結果
list[0] -> Alice
list[3] -> Dave
list[1] -> Frank

C#ではインデクサによって、VB.NETでは既定のプロパティItemによって、配列と同様にインデックスによる取得・設定ができるようになっています。 インデクサについてはプロパティ §.インデクサを参照してください。


Listに要素を追加するには、Addメソッドなどを使用します。 インスタンスを作成した後でも任意に要素を追加できる点がListと配列の大きく異なる点です。

空のListを作成する・Listに要素を追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // int型を格納する空のListを作成
    List<int> list = new List<int>();

    // Listに要素を3つ追加する
    list.Add(1);
    list.Add(3);
    list.Add(5);

    Console.WriteLine("list[0] -> {0}", list[0]);
    Console.WriteLine("list[1] -> {0}", list[1]);
    Console.WriteLine("list[2] -> {0}", list[2]);
  }
}
実行結果
list[0] -> 1
list[1] -> 3
list[2] -> 5

Listの作成方法のバリエーションについては後述の§.インスタンスの作成で解説しています。

§1.2 List内の要素の列挙

List内の全ての要素を列挙するには、配列同様にfor/foreach文を使うことが出来ます。 配列の場合とは異なり、現在List内に含まれている要素の数を取得するにはLengthプロパティではなくCountプロパティを参照します。

for文/foreach文でList内の全要素を列挙する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 初期状態で5つの要素があるstring型のListを作成
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // for文でList内の要素を列挙して表示
    Console.WriteLine("[for]");

    for (int i = 0; i < list.Count; i++) {
      Console.WriteLine("list[{0}] = {1}", i, list[i]);
    }

    Console.WriteLine();

    // foreach文でList内の要素を列挙して表示
    Console.WriteLine("[foreach]");

    foreach (string e in list) {
      Console.WriteLine(e);
    }
  }
}
実行結果
[for]
list[0] = Alice
list[1] = Bob
list[2] = Charlie
list[3] = Dave
list[4] = Eve

[foreach]
Alice
Bob
Charlie
Dave
Eve

§1.3 配列への変換

Listを配列に変換する必要性が生じた場合は、ToArrayメソッドを使うことができます。 ToArrayメソッドを呼び出すと、Listの内容と同じ内容をもつ配列を取得することができます。

Listを配列に変換する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 空のListを作成
    List<string> list = new List<string>();

    // Listに要素を追加
    list.Add("Alice");
    list.Add("Bob");
    list.Add("Charlie");

    // Listの内容を配列に変換
    string[] arr = list.ToArray();

    Console.WriteLine(string.Join(", ", arr));
  }
}
実行結果
Alice, Bob, Charlie

引数の型が配列となっているメソッドにListの内容を渡したい場合などにはToArrayメソッドを使う必要があります。 一方、引数の型がIEnumerable<T>となっている場合は配列と同様にListをそのまま渡すことができるため、この場合はToArrayメソッドによって配列に変換する必要はありません。

例えば上記の例で使用しているString.Joinメソッドも、.NET Framework 4以降ではIEnumerable<T>を引数にとることができるようになっているので、わざわざ配列に変換することなく直接Listを渡すことができます。

String.Joinメソッドについては文字列の加工・編集 §.結合 (Join)を参照してください。

§1.4 Listの要素数の拡張

Listは要素の数は可変ですが、現在の要素数(Count)を超えるインデックスを指定した場合は例外ArgumentOutOfRangeExceptionがスローされます。 例えば、次のコードのように現在の要素数よりも大きなインデックスを指定しても、その分の容量が自動的に確保されるわけではない点に注意してください。

JavaScript等では認められているこのような操作は、Listクラスでは行うことはできない
var arr = new Array();

arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
arr[99] = 99; // arr.length = 100
Listではインデックスで指定した分の要素数が自動的に確保されるわけではない
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 要素数が3(=インデックスの上限が2)のList
    List<int> list = new List<int>() {0, 1, 2};

    // インデックス0の要素に値を設定する
    list[0] = 0;
    // (インデックス0は全要素数3の範囲内なので例外はスローされない)

    // インデックス99の要素に値を設定する
    list[99] = 99;
    // (インデックスは全要素数の範囲を超えるため例外がスローされる)
    // (→インデックス99に値を格納するために容量が拡張されるわけではない)
  }
}
実行結果
ハンドルされていない例外: System.ArgumentOutOfRangeException: インデックスが範囲を超えています。負でない値で、コレクションのサイズよりも小さくなければなりません。
パラメーター名: index
   場所 System.Collections.Generic.List`1.set_Item(Int32 index, T value)
   場所 Sample.Main()

Listにおける容量の自動的な拡張は、Addメソッドなどによって要素が追加・挿入される場合にのみ行われます。 従って、上記のように現在の要素数よりも大きいインデックスに要素を格納したい場合は、Addメソッドを使って必要な要素数になるまで適当な値の追加を繰り返す必要があります。

Addメソッドを使ってListの容量を拡張する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 要素数が3(=インデックスの上限が2)のList
    List<int> list = new List<int>() {0, 1, 2};

    // インデックス99に値を設定するために、
    // 要素数が100になるまでListに適当な値を追加する
    while (list.Count < 100) {
      list.Add(-1);
    }

    // インデックス99に値を設定する
    list[99] = 99;
  }
}

あるいは、あらかじめ必要な要素数を確保した配列を用意してからそれをもとにListを作成する、といった方法を取ることもできます。 次の例では、全要素数が100の配列を用意してからListを作成しています。

配列を使ってListに指定した要素数を確保する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 全要素数が100の配列を作成する
    int[] arr = new int[100];

    // 配列からListを作成する
    List<int> list = new List<int>(arr);

    // インデックス99の要素に値を設定する
    // (インデックスはListの元になった配列の全要素数の範囲内なので例外はスローされない)
    list[99] = 99;
  }
}

上記の例では、元になる配列arrと、それをもとに作成するlistの二つで個別にインスタンスが確保されている点に注意してください。

Listの要素数・容量に関して、後述の§.容量も合わせて参照してください。



§1.5 Listクラスのメソッド

ここまでで解説した基本的な操作以外にも、Listクラスでは次のようなメソッドを使った操作を行うことができます。

Listクラスのメソッド
操作 対応するListクラスのメソッド
要素の追加・挿入 要素を追加する Add
AddRange
要素を指定した位置に挿入する Insert
InsertRange
要素の削除 値を指定して削除する Remove
インデックスを指定して削除する RemoveAt
RemoveRange
条件に合致するものを削除する RemoveAll
すべての要素を削除する Clear
要素の位置(インデックス)の検索 値を指定して検索する IndexOf
LastIndexOf
条件に合致するものの位置を検索する FindIndex
FindLastIndex
二分探索によって検索する BinarySearch
要素の有無のテスト 値を指定して有無を調べる Contains
条件に合致する要素がひとつでもあるか調べる Exists
すべての要素が条件に合致するか調べる TrueForAll
要素の抽出 条件に合致する要素を取得する Find
FindLast
条件に合致するすべての要素を取得する FindAll
指定した範囲にある要素を抽出する
(Listのサブセットを取得する)
GetRange
変換 全要素を別の型・値に変換する ConvertAll
配列に変換する ToArray
配列からListを作成する Listコンストラクタ
配列にコピーする CopyTo
並べ替え ソートする Sort
リバースする Reverse
ユーティリティ 容量を縮小する TrimExcess
読み取り専用のビューを作成する AsReadOnly
操作 対応するListクラスのメソッド

以降でListクラスの使い方に関してより詳しく解説します。

§2 インスタンスの作成

コンストラクタに特に何も指定しないでListを作成した場合は、空のListが作成されます。 配列やコレクションを指定して作成した場合は、それと同じ内容のListを持つListが作成されます。 どちらの場合でも、インスタンス作成後に要素を追加したり削除したりすることができます。

空のListを作成して後から要素を追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 空のListを作成する
    List<string> list = new List<string>();

    // 作成したListに要素を追加する
    list.Add("Alice");

    list.Add("Charlie");

    list.Insert(1, "Bob");

    // listの内容は{"Alice", "Bob", "Charlie"}となる
  }
}
初期状態で配列と同じ内容をもったListを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 初期状態でListに格納しておきたい要素を含む配列
    string[] arr = new string[] {"Alice", "Bob"};

    // 初期状態で上記の配列と同じ内容をもつListを作成する
    List<string> list = new List<string>(arr);

    // 作成したListに要素を追加する
    list.Add("Charlie");

    // listの内容は{"Alice", "Bob", "Charlie"}となる
  }
}

初期値をもったList(あらかじめ要素が格納された状態のList)を作成したい場合は、上記のように配列やコレクションをコンストラクタに指定してインスタンスを作成するか、後述のコレクション初期化子を使用してインスタンスを作成します。

また、コンストラクタに他のListを指定すれば、そのListの複製を作ることができます。(詳細:§.Listの複製) このほか、初期容量を指定してListインスタンスを作成することもできます。(詳細:§.初期容量)

§2.1 コレクション初期化子

Listを使用する場合、あらかじめ決められた内容を含むリストを用意したい場合が多々あります。 こういった場合、まず空のリストを作成してから要素を追加していくこともできますが、コレクション初期化子を使うとインスタンスの作成と要素の追加を同時に行うことができ、あらかじめ決められた内容を含むリストを作成できます。 コレクション初期化子はC# 3.0(Visual C# 2008)以降、VB 10(Visual Basic 2010)以降でサポートされます。

コレクション初期化子を使ってあらかじめ要素を格納した状態のListを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // コレクション初期化子を使ってインスタンスの作成と要素の追加を行う
    List<string> list = new List<string>() {
      "Alice", "Bob", "Charlie", "Dave", "Eve"
    };

    Console.WriteLine("list[2] = {0}", list[2]);
    Console.WriteLine("Count = {0}", list.Count);
  }
}
実行結果
list[2] = Charlie
Count = 5

コレクション初期化子を使用できない場合は、コンストラクタに配列を渡すことで同様の初期化を行うことができます。

コンストラクタに配列を指定してあらかじめ要素を格納した状態のListを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 配列を使ってインスタンスの作成と要素の追加を行う
    List<string> list = new List<string>(new string[] {
      "Alice", "Bob", "Charlie", "Dave", "Eve"
    });

    Console.WriteLine("list[2] = {0}", list[2]);
    Console.WriteLine("Count = {0}", list.Count);
  }
}
実行結果
list[2] = Charlie
Count = 5

§3 要素の追加・挿入

Listは格納できる要素の数は可変であるため、任意の時点で任意の数の要素を追加することができます。

§3.1 要素の追加 (Add)

Listに要素を追加するには、Addメソッドを使います。 このメソッドを使うと、Listの末尾(一番最後)に要素が追加されます。

Addメソッドを使ってListの末尾に要素を追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 要素が格納されていない空のListを作成
    List<string> list = new List<string>();

    // 要素を追加する
    list.Add("Alice");
    list.Add("Bob");

    Print(list);

    // 要素を追加する
    list.Add("Charlie");

    Print(list);
  }

  // Listの内容を列挙して表示する
  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Alice, Bob, 
Alice, Bob, Charlie, 

追加する位置を指定して要素を挿入したい場合はInsertメソッドを使うことができます。

§3.2 要素の挿入 (Insert)

追加する位置を指定して要素を挿入したい場合はInsertメソッドを使います。 Insertメソッドでは挿入する位置(0から始まるインデックス)、挿入したい要素の順で引数を指定します。

Insertメソッドを使ってListに要素を挿入する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 初期状態で2つの要素があるListを作成
    List<string> list = new List<string>() {"Alice", "Charlie"};

    Print(list);

    // インデックス1の位置に要素を挿入する
    list.Insert(1, "Bob");

    Print(list);
  }

  // Listの内容を列挙して表示する
  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Alice, Charlie, 
Alice, Bob, Charlie, 

§3.3 複数の要素・コレクションの追加 (AddRange)

複数の要素を一度にまとめて追加したい場合は、AddRangeメソッドを使います。 配列や他のListなどのコレクションに格納されている内容をまとめてListに追加したい場合にAddRangeメソッドを使うことができます。

AddRangeメソッドを使って複数の要素を一度にListへ追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 初期状態でいくつか要素が格納されているListを作成
    List<string> list = new List<string>() {"Dave", "Eve"};

    // 追加したい要素を含む配列
    string[] arr = new string[] {"Alice", "Bob", "Charlie"};

    // Listの末尾に複数の要素を追加する (配列に含まれている内容をすべて追加する)
    list.AddRange(arr);

    Print(list);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Dave, Eve, Alice, Bob, Charlie, 

AddRangeメソッドでは、Addメソッドと同様Listの末尾に要素が追加されていきます。 挿入する位置を指定したい場合にはInsertRangeメソッドを使うことが出来ます。

§3.3.1 Listの結合

AddRangeメソッドでは他のListの内容をListへ追加することもできます。 これを使って複数のListを結合することができます。

AddRangeメソッドをふたつのListを結合したListを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 結合したい内容を含むList
    List<string> l1 = new List<string>() {"Alice", "Bob", "Charlie"};
    List<string> l2 = new List<string>() {"Dave", "Eve"};

    // l1の内容を複製して新しいListを作成
    List<string> lc = new List<string>(l1);

    // l2の内容を追加することで結合する
    lc.AddRange(l2);

    // 結合した結果を表示
    Print(lc);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Alice, Bob, Charlie, Dave, Eve, 

この例ではコンストラクタを使ってListの複製を作成しています。

§3.3.2 コレクションの一部の追加

AddRangeメソッドでは、指定されたコレクションのすべての内容を追加します。 一部分だけを追加することができるオーバーロードは用意されていません。

LINQのSkipメソッドTakeメソッドを使ってコレクションの一部を取り出すことにより、一部分だけをListに追加することができます。 この方法はListだけでなく配列を含む任意のコレクションに対して用いることができます。

SkipメソッドとTakeメソッドを使ってコレクションの一部をListに追加する
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    // Listへ格納したい内容をもったコレクション
    List<int> source = new List<int>() {0, 1, 2, 3, 4};

    // 追加先となるList
    List<int> list = new List<int>();

    // sourceのインデックス1から3つ分の要素をlistに追加する
    // (sourceの先頭から1つ分の要素はスキップし、それに続く3つの要素を取り出して追加する)
    list.AddRange(source.Skip(1).Take(3));

    Print(list);
  }

  static void Print(List<int> list)
  {
    foreach (int e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
1, 2, 3,

Listの一部を追加する場合は、GetRangeメソッドを使ってListの一部を切り出すことによって追加することができます。

GetRangeメソッドを使ってListの一部を別のListに追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // Listへ格納したい内容をもったコレクション
    List<int> source = new List<int>() {0, 1, 2, 3, 4};

    // 追加先となるList
    List<int> list = new List<int>();

    // sourceのインデックス1から3つ分の要素を取り出してlistに追加する
    list.AddRange(source.GetRange(1, 3));

    Print(list);
  }

  static void Print(List<int> list)
  {
    foreach (int e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
1, 2, 3,

.NET Framework 4.5以降の場合は、ArraySegment構造体を使うことによって配列の一部分をListに追加することができます。

ArraySegment構造体を使って配列の一部をListに追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // Listへ格納したい内容をもった配列
    int[] source = new int[] {0, 1, 2, 3, 4};

    // 追加先となるList
    List<int> list = new List<int>();

    // sourceのインデックス1から3つ分の要素をlistに追加する
    // (sourceのインデックス1から3つ分の要素を参照するArraySegmentを作成して追加する)
    list.AddRange(new ArraySegment<int>(source, 1, 3));

    Print(list);
  }

  static void Print(List<int> list)
  {
    foreach (int e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
1, 2, 3,

.NET Framework 4.5より前のバージョンでは、ArraySegment構造体がIEnumerable<T>インターフェイスを実装していないため、AddRangeメソッドでArraySegment構造体を追加することはできません。

ArraySegment構造体について詳しくは部分配列 §.ArraySegment構造体を参照してください。

§3.4 複数の要素・コレクションの挿入 (InsertRange)

挿入する位置を指定してコレクションに格納されている内容をListへ挿入したい場合にはInsertRangeメソッドを使うことが出来ます。

InsertRangeメソッドを使って複数の要素を一度にListへ挿入する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 初期状態でいくつか要素が格納されているListを作成
    List<string> list = new List<string>() {"Dave", "Eve"};

    // 挿入したい要素を含む配列
    string[] arr = new string[] {"Alice", "Bob", "Charlie"};

    // Listのインデックス1以降に複数の要素を挿入する (配列に含まれている内容をすべて挿入する)
    list.InsertRange(1, arr);

    Print(list);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Dave, Alice, Bob, Charlie, Eve, 

InsertRangeメソッドでコレクションの一部分だけを挿入する方法については、AddRangeメソッドでの例(§.コレクションの一部の追加)を参照してください。

§3.5 列挙操作中の要素の追加

Listをforeach文で列挙している最中に要素の追加などの変更を行おうとすると、「列挙操作は実行されない可能性があります。」というメッセージとともに例外InvalidOperationExceptionがスローされます。 これを回避する方法などについては列挙操作中のコレクションの変更で詳しく解説しています。

§4 要素の変更

要素を入れ替えて値を変更するには、変更したい要素のインデックスを指定して値を代入します。

List内の要素の値を変更する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 要素が格納されていない空のListを作成
    List<string> list = new List<string>();

    // 要素を追加する
    list.Add("Alice");
    list.Add("Bob");
    list.Add("Charlie");

    Print(list);

    // インデックス1の要素に格納されている値を変更する
    list[1] = "Dave";

    Print(list);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Alice, Bob, Charlie, 
Alice, Dave, Charlie, 

§4.1 構造体要素の変更

要素に構造体を格納するようにしたListで構造体のプロパティやフィールドだけを変更しようとする場合は注意が必要です。 例えば、次のようなコードはコンパイルエラーとなります。

Listに格納されている構造体のフィールドを直接変更しようとした場合
using System;
using System.Collections.Generic;

// Listに格納する構造体
struct S {
  public int F;

  public S(int f)
  {
    F = f;
  }
}

class Sample {
  static void Main()
  {
    List<S> list = new List<S>();

    // フィールドFに値3を設定した構造体を格納
    list.Add(new S(3));

    // インデックス0の構造体フィールドの値を変更したいが、コンパイルエラーとなる
    list[0].F = 16;
    // error CS1612: 変数ではないため、'System.Collections.Generic.List<S>.this[int]'の戻り値を変更できません。

  }
}

このような操作を行いたい場合は、いったんListから一時変数に構造体をコピーし、値を変更してから再度Listに格納するようにします。

Listに格納されている構造体のフィールドを変更する
using System;
using System.Collections.Generic;

// Listに格納する構造体
struct S {
  public int F;

  public S(int f)
  {
    F = f;
  }
}

class Sample {
  static void Main()
  {
    List<S> list = new List<S>();

    // フィールドFに値3を設定した構造体を格納
    list.Add(new S(3));

    // Listに格納した構造体フィールドの値を参照
    Console.WriteLine("list[0].F = {0}", list[0].F);

    // フィールドの値を変更したい構造体を一時変数にコピーする
    S tmp = list[0];

    // 一時変数に代入した構造体のフィールドの値を変更する
    tmp.F = 16;

    // 変更した構造体を再度Listに格納する
    list[0] = tmp;

    // Listに格納した構造体フィールドの値を参照
    Console.WriteLine("list[0].F = {0}", list[0].F);
  }
}
実行結果
list[0].F = 3
list[0].F = 16

構造体ではなくクラスのインスタンスを格納する場合はこのような操作を行う必要はありません。 この違いは型が値型か参照型かによって決まります。 詳しくは値型と参照型 §.値型のプロパティ・インデクサを参照してください。

§5 要素の削除

§5.1 値を指定した削除 (Remove)

削除したい値を指定してListに格納されている要素を削除するにはRemoveメソッドを使います。 List内に同じ値を持つ要素が複数ある場合、Removeメソッドは最初に見つかったものを削除します。

Listから要素を削除した場合、RemoveメソッドはTrueを返します。 削除しようとした値をもつ要素がList内に無かった場合、RemoveメソッドはFalseを返します。

Removeメソッドを使って指定された値を持つ要素をList内から削除する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<int> list = new List<int>() {0, 1, 2, 1, 0};

    Print(list);

    // 値1を持つ最初の要素を削除する
    // (最初に見つかった1が削除される)
    list.Remove(1);

    Print(list);

    // 値1を持つ最初の要素を削除する
    list.Remove(1);

    Print(list);
  }

  static void Print(List<int> list)
  {
    foreach (int e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 1, 0, 
0, 2, 1, 0, 
0, 2, 0,

Removeメソッドでは削除したい値を指定します。 削除したい要素のインデックスを指定して要素を削除したい場合はRemoveAtメソッドメソッドを使うことができます。

§5.2 インデックスを指定した削除 (RemoveAt)

削除したい要素のインデックスを指定してListに格納されている要素を削除するにはRemoveAtメソッドを使います。

RemoveAtメソッドを使って指定された位置にあるList内の要素を削除する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<int> list = new List<int>() {0, 1, 2, 3, 4};

    Print(list);

    // インデックス1の要素を削除する
    // ({0, 1, 2, 3, 4}の1が削除される)
    list.RemoveAt(1);

    Print(list);

    // インデックス2の要素を削除する
    // ({0, 2, 3, 4}の3が削除される)
    list.RemoveAt(2);

    Print(list);
  }

  static void Print(List<int> list)
  {
    foreach (int e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 4, 
0, 2, 3, 4, 
0, 2, 4, 

RemoveAtメソッドでは削除したい要素のインデックスを指定します。 削除したい値を指定して要素を削除したい場合はRemoveメソッドメソッドを使うことができます。 また、削除したい要素の範囲を指定して要素を削除したい場合はRemoveRangeメソッドを使うことができます。

§5.3 範囲を指定した削除 (RemoveRange)

RemoveRangeメソッドを使うとList内の指定した範囲にある複数の要素を削除することができます。

RemoveRangeメソッドを使って指定した範囲にある要素をListから削除する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<int> list = new List<int>() {0, 1, 2, 3, 4};

    Print(list);

    // インデックス2から要素3個を削除する
    // ({0, 1, 2, 3, 4}の2, 3, 4が削除される)
    list.RemoveRange(2, 3);

    Print(list);
  }

  static void Print(List<int> list)
  {
    foreach (int e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 4, 
0, 1, 

§5.4 全要素のクリア (Clear)

Clearメソッドを使うと、List内の全ての要素を削除することができます。 Clearメソッドを呼び出すと、Listは空になり長さ(Length)が0となります。

Clearメソッドを使ってListを空にする
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<int> list = new List<int>() {0, 1, 2, 3, 4};

    Print(list);

    // List内の全要素を削除する
    list.Clear();

    Print(list);

    // 新たにListへ要素を追加する
    list.Add(5);
    list.Add(6);

    Print(list);
  }

  static void Print(List<int> list)
  {
    foreach (int e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 4, 

5, 6, 

§5.5 列挙操作中の要素の削除

Listをforeach文で列挙している最中に要素の削除などの変更を行おうとすると、「列挙操作は実行されない可能性があります。」というメッセージとともに例外InvalidOperationExceptionがスローされます。 これを回避する方法などについては列挙操作中のコレクションの変更で詳しく解説しています。

§5.6 要素の削除に関するほかの操作

RemoveAllメソッドを使うと、条件を指定して要素を削除することが出来ます。 例えば「値が5以上の要素を削除する」、「値が"Str"で始まる要素を削除する」といった削除を行いたい場合は、RemoveAllメソッドを使うことができます。

要素の削除を行ったのちにTrimExcessメソッドを使うことにより、List内で確保されている領域のうち、不要になった分を削減することができます。

§6 要素の検索

§6.1 要素の有無 (Contains)

List内に特定の値をもつ要素が含まれているかどうかを調べるにはContainsメソッドを使うことが出来ます。

ContainsメソッドでList内に指定した要素が含まれているか調べる
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie"};

    // List内に値"Bob"を持つ要素があるかどうか
    Console.WriteLine("Contains(Bob) : {0}", list.Contains("Bob"));

    // List内に値"Dave"を持つ要素があるかどうか
    Console.WriteLine("Contains(Dave) : {0}", list.Contains("Dave"));
  }
}
実行結果
Contains(Bob) : True
Contains(Dave) : False

Exists等のメソッドを使うと、より複雑な条件を指定して要素を検索することが出来ます。 また、文字列を格納するリストで大文字小文字を無視した検索をしたい場合などにはこれらのメソッドを使う必要があります。 詳しくは§.述語(Predicate)を用いた検索で後述します。

§6.2 要素の位置の検索 (IndexOf, LastIndexOf)

List内のどこに含まれているかを調べるにはIndexOfメソッドを使うことが出来ます。 IndexOfメソッドでは、要素が見つかった場合はその要素のインデックスを返します。 もし見つからなければ-1が返されます。

IndexOfメソッドはListの先頭から調べて最初に見つかった要素のインデックスを返しますが、LastIndexOfメソッドを使うとListの末尾から調べて最初に見つかった要素のインデックス(List内で一番最後にあるインデックス)を取得することができます。 検索したい要素がListの後方にあることが想定される場合は、LastIndexOfメソッドを使った方が早く見つけることができます。

IndexOfメソッド・LastIndexOfメソッドを使ってList内にある要素の位置を検索して取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Bob", "Alice"};

    // 値"Bob"を持つ最初の要素の位置を取得する
    Console.WriteLine("IndexOf(Bob) = {0}", list.IndexOf("Bob"));

    // 値"Eve"を持つ最初の要素の位置を取得する
    Console.WriteLine("IndexOf(Eve) = {0}", list.IndexOf("Eve"));

    // 値"Alice"を持つ最後の要素の位置を取得する
    Console.WriteLine("LastIndexOf(Alice) = {0}", list.LastIndexOf("Alice"));
  }
}
実行結果
IndexOf(Bob) = 1
IndexOf(Eve) = -1
LastIndexOf(Alice) = 4

FindIndex・FindLastIndex等のメソッドを使うと、より複雑な条件を指定して要素を検索することが出来ます。 また、文字列を格納するリストで大文字小文字を無視した検索をしたい場合などにはこれらのメソッドを使う必要があります。 詳しくは§.述語(Predicate)を用いた検索で後述します。

§6.3 二分探索 (BinarySearch)

ContainsメソッドIndexOfメソッドではList内の要素を先頭から走査して一致する要素を検索します。 BinarySearchメソッドを使うと二分探索によって要素を検索することができます。

二分探索による検索では、あらかじめList内の要素がソートされていないと結果が不正となる一方、IndexOfメソッドよりも高速に検索を行うことができるという特徴があります(IndexOfやContainsはO(n)、BinarySearchはO(log n))。 格納されている要素が多数となる場合には二分探索による検索がより効果的です。

二分探索によってList内にある要素の位置を検索して取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Bob", "Alice", "Eve", "Charlie", "Dave"};

    // 二分探索を行う前にlistをソートされた状態にしておく
    list.Sort();

    // 値"Bob"を持つ要素の位置を取得する
    Console.WriteLine("BinarySearch(Bob) = {0}", list.BinarySearch("Bob"));

    // 値"Eve"を持つ要素の位置を取得する
    Console.WriteLine("BinarySearch(Eve) = {0}", list.BinarySearch("Eve"));

    // 存在しない値"Adam"の位置を取得する(負の値が返される)
    Console.WriteLine("BinarySearch(Adam) = {0}", list.BinarySearch("Adam"));
  }
}
実行結果
BinarySearch(Bob) = 1
BinarySearch(Eve) = 4
BinarySearch(Adam) = -1

Listとソートについては§.ソート (Sort)で解説します。

§6.4 最初の要素・最後の要素

ListにはFirstやLastといった最初の要素・最後の要素を参照するプロパティは用意されていません。 そのため、次のように直接インデックスを指定して要素を参照する必要があります。

直接インデックスを指定してListの最初の要素・最後の要素を参照する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // 最初の要素(インデックスが0の要素)を参照
    Console.WriteLine(list[0]);

    // 最後の要素(インデックスがCount - 1の要素)を参照
    Console.WriteLine(list[list.Count - 1]);
  }
}
実行結果
Alice
Eve

LINQの拡張メソッドであるFirstメソッドLastメソッドを使うことでも最初の要素・最後の要素を取得することができます。

Firstメソッド・Lastメソッドを使ってListの最初の要素・最後の要素を参照する
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // 最初の要素を取得
    Console.WriteLine(list.First());

    // 最後の要素を取得
    Console.WriteLine(list.Last());
  }
}
実行結果
Alice
Eve

最初の要素・最後の要素を参照することが多くなる場合は、ListではなくStackQueueLinkedListなどのコレクションを使う方がアルゴリズム上適切な可能性があります。

LinkedListでは最初の要素・最後の要素を表すFirstプロパティとLastプロパティが用意されています。

Listクラスでは先頭・末尾の要素の追加/取り出しを行うpush/popshift/unshiftのようなメソッドは提供されません。 push/popを行いたい場合はStackクラスshift/unshiftを行いたい場合はQueueクラスを使うようにしてください。

§6.5 最大の要素・最小の要素

ListにはMinやMaxといったコレクション内での最大の値を持つ要素・最小の値を持つ要素を参照するプロパティは用意されていません。 そのため、Listのすべての要素を走査して最大・最小の要素を検索する必要があります。

List内の最大の要素・最小の要素を検索する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<int> list = new List<int>() {0, 4, 2, 5, 3, 1};

    // 最大の要素を取得
    int max = 0;

    foreach (int e in list) {
      if (max < e) max = e;
    }

    Console.WriteLine("max = {0}", max);

    // 最小の要素を取得
    int min = int.MaxValue;

    foreach (int e in list) {
      if (e < min) min = e;
    }

    Console.WriteLine("min = {0}", min);
  }
}
実行結果
max = 5
min = 0

LINQの拡張メソッドであるMaxメソッドMinメソッドを使うことでも最大の要素・最小の要素を取得することができます。

Maxメソッド・Minメソッドを使ってList内の最大の要素・最小の要素を取得する
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    List<int> list = new List<int>() {0, 4, 2, 5, 3, 1};

    // 最大の要素を取得
    Console.WriteLine("max = {0}", list.Max());

    // 最小の要素を取得
    Console.WriteLine("min = {0}", list.Min());
  }
}
実行結果
max = 5
min = 0

SortedSetでは、Maxプロパティ・Minプロパティを参照することにより最大の要素・最小の要素を取得することができるようになっています。

§6.6 述語(Predicate)を用いた検索

Listクラスでは、述語(Predicate<T>デリゲート)を用いた検索・一致の検証が行えます。 条件式を記述したメソッドを一種の引数として渡すことで、その条件に合致する要素が存在するかどうか、といった操作が出来るようになります。

例えば、Containsメソッドでは引数に具体的な値を指定してその要素が含まれているかどうかは調べられますが、どのような要素が含まれているかという条件を指定することは出来ません。 対してExistsメソッドを使うと、「文字列の長さが5以上の」や「数値の大きさが16未満の」といった条件を指定した上で、それに合致する要素が含まれているかどうかを調べることができます。

Existsメソッドを使って条件に合致する要素がListに含まれるかどうか調べる(匿名メソッド版、C# 2.0以降)
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // 長さが5以上の要素が存在するかどうか
    Console.WriteLine("Exists: {0}", list.Exists(delegate(string s) { return 5 <= s.Length; }));
  }
}
実行結果
Exists: True

述語を用いた検索・一致の検証の場合、多くの場合既存のメソッドではなく匿名メソッドやラムダ式を使って述語となるデリゲートを記述することになります。 匿名メソッド・ラムダ式によるデリゲートの記述方法について詳しくはデリゲートの基本 §.匿名メソッドを使ったデリゲートの作成およびデリゲートの基本 §.ラムダ式を使ったデリゲートの作成を参照してください。

このように、Predicateデリゲートをメソッドの引数として渡すことで、そのデリゲートによって記述された述語の条件にあう要素の検索などが行えます。

Existsメソッド以外にも、Listクラスには述語を使うことができるメソッドとして次のようなメソッドが用意されています。 なお、LINQのメソッドを使うことでも同様の操作ができるため、参考のため相当する操作を行えるLINQメソッドについても併記しています。

述語を使った検索・一致の検証を行うためのListクラスのメソッド
メソッド 動作 相当するLINQのメソッドの呼び出し
合致する要素の取得 Find 述語で記述した条件に合致する最初の要素を取得する
(合致する要素がない場合は型のデフォルト値*1が返される)
FirstOrDefault(Func<TSource, bool>)
FindLast 述語で記述した条件に合致する最後の要素を取得する
(合致する要素がない場合は型のデフォルト値*1が返される)
LastOrDefault(Func<TSource, bool>)
FindAll 述語で記述した条件に合致するすべての要素をListで取得する
(合致する要素がない場合は空のListが返される)
Where(Func<TSource, bool>) + ToList()
合致する要素のインデックスの取得 FindIndex 述語で記述した条件に合致する最初の要素のインデックスを取得する
(合致する要素がない場合は-1が返される)
Select(Func<TSource, int, TResult>) + First(Func<TSource, bool>)
FindLastIndex 述語で記述した条件に合致する最後の要素のインデックスを取得する
(合致する要素がない場合は-1が返される)
Select(Func<TSource, int, TResult>) + Last(Func<TSource, bool>)
合致する要素の有無の検証 Exists 述語で記述した条件に合致する要素が存在するかどうかを調べる Any(Func<TSource, bool>)
TrueForAll 述語で記述した条件に、すべての要素が合致するかどうかを調べる All(Func<TSource, bool>)
合致する要素の削除 RemoveAll 述語で記述した条件に合致する要素を削除する Where(Func<TSource, bool>) + ToList()
(FindAllの述語を否定(NOT)した述語を指定)
*1型のデフォルト値
参照型のListではnull/Nothing、値型のListでは00に相当する値が返されます。 型とデフォルト値については型の種類・サイズ・精度・値域 §.型のデフォルト値を参照してください。

以下は、これらのメソッドを使った例です。

§6.6.1 条件に合致する要素の検索 (Find・FindLast・FindAll)

Find・FindLast・FindAllメソッドを使って条件に合致するList内の要素を取得する(匿名メソッド版、C# 2.0以降)
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // 長さが3である最初の要素
    Console.WriteLine("Find: {0}", list.Find(delegate(string s) { return s.Length == 3; }));

    // 長さが3である最後の要素
    Console.WriteLine("FindLast: {0}", list.FindLast(delegate(string s) { return s.Length == 3; }));

    // "e"を含むすべての要素
    List<string> found = list.FindAll(delegate(string s) { return s.Contains("e"); });

    Console.Write("FindAll: ");
    foreach (string e in found) {
      Console.Write("{0}, ", e);
    }
    Console.WriteLine();
  }
}
実行結果
Find: Bob
FindLast: Eve
FindAll: Alice, Charlie, Dave, Eve, 

§6.6.2 条件に合致する要素のインデックスの取得 (FindIndex・FindLastIndex)

FindIndex・FindLastIndexメソッドを使ってListから条件に合致する要素のインデックスを取得する(ラムダ式版、C# 3.0以降)
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // "ve"で終わるもののうち、最初の要素のインデックス
    Console.WriteLine("FindIndex: {0}", list.FindIndex(s => s.EndsWith("ve")));

    // "ve"で終わるもののうち、最後の要素のインデックス
    Console.WriteLine("FindLastIndex: {0}", list.FindLastIndex(s => s.EndsWith("ve")));

    // "ve"で終わるもののうち、最初の要素のインデックス
    // (Select+Firstメソッドを使ったFindIndexとの比較)
    Console.WriteLine("Select+First: {0}", list.Select((s, index) => new {Element = s, Index = index}).First(e => e.Element.EndsWith("ve")).Index);

    // "ve"で終わるもののうち、最後の要素のインデックス
    // (Select+Lastメソッドを使ったFindLastIndexとの比較)
    Console.WriteLine("Select+Last: {0}", list.Select((s, index) => new {Element = s, Index = index}).Last(e => e.Element.EndsWith("ve")).Index);
  }
}
実行結果
FindIndex: 3
FindLastIndex: 4
Select+First: 3
Select+Last: 4

比較として、LINQのSelectメソッドFirstメソッドLastメソッドを使った場合についても併記しています。 なお、要素が見つからなかった場合は例外InvalidOperationExceptionがスローされるため、必要に応じて実装を書き換える必要があります。

§6.6.3 条件に合致する要素の有無 (Exists・TrueForAll)

Exists・TrueForAllメソッドを使って条件に合致する要素がListに含まれるかどうか調べる(匿名メソッド版、C# 2.0以降)
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // 長さが5以上の要素が存在するかどうか
    Console.WriteLine("Exists: {0}", list.Exists(delegate(string s) { return 5 <= s.Length; }));

    // すべての要素が"e"で終わるかどうか
    Console.WriteLine("TrueForAll: {0}", list.TrueForAll(delegate(string s) { return s.EndsWith("e"); }));

    // すべての要素の長さが10未満かどうか
    Console.WriteLine("TrueForAll: {0}", list.TrueForAll(delegate(string s) { return s.Length < 10; }));
  }
}
実行結果
FindLast: Eve
TrueForAll: False
TrueForAll: True

§6.6.4 条件に合致する要素の削除 (RemoveAll)

RemoveAllメソッドを使って条件に合致するすべての要素をListから削除する(匿名メソッド版、C# 2.0以降)
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // "li"を含むすべての要素を削除する
    list.RemoveAll(delegate(string s) { return s.Contains("li"); });

    foreach (string e in list) {
      Console.WriteLine(e);
    }
  }
}
実行結果
Bob
Dave
Eve

§7 複写・複製

§7.1 配列への変換 (ToArray)

ToArrayメソッドを使うと、Listと同じ内容の配列を作成することができます。 このメソッドは、現在Listに格納されている内容をコピーした配列を返します。

ToArrayメソッドを使ってListの内容を配列に変換する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave"};

    list.Add("Eve");

    // Listの内容を配列に変換
    string[] arr = list.ToArray();

    // 変換した配列の内容を表示
    Console.WriteLine("Length = {0}", arr.Length);

    foreach (string e in arr) {
      Console.WriteLine(e);
    }
  }
}
実行結果
Length = 5
Alice
Bob
Charlie
Dave
Eve

このメソッドでは簡易コピーが行われるため、参照型を要素に持つListの場合は参照のみがコピーされます。 簡易コピーについてより詳しくはオブジェクトの複製で解説しています。

このメソッドではAsReadOnlyメソッド等とは異なり、Listに対するビューを返すのではなく、同じ内容を持つ配列を作成します。 そのため、作成元のListと作成後の配列のどちらかに変更を加えた場合でも、もう一方に影響することはありません。

§7.2 配列への複写 (CopyTo)

Listの全部あるいは一部分だけを既存の配列にコピー(複写)したい場合は、CopyToメソッドを使うことができます。 ToArrayメソッドとは異なり、コピー先となる配列はあらかじめ用意しておく必要があります。

CopyToメソッドを使ってListの一部を配列にコピーする
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // コピー先の配列を用意
    string[] arr = new string[5];

    // Listのインデックス2から3要素分を配列のインデックス0以降にコピー
    list.CopyTo(2, arr, 0, 3);

    Print(arr);

    // Listのインデックス0から2要素分を配列のインデックス3以降にコピー
    list.CopyTo(0, arr, 3, 2);

    Print(arr);
  }

  static void Print(string[] arr)
  {
    foreach (string e in arr) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Charlie, Dave, Eve, , , 
Charlie, Dave, Eve, Alice, Bob, 

このメソッドでは簡易コピーが行われるため、参照型を要素に持つListの場合は参照のみがコピーされます。 簡易コピーについてより詳しくはオブジェクトの複製で解説しています。

Listの全要素を配列にして扱いたい場合は前述のToArrayメソッドを使用することもできます。

§7.3 Listの複製

Listのコンストラクタに別のListを指定すると、それと全く同じ内容のListが作成されます。 これにより、Listの複製を作成することができます。

Listのコンストラクタを使って既存のListの複製を作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list1 = new List<string>() {"Alice", "Bob", "Charlie"};

    Print(list1);

    // list1と同じの内容Listを作成する
    List<string> list2 = new List<string>(list1);

    Print(list2);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Alice, Bob, Charlie, 
Alice, Bob, Charlie, 

このコンストラクタによる複製では簡易コピーが行われるため、参照型を要素に持つListの場合は参照のみがコピーされます。 簡易コピーについてより詳しくはオブジェクトの複製で解説しています。

§7.4 Listの一部分の複製・サブセットの取得 (GetRange)

GetRangeメソッドを使うと、Listの一部分のみを複製したList(Listのサブセット)を作成することができます。

GetRangeメソッドを使ってListの一部分を切り出したListを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // listのインデックス2から3つ分を切り出したListを作成する
    List<string> sublist = list.GetRange(2, 3);

    Print(sublist);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Charlie, Dave, Eve, 

このメソッドによる複製では簡易コピーが行われるため、参照型を要素に持つListの場合は参照のみがコピーされます。 簡易コピーについてより詳しくはオブジェクトの複製で解説しています。

このメソッドは、部分配列を構成するArraySegment構造体と似ていますが、元の配列に対するビューとして動作するArraySegmentとは異なり、GetRangeメソッドが返すListは元のListを複製したものになります。 従って、元になったList・GetRangeメソッドによって取得したListに変更を加えても、もう一方に影響が及ぶことはありません。


参考までに、LINQのSkipメソッドTakeメソッドを使うことによってもコレクションの一部分を取得することができます。 これらのメソッドは、Listだけでなく配列を含む任意のコレクションに対して用いることができます。

Skipメソッド・Takeメソッドを使ってコレクションの一部分を切り出したListを作成する
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    string[] arr = new string[] {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // arrの先頭2つを飛ばし、その後の3つ分を切り出したListを作成する
    List<string> sublist = arr.Skip(2).Take(3).ToList();

    Print(sublist);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Charlie, Dave, Eve, 

§8 型変換・全要素の変換 (ConvertAll)

ConvertAllメソッドを使うと、要素のすべてを別の型に変換したListを作成できます。 ConvertAllメソッドでは、引数に変換関数となるメソッドをデリゲート(Converterデリゲート)として指定すると、List内の要素すべてに同じ変換関数を適用した結果を得ることができます。

ConvertAllメソッドを使って文字列型のListから数値型のListに変換する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 文字列型のList
    List<string> stringList = new List<string>() {"0", "1", "2", "3", "4"};

    // stringList内のすべての要素をint.Parseメソッドで数値に変換する
    List<int> intList = stringList.ConvertAll(int.Parse);

    foreach (int e in intList) {
      Console.WriteLine(e);
    }
  }
}
実行結果
0
1
2
3
4

Integer.Parseなどの型変換のメソッドについては基本型の型変換を参照してください。


またConvertAllメソッドは、型変換だけでなくすべての要素に同じ処理を施した結果を取得するといった方法にも使えます。

ConvertAllメソッドを使ってList内のすべての文字列を長さを求めたListを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> stringList = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // stringListのすべての要素に対して、その長さを求めたListを作成する
    List<int> lengthList = stringList.ConvertAll(delegate(string s) { return s.Length; });

    for (int index = 0; index < stringList.Count; index++) {
      Console.WriteLine("\"{0}\".Length = {1}", stringList[index], lengthList[index]);
    }
  }
}
実行結果
"Alice".Length = 5
"Bob".Length = 3
"Charlie".Length = 7
"Dave".Length = 4
"Eve".Length = 3
ConvertAllメソッドを使ってList内のすべての文字列をリバースしたListを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // list内のすべての要素を変換する
    List<string> reversedList = list.ConvertAll(delegate(string s) {
      // 文字列(list内の各要素)をリバースする
      char[] chars = s.ToCharArray();

      Array.Reverse(chars);

      return new string(chars);
    });

    foreach (string e in reversedList) {
      Console.WriteLine(e);
    }
  }
}
実行結果
ecilA
boB
eilrahC
evaD
evE

ConvertAllメソッドではなく、LINQのSelectメソッドを使うことでも同様のことを行えます。 Selectメソッドは、ConvertAllのようなメソッドをもたない他のコレクションに対しても使用することができます。 そのため、他のコレクションの内容を一度Listに移してからConvertAllメソッドを呼ぶ、といった手順を踏む必要がなくなります。

Selectメソッドを使ってList内のすべての要素を変換したListを作成する
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // list内のすべての要素を変換する
    List<string> reversedList = list.Select(delegate(string s) {
      // 文字列(list内の各要素)をリバースする
      char[] chars = s.ToCharArray();

      Array.Reverse(chars);

      return new string(chars);
    }).ToList();

    foreach (string e in reversedList) {
      Console.WriteLine(e);
    }
  }
}
実行結果
ecilA
boB
eilrahC
evaD
evE

§9 要素の並べ替え

§9.1 ソート (Sort)

Sortメソッドを使うことでList内の要素をソートすることが出来ます。

Sortメソッドを使ってList内の要素をソートする
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<int> list = new List<int>() {2, 4, 0, 1, 3};

    // List内の要素をソート (昇順に並べ替える)
    list.Sort();

    Print(list);
  }

  static void Print(List<int> list)
  {
    foreach (int e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 4, 

Sortメソッドでは、インスタンス自身をソートします(破壊的変更)。 ソートされたListが新たに作成され戻り値として返されることはありません。 そのため、ソート前の状態も維持しておきたい場合は、あらかじめListの複製を作っておき、その後で変更用のListをソートする必要があります。

非破壊的なソートを行いたい場合はEnumerable.OrderByメソッドを使うことができます。

大文字小文字を無視したソートや降順でのソートなど、Listクラスを使ったソートについては基本型のソートと昇順・降順でのソート複合型のソート・複数キーでのソート、デフォルトでどのような順序でソートされるかについては基本型とデフォルトのソート順で詳しく解説しています。

§9.2 リバース (Reverse)

Reverseメソッドを使うことでList内の要素の並びを逆順にする(リバースする)ことができます。

Reverseメソッドを使ってList内の要素をリバースする
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Charlie", "Dave", "Bob"};

    // List内の要素をリバース (逆順に並べ替える)
    list.Reverse();

    Print(list);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Bob, Dave, Charlie, Alice, 

Reverseメソッドでは、インスタンス自身を逆順にします(破壊的変更)。 リバースされたListが新たに作成され戻り値として返されることはありません。 そのため、リバース前の状態も維持しておきたい場合は、あらかじめListの複製を作っておき、その後で変更用のListをリバースする必要があります。

非破壊的なリバースを行いたい場合はLINQのReverseメソッド(Enumerable.Reverse)を使うことができます。

§9.3 降順でのソート (Sort+Reverse)

Sortメソッドはデフォルトでは昇順での並べ替えを行いますが、Reverseメソッドを組み合わせることによって降順でのソート結果を得ることが出来ます。

Sortメソッド+Reverseメソッドを使ってList内の要素を降順にソートする
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Charlie", "Dave", "Bob"};

    // List内の要素をソートしてリバース (降順に並べ替える)
    list.Sort();
    list.Reverse();

    Print(list);
  }

  static void Print(List<string> list)
  {
    foreach (string e in list) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Dave, Charlie, Bob, Alice,

そのほかの降順でのソート方法については基本型のソートと昇順・降順でのソート §.降順でのソートで詳しく解説しています。

§10 読み取り専用 (AsReadOnly)

AsReadOnlyメソッドを使うと、Listと同じ内容をもった読み取り専用コレクション(ReadOnlyCollection)を取得することが出来ます。 このメソッドを使って作成した読み取り専用のコレクションに対しては、要素の追加・削除・変更などの操作を行うことが出来ません(行おうとするとNotSupportedExceptionがスローされます)。

なお、AsReadOnlyメソッドは元のListを読み取り専用にするものではないので注意してください。 このメソッドは単に、元のListを参照する読み取り専用のコレクション、Listに対する読み取り専用のビューを作成します。 元になったListに対しては、依然として要素の追加・削除・変更が可能です。

AsReadOnlyメソッドを使ってListを読み取り専用にしたコレクションを取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // 読み取り専用にしたものをIListとして格納する
    IList<string> readOnlyList = list.AsReadOnly();

    // IList.Addメソッドを使って要素の追加を試みる
    // (読み取り専用のため例外NotSupportedExceptionがスローされる)
    readOnlyList.Add("Frank");
  }
}
実行結果
ハンドルされていない例外: System.NotSupportedException: コレクションは読み取り専用です。
   場所 System.Collections.ObjectModel.ReadOnlyCollection`1.System.Collections.Generic.ICollection<T>.Add(T value)
   場所 Sample.Main()

ReadOnlyCollectionについてより詳しくは汎用ジェネリックコレクション(1) Collection/ReadOnlyCollection §.ReadOnlyCollectionを参照してください。

配列を読み取り専用にする場合についての配列操作 §.読み取り専用化 (AsReadOnly)も合わせて参照してください。

§11 容量

Listでは、あらかじめListの内部に大きめの配列を用意しておき、そこに要素を格納していくことで可変長配列の機能を実現しています。 そのためListでは、実際にListに格納されている要素数よりも大きい配列が自動的に確保されます。 CountプロパティでList内に含まれる要素数を取得することができるのに対し、Capacityプロパティを参照することでList内で確保されている配列の容量を知ることができます。

以下のコードを使って、空のListに1つずつ要素を追加していったときのListの容量の変化を見てみます。

Listの要素数Countと容量Capacityの変化
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 空のListを作成
    List<int> list = new List<int>();

    // 現在の要素数とListの容量を表示
    Console.WriteLine("Count={0} Capacity={1}", list.Count, list.Capacity);

    // 要素を20個追加する
    for (int i = 0; i < 20; i++) {
      list.Add(i);

      // 現在の要素数とListの容量を表示
      Console.WriteLine("Count={0} Capacity={1}", list.Count, list.Capacity);
    }

    Console.WriteLine();

    // Listを空にする
    list.Clear();

    // 現在の要素数とListの容量を表示
    Console.WriteLine("Count={0} Capacity={1}", list.Count, list.Capacity);
  }
}
実行結果
Count=0 Capacity=0
Count=1 Capacity=4
Count=2 Capacity=4
Count=3 Capacity=4
Count=4 Capacity=4
Count=5 Capacity=8
Count=6 Capacity=8
Count=7 Capacity=8
Count=8 Capacity=8
Count=9 Capacity=16
Count=10 Capacity=16
Count=11 Capacity=16
Count=12 Capacity=16
Count=13 Capacity=16
Count=14 Capacity=16
Count=15 Capacity=16
Count=16 Capacity=16
Count=17 Capacity=32
Count=18 Capacity=32
Count=19 Capacity=32
Count=20 Capacity=32

Count=0 Capacity=32

この結果からも、Listでは要素の追加を行う際など必要になった時点で容量を拡張していることがわかります。 また、Clearメソッドを呼び出したあとも容量が変わっていないことがわかります。

このように、Listでは常に格納されている要素数以上の容量を持ちます。 また、Clearメソッドなどによって要素数が減っても一度確保された容量が減ることはありません。 従って、Listに対して多数の要素の追加・削除を行った後には、特に操作を行わないかぎり使われなくなった(必要以上の)領域がそのまま残されることになります。

Listへの要素の追加と、List内部の状態
Listに対する操作 List内部の状態
- 0 1 (空き) (空き)
(操作前の状態)
Count=2
Capacity=4
list.Add(2) 0 1 2 (空き)
(空いている領域があるため、そこに要素が追加される)
Count=3
Capacity=4
list.Add(3) 0 1 2 3
(空いている領域があるため、そこに要素が追加されるが、以降追加するための空き領域がなくなる)
Count=4
Capacity=4
list.Add(4) 0 1 2 3 (空き) (空き) (空き) (空き)
(まずList内で自動的に容量の拡張が行われ、追加するための領域が確保される)
Count=4
Capacity=8
0 1 2 3 4 (空き) (空き) (空き)
(確保された領域に要素が追加される)
Count=5
Capacity=8
ListのクリアとList内部の状態
Listに対する操作 List内部の状態
- 0 1 2 (空き)
(操作前の状態)
Count=3
Capacity=4
list.Clear() (空き) (空き) (空き) (空き)
(すべての要素が削除され、各領域は空き状態になるが、容量自体は削減されない)
Count=0
Capacity=4

§11.1 容量の縮小 (TrimExcess)

Listにそれ以上要素を追加する必要がなくなった場合などには、TrimExcessメソッドを使用することで不要な容量を減らすことができます。

TrimExcessメソッドを使ってList内部の不要になっている容量を減らす
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 空のListを作成
    List<int> list = new List<int>();

    // 1500個の要素を追加
    for (int i = 0; i < 1500; i++) {
      list.Add(i);
    }

    // 先頭から500個の要素を削除
    list.RemoveRange(0, 500);

    // 要素数とListの容量を表示
    Console.WriteLine("[before]");
    Console.WriteLine("Count={0} Capacity={1}", list.Count, list.Capacity);

    // Listが確保している容量を必要最低限に減らす
    list.TrimExcess();

    // 要素数とListの容量を表示
    Console.WriteLine("[after]");
    Console.WriteLine("Count={0} Capacity={1}", list.Count, list.Capacity);
  }
}
実行結果
[before]
Count=1000 Capacity=2048
[after]
Count=1000 Capacity=1000

このメソッドはList内の再割当てを行うことで使用するメモリを最小化します。 そのため、Listにそれ以上変更を加えない(追加によって容量を再度拡張する必要がない)ことが明らかな場合などに用いるべきで、不必要に何度も呼び出すことはパフォーマンスの劣化に繋がります。

List.TrimExcessとList内部の状態
Listに対する操作 List内部の状態
- 0 1 499 500 1499 (空き) (空き)
(操作前の状態・インデックス1500〜2047までの領域は空きとなっている)
Count=1500
Capacity=2048
list.RemoveRange(0, 500) (空き) (空き) 500 1499 (空き) (空き)
(インデックス0〜499までの領域が空きとなる)
Count=1000
Capacity=2048
list.TrimExcess() 500 1499
(領域の再作成と要素の再配置が行われ、必要最低限の容量になる)
Count=1000
Capacity=1000

§11.2 初期容量

Listでは容量の拡張が行われる度に領域の再割当てと要素のコピーが行われます。 そのため、Listに格納される要素の最大数があらかじめ見積もれる場合には、Listの初期容量を指定することにより再割当てとコピーを抑止することができ、パフォーマンスの向上が見込めます。 また、不必要に大きい領域の確保が行われなくなるというメリットもあります。 Listでは、コンストラクタで初期容量を指定してインスタンスを作成することができるようになっています。

次の例では、初期容量を指定しないListと指定したListについて、要素を10000個追加する場合の処理時間の違いを計測しています。

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Sample {
  private static void Case1()
  {
    // 空のListを作成
    List<int> list = new List<int>();

    // 10000個の要素を追加する場合の処理時間を計測
    Stopwatch sw = Stopwatch.StartNew();

    for (int i = 0; i < 10000; i++) {
      list.Add(i);
    }

    Console.WriteLine("Count={0} Capacity={1} Elapsed={2}", list.Count, list.Capacity, sw.Elapsed);
  }

  private static void Case2()
  {
    // 初期容量を指定してListを作成
    List<int> list = new List<int>(10000);

    // 10000個の要素を追加する場合の処理時間を計測
    Stopwatch sw = Stopwatch.StartNew();

    for (int i = 0; i < 10000; i++) {
      list.Add(i);
    }

    Console.WriteLine("Count={0} Capacity={1} Elapsed={2}", list.Count, list.Capacity, sw.Elapsed);
  }

  static void Main()
  {
    // Case1とCase2を5回ずつ試行
    Console.WriteLine("Case1");
    for (int t = 0; t < 5; t++) {
      Case1();
    }

    Console.WriteLine("Case2");
    for (int t = 0; t < 5; t++) {
      Case2();
    }
  }
}
実行結果
Case1
Count=10000 Capacity=16384 Elapsed=00:00:00.0013732
Count=10000 Capacity=16384 Elapsed=00:00:00.0004316
Count=10000 Capacity=16384 Elapsed=00:00:00.0001275
Count=10000 Capacity=16384 Elapsed=00:00:00.0002532
Count=10000 Capacity=16384 Elapsed=00:00:00.0002470
Case2
Count=10000 Capacity=10000 Elapsed=00:00:00.0000754
Count=10000 Capacity=10000 Elapsed=00:00:00.0000750
Count=10000 Capacity=10000 Elapsed=00:00:00.0000717
Count=10000 Capacity=10000 Elapsed=00:00:00.0000683
Count=10000 Capacity=10000 Elapsed=00:00:00.0000891

初期容量を指定してListのインスタンスを作成しても、その数の要素数が確保されるわけではありません。 初期容量として指定した値に関わらず、インスタンスを作成した直後の要素数は0となります。

Listの初期容量を指定してもその要素数は確保されない
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 初期容量に100を指定してListを作成
    List<int> list = new List<int>(100);

    // CountとCapacityを表示
    Console.WriteLine("Count = {0}", list.Count);
    Console.WriteLine("Capacity = {0}", list.Capacity);

    // インデックス50の要素を設定
    // (現在の要素数は0で、インデックス50は範囲外であるため、例外が発生する)
    list[50] = 0;
  }
}
実行結果
Count = 0
Capacity = 100

ハンドルされていない例外: System.ArgumentOutOfRangeException: インデックスが範囲を超えています。負でない値で、コレクションのサイズよりも小さくなければなりません。
パラメーター名: index
   場所 System.Collections.Generic.List`1.set_Item(Int32 index, T value)
   場所 Sample.Main()

§12 入れ子

List<T>は任意の型Tに型付けしたリストを作成できるため、TにList<T>を指定すれば入れ子になったリストList<List<T>>を作成することが出来ます。 例えば可変長のジャグ配列のようなものを作成したい場合は、Listを入れ子にすることで実現できます。 入れ子になったListを列挙する場合、例えばList<List<int>>を列挙すると、List<List<int>>に含まれているList<int>を一つずつ取り出せます。

次の例では、入れ子になったListを作成し、追加や削除・列挙などの操作を行っています。

Listを入れ子にして扱う
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 入れ子になったListを作成
    List<List<string>> dictionary = new List<List<string>>() {
      // Listの中にListを追加
      new List<string>() {"apple", "action", "after"},  // 'a'で始まる単語のList
      new List<string>() {"big", "best", "bridge"},     // 'b'で始まる単語のList
      new List<string>() {"cheese", "cat", "connect"},  // 'c'で始まる単語のList
    };

    dictionary[0].Add("add");         // 'a'で始まる単語のListにアイテムを追加
    dictionary[1].Add("book");        // 'b'で始まる単語のListにアイテムを追加
    dictionary[2].Remove("connect");  // 'c'で始まる単語のListからアイテムを削除

    // 'd'で始まる単語のListを作成
    List<string> wordsStartWithD = new List<string>() {"drink", "dear", "dig"};

    // 作成したListを入れ子になっているListの2番目に追加
    dictionary.Insert(1, wordsStartWithD);

    // すべての単語のListをソートする
    foreach (List<string> words in dictionary) {
      words.Sort();
    }

    // 入れ子になっているListの内容を表示
    foreach (List<string> words in dictionary) {
      // Listの中にあるListの内容を表示
      foreach (string word in words) {
        Console.Write("{0} ", word);
      }

      Console.WriteLine();
    }
  }
}
実行結果
action add after apple 
dear dig drink 
best big book bridge 
cat cheese 

上記の例では2段階に入れ子になったListを作成しましたが、必要ならばList<List<List<int>>>といった3段階以上に入れ子になったListを作成することも可能です。

このように、Listを入れ子にすることで可変長のジャグ配列と同様の構造を作成することができますが、一方Listでは可変長の多次元配列(矩形配列)の構造を単純に作成することはできません。 またそういったコレクションクラスも用意されていないため、独自に実装する必要があります。

§13 ForEachメソッドによる列挙操作

Listクラスでは、foreach文による列挙の他に、ForEachメソッドによる列挙が出来るようになっています。 このメソッドは、引数に列挙時に個々の要素に対して行う操作をデリゲートとして指定します。

ForEachメソッドによる列挙は、foreach文による列挙と結果は同じであるものの、breakに相当する操作ができない、列挙する要素の数だけデリゲート呼び出しが行われるといった不都合があるため、ほとんどの場合では強いてForEachメソッドを使って列挙しなければならない必要性はありません。

foreach文・ForEachメソッドを使ってList内の要素を列挙する(匿名メソッド版、C# 2.0以降)
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    List<string> list = new List<string>() {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // foreach文による列挙
    Console.WriteLine("[foreach]");

    foreach (string e in list) {
      Console.WriteLine(e);
    }

    // ForEachメソッドによる列挙
    Console.WriteLine("[ForEach]");

    list.ForEach(delegate(string e) {
      Console.WriteLine(e);
    });
  }
}
実行結果
[foreach]
Alice
Bob
Charlie
Dave
Eve
[ForEach]
Alice
Bob
Charlie
Dave
Eve

foreach文で列挙している途中にRemoveメソッドやAddメソッドなどを呼び出してListクラスの内容を変更することは無効な操作であるため、例外InvalidOperationExceptionがスローされます。

ForEachメソッドを使った列挙の場合でも同様に無効な操作となりますが、.NET Framework 4.0以前ではInvalidOperationExceptionがスローされず、.NET Framework 4.5以降ではスローされるようになっています。 この点については列挙操作中のコレクションの変更 §.List.ForEachメソッドとコレクションの変更で詳しく解説しています。