List<T>クラス

サイズが固定ならば、配列で代替できる可能性があります。

実装しているインターフェイス

[SerializableAttribute]
public class List<T> :
    IList<T>,
    IList,
    ICollection<T>,
    ICollection,
    IEnumerable<T>,
    IEnumerable,
    IReadOnlyList<T>,
    IReadOnlyCollection<T>
構文 - List(T) クラス (System.Collections.Generic) | MSDN

コンストラクタ

コンストラクタ  
List<T>()  
List<T>(IEnumerable<T>)  
List<T>(Int32)  

List<T>()

インスタンスが既定の初期量で作成されますが、その初期量とは0です。List - list.cs

List<int> list = new List<int>();
int cap = list.Capacity; // 0

List<T>(IEnumerable<T>)

public List(IEnumerable<T> collection)
List(T) コンストラクター (IEnumerable(T)) (System.Collections.Generic) | MSDN

定義時に初期値を与えるには、IEnumerableを実装するArrayなどを引数に渡します。

List<int> list = new List<int>(new[] { 1, 2, 3 });

このコンストラクタではcollectionICollection<T>を実装するオブジェクトならばICollection<T>.CopyTo()で、さもなくばすべての要素がList<T>.Add(T)で複製されます。List - list.cs

またはコレクション初期化子を用いれば次のようにも記述でき、要素が1つずつList<T>.Add(T)により追加されます。

List<int> list = new List<int> { 1, 2, 3 };

List<T>(Int32)

public List(int capacity)

あらかじめコレクションのサイズを見積もれるならばそれをcapacityで指定することで、要素を追加したときのサイズ変更の処理を削減できます。

List<int> list = new List<int>(3);
int cap = list.Capacity; // 3

list.Add(0); int cap1 = list.Capacity; // 3
list.Add(0); int cap2 = list.Capacity; // 3
list.Add(0); int cap3 = list.Capacity; // 3
list.Add(0); int cap4 = list.Capacity; // 6
list.Add(0); int cap5 = list.Capacity; // 6

このコンストラクタの計算量は、O(1)です。

プロパティ

プロパティ 内容
int Capacity 内部のデータ構造がサイズ変更なく保持できる要素の総数
int Count 含まれる要素の数
T Item[Int32] 指定のインデックスにある要素
プロパティ - List<T> Class (System.Collections.Generic) | Microsoft Learn

Count

要素が含まれているか確認するだけならばCountプロパティの値を得る必要はなく、Any()や、その実装と同様に列挙子を進めてみることでも判定できます。ただしList<T>.Countは_sizeフィールドの値を返すだけのため、このようにしてもパフォーマンスの向上は見込めません。Count - list.cs

List<int> list1 = new List<int> { 1, 2, 3 };
bool a1 = list1.Any(); // true
using (IEnumerator<int> e = list1.GetEnumerator())
{
    bool a2 = e.MoveNext(); // true
}

List<int> list2 = new List<int>();
bool b1 = list2.Any(); // false
using (IEnumerator<int> e = list2.GetEnumerator())
{
    bool b2 = e.MoveNext(); // false
}

Item[Int32]

このプロパティへの取得、設定の計算量はO(1)です。

メソッド

主要なメソッド
  メソッド 機能
追加 Add(T) 要素を、末尾に追加する
AddRange(IEnumerable<T>) コレクションの要素を、末尾に追加する
挿入 Insert(Int32, T) 要素を、指定位置に追加する
InsertRange(Int32, IEnumerable<T>) コレクションの要素を、指定位置に追加する
削除 Remove(T) 指定のオブジェクトを検索し、最初に見つかった要素を削除する
RemoveAt(Int32) 指定位置にある要素を削除する
RemoveRange(Int32, Int32) 指定範囲の要素を削除する
RemoveAll(Predicate<T>) 条件に一致する要素を、すべて削除する
Clear() すべての要素を削除する
検索 Contains(T) 要素が存在するか確認する
Exists(Predicate<T>) 条件に一致する要素が存在するか確認する
Find(Predicate<T>) 条件に一致する要素を先頭から検索し、最初に見つかった要素を返す
FindLast(Predicate<T>) 条件に一致する要素を末尾から検索し、最初に見つかった要素を返す
FindAll(Predicate<T>) 条件に一致する要素を検索し、すべての要素を返す
IndexOf(T) 指定のオブジェクトを検索し、最初に見つかった要素のインデックスを返す
FindIndex(Int32, Int32, Predicate<T>) 条件に一致する要素を指定の範囲の先頭から検索し、最初に見つかった要素のインデックスを返す
FindLastIndex(Int32, Int32, Predicate<T>) 条件に一致する要素を指定の範囲の末尾から検索し、最初に見つかった要素のインデックスを返す
並べ替え Sort() 既定の比較演算子で、すべての要素を並べ替える
Sort(Comparison<T>) 指定のメソッドで、すべての要素を並べ替える
Reverse() すべての要素の順番を反転させる
変換 ToArray() すべての要素を配列にコピーする
CopyTo(T[]) すべての要素を配列にコピーする
CopyTo(T[], Int32) すべての要素を、配列の指定位置にコピーする
CopyTo(Int32, T[], Int32, Int32) 指定範囲にある要素を、配列の指定位置にコピーする
AsReadOnly() 読み取り専用のReadOnlyCollection<T>を返す

返されたコレクションは変更されないが、基のコレクションが変更されると、返されたコレクションに影響する

実行 ForEach(Action<T>) すべての要素に、指定の処理を実行する
メソッド - List<T> Class (System.Collections.Generic) | Microsoft Learn

Add(T)

要素を末尾に追加できます。要素を末尾にしか追加せず、要素へインデックスを用いてアクセスしないならば、Queue<T>で代替できる可能性があります。

public void Add(
    T item
)
List<T>.Add(T) Method (System.Collections.Generic) | Microsoft Learn
List<string> list = new List<string>();
list.Add("A");
list.Add("B");

Console.Write(list[1]); // "B"

// すべての要素を表示
foreach(string value in list)
{
    Console.Write(value);
}

// これは、ForEach()で次のようにも記述できる
list.ForEach(delegate (int value)
{
    Console.Write(value);
});

HashSet<T>と異なり、重複した値も追加できます。

List<MyClass> list = new List<MyClass>();

MyClass myClass = new MyClass();
list.Add(myClass);
list.Add(myClass); // 重複した要素も許容する
list.Add(null);    // nullも許容する

このメソッドの計算量はO(1)ですが、容量を増加する必要があるときにはCountプロパティをnとして、O(n)です。Add - list.cs

AddRange(IEnumerable<T>)

コレクションの要素を、末尾に追加できます。

public void AddRange (
    System.Collections.Generic.IEnumerable<T> collection
    );
List<T>.AddRange(IEnumerable<T>) Method (System.Collections.Generic) | Microsoft Learn
int[] array = new[] { 1, 2, 3 };
List<int> list1 = new List<int>(new[] { 4, 5, 6 });

List<int> list2 = new List<int>();
list2.AddRange(array);
list2.AddRange(list1);

これは次のように実装されており、collectionInsertRange()で末尾に追加されます。

public void AddRange(IEnumerable<T> collection) {
    Contract.Ensures(Count >= Contract.OldValue(Count));

    InsertRange(_size, collection);
}
AddRange() - list.cs

Queue<T>のようにコレクションを追加するメソッドが用意されていないクラスでも、ForEach(Action<T>)を用いることで簡潔に記述できます。

Insert(Int32, T)

要素を指定位置に追加できます。要素を先頭にしか追加せず、要素へインデックスを用いてアクセスしないならば、Stack<T>で代替できる可能性があります。

public void Insert (
    int index,
    T item
    );
List<T>.Insert(Int32, T) メソッド - List<T>.Insert(Int32, T) メソッド (System.Collections.Generic) | Microsoft Learn

内部ではindexから末尾までの要素がArray.Copy()でコピーされ、indexの位置にitemが設定されます。Insert - list.cs

計算量はCountをnとしてO(n)のため、挿入をくり返すならばLinkedList<T>で代替することを検討します。

要素の移動

要素を移動するメソッドは提供されていないため、Insert()とRemove()を組み合わせて実現します。

List<string> list = new List<string>(new[] { "a", "b", "c", "d" });

int oldIndex = 2;
int newIndex = 1;

string item = list[oldIndex];
list.RemoveAt(oldIndex);
list.Insert(newIndex, item); // "a", "c", "b", "d"

InsertRange()

public void InsertRange (
    int index,                                           // 挿入する位置
    System.Collections.Generic.IEnumerable<T> collection // 挿入するコレクション
    );
List<T>.InsertRange(Int32, IEnumerable<T>) メソッド (System.Collections.Generic) | Microsoft Learn
List<int> list = new List<int>(new[] { 1, 2, 3 });
list.InsertRange(1, new[] { 5, 6 }); // 1, 5, 6, 2, 3

collectionICollection<T>を実装しているならば、それのCopyTo()やT[]のCopyTo()で、indexの位置へコピーされます。InsertRange - list.cs

Remove(T)

public bool Remove(
    T item
)
List(T).Remove メソッド (T) (System.Collections.Generic) | MSDN

削除に成功したときはtrue、失敗するかitemが見つからなかったときはfalseが返されます。

List<int> list = new List<int>(new[] { 1, 2, 3, 4, 5 });
if (list.Remove(3))
{
    // 1, 2, 4, 5
}

内部ではIndexOf()でitemのインデックスを取得し、RemoveAt()を呼んでいます。Remove - list.cs

このメソッドは線形検索 (linear search) で実行され、計算量はCountプロパティをnとして、O(n)です。Remarks - List<T>.Remove(T) Method (System.Collections.Generic) | Microsoft Learn

RemoveAt(Int32)

指定のインデックスにある要素を削除できます。

public void RemoveAt(
    int index
)
List(T).RemoveAt メソッド (Int32) (System.Collections.Generic) | MSDN

indexにCount以上の値を指定すると、ArgumentOutOfRangeExceptionが投げられます。

内部ではindexより後の要素をindexの位置までコピーして、末尾の要素にdefault(T)を代入しています。よってその要素を削除しているわけではなく、Tが参照型ならばその参照を破棄しているだけです。RemoveAt - list.cs

このメソッドの計算量は、(Count - index)をnとして、O(n)です。Remarks - List<T>.RemoveAt(Int32) Method (System.Collections.Generic) | Microsoft Learn

RemoveRange(Int32, Int32)

要素の範囲を削除できます。

public void RemoveRange (
    int index, // 削除を開始するインデックス
    int count  // 削除する要素の数
    );
List<T>.RemoveRange(Int32, Int32) メソッド (System.Collections.Generic) | Microsoft Learn
List<int> list1 = new List<int>(new int[] { 1, 2, 3, 4, 5 });
list1.RemoveRange(1, 2); // 1, 4, 5

List<int> list2 = new List<int>(new int[] { 1, 2, 3, 4, 5 });
list2.RemoveRange(1, 5); // ArgumentException「配列のオフセットおよび長さが範囲を超えているか、カウンターがソース コレクションのインデックスから最後までの要素の数より大きい値です。」

Contains(T)

等しいことは既定の等値比較子であるEqualityComparer<T>.Defaultで評価されます。 Remarks - List<T>.Contains(T) Method (System.Collections.Generic) | Microsoft Learn Contains - list.cs

このメソッドは線形検索で実行され、計算量はCountプロパティをnとして、O(n)です。Remarks - List<T>.Contains(T) Method (System.Collections.Generic) | Microsoft Learn

List<T>は要素を線形検索 (linear search) することから低速なため、要素数が巨大になるならばHashSet<T>への置き換えを検討します。

List<T> HashSet<T>
Stopwatch sw = new Stopwatch();

// 追加
sw.Restart();
List<string> list = new List<string>();
for (int i = 0; i < 1000 * 1000; i++)
{
    list.Add(i.ToString());
}
Console.WriteLine(sw.ElapsedMilliseconds); // 162

// 検索
sw.Restart();
for (int i = 0; i < 1000 * 100; i++)
{
    list.Contains(i.ToString());
}
Console.WriteLine(sw.ElapsedMilliseconds); // 31129
Stopwatch sw = new Stopwatch();

// 追加
sw.Restart();
HashSet<string> hashSet = new HashSet<string>();
for (int i = 0; i < 1000 * 1000; i++)
{
    hashSet.Add(i.ToString());
}
Console.WriteLine(sw.ElapsedMilliseconds); // 252

// 検索
sw.Restart();
for (int i = 0; i < 1000 * 100; i++)
{
    hashSet.Contains(i.ToString());
}
Console.WriteLine(sw.ElapsedMilliseconds); // 14

IndexOf(T)

指定オブジェクトを検索し、最初に見つかった要素のインデックスを取得できます。見つからない場合には-1が返されます。

public int IndexOf(
    T item
)
List(T).IndexOf メソッド (T) (System.Collections.Generic) | MSDN

引数と等しいかどうかは、既定の等値演算子であるEqualityComparer<T>.Defaultで評価されます。

List<int> list = new List<int>(new[] { 1, 2, 3, 4, 5 });
int r1 = list.IndexOf(3);  // 2
int r2 = list.IndexOf(10); // -1

検索の処理にはArray.IndexOf()が利用されます。IndexOf - list.cs

Find(Predicate<T>)

指定の述語 (predicate) で定義された条件に一致する、最初の要素を得られます。

public T Find(
    Predicate<T> match
)
List(T).Find メソッド (Predicate(T)) (System.Collections.Generic) | MSDN

用法はArray.Find()と同様です。

List<int> list = new List<int>(new[] { 1, 2, 3, 4, 5 });
int r1 = list.Find(x => 3 < x);  // 4
int r2 = list.Find(x => 10 < x); // 0
List<List<int>> list = new List<List<int>>();
list.Add(new List<int>(new[] { 1, 2, 3 }));
list.Add(new List<int>(new[] { 4, 5 }));
list.Add(new List<int>(new[] { 6, 7, 8 }));

List<int> r1 = list.Find(x => x.Contains(7));  // {6,7,8}を含む要素
List<int> r2 = list.Find(x => x.Contains(10)); // null

Sort()

public void Sort()
List(T).Sort メソッド (System.Collections.Generic) | MSDN
List<int> list1 = new List<int>(new[] { 4, 3, 5, 1, 2 });
list1.Sort();  // 1, 2, 3, 4, 5
List<string> list2 = new List<string>(new[] { "c", "a", "A", "B", "b" });
list2.Sort(); // "a", "A", "b", "B", "c"

独自の規則で並べ替えたいならば、次の形式を用います。

Sort(Comparison<T>)

public void Sort(
    Comparison<T> comparison
)
List(T).Sort メソッド (Comparison(T)) (System.Collections.Generic) | MSDN

引数のComparison<T>デリゲートは、次のように定義されています。

public delegate int Comparison<in T>(
    T x,
    T y
)
Comparison(T) デリゲート (System) | MSDN

このデリゲートでは、xyの比較結果に基づき次の値を返します。

  • xyより小さい … 0より小さい値
  • xyと等しい … 0
  • xyより大きい … 0より大きい値
List<string> list2 = new List<string>(new[] { "c", "a", "A", "B", "b" });
list2.Sort((x, y) => { return String.CompareOrdinal(x, y); }); // "A", "B", "a", "b", "c"

Sort(IComparer<T>)

このメソッドで並べ替える前提で、要素が重複しないならばSortedSet<T>の使用を検討します。

ToArray()

public T[] ToArray ();
List<T>.ToArray Method (System.Collections.Generic) | Microsoft Learn

List<T>からT[]を得られますが、内部ではArray.Copy()ですべての要素をコピーしているだけです。よって計算量はO(n)です。

// ToArray returns a new Object array containing the contents of the List.
// This requires copying the List, which is an O(n) operation.
public T[] ToArray() {
    Contract.Ensures(Contract.Result<T[]>() != null);
    Contract.Ensures(Contract.Result<T[]>().Length == Count);

    T[] array = new T[_size];
    Array.Copy(_items, 0, array, 0, _size);
    return array;
}
ToArray() - list.cs

ToArray()による変換では、その要素はシャローコピー (Shallow copy) されます。

List<int[]> list = new List<int[]>();
list.Add(new[] { 1, 2 });

int[][] array1 = list.ToArray();

array1[0][0] = 10;         // コピー先の配列を変更
Console.Write(list[0][0]); // 10 (コピー元のリストへ影響する)

これはCopyTo()でも同様です。

int[][] array2 = new int[list.Count][];
list.CopyTo(array2);

array2[0][0] = 20;         // コピー先の配列を変更
Console.Write(list[0][0]); // 20 (コピー元のリストへ影響する)

ForEach(Action<T>)

foreachステートメントと同等の処理を、メソッドとして実行できます。

public void ForEach(
    Action<T> action // リストの各要素に対して実行するデリゲート
)
List(T).ForEach メソッド (Action(T)) (System.Collections.Generic) | MSDN

Arrayでは、ForEach()で同様の処理をできます。

List<int> list = new List<int>(new[] { 1, 2, 3 });

list.ForEach(delegate (int value)
{
    Console.Write(value);
});

// ラムダ式によるに記述
list.ForEach(value => Console.Write(value));

// Action<T>型のメソッドに各要素を渡すだけならば、そのメソッドを指定するだけで良い
list.ForEach(Console.Write);

内部では引数のactionに、要素を順に渡しているだけです。ForEach - list.cs

GetEnumerator()

反復子を取得できます。

public List<T>.Enumerator GetEnumerator()
List.GetEnumerator メソッド (System.Collections.Generic) | MSDN

戻り値の型はList<T>.Enumerator構造体です。

List<string> list = new List<string>();
List<string>.Enumerator iterator = list.GetEnumerator();

while( iterator.MoveNext() )
{
    T value = iterator.Current;
    Console.Write( value );
}

これをforeachを用いて書き直すと、

List<string> list = new List<string>();
foreach( string value in list )
{
    Console.Write( value );
}

のようになります。

多次元リスト

2次元リスト

T型をList<T>として、List<List<T>>の形式で記述します。

List<List<int>> list = new List<List<int>>();

list.Add(new List<int>(new[] { 1, 2, 3 }));
list.Add(new List<int>(new[] { 4, 5 }));

Console.Write(list[0][0]); // 1
Console.Write(list[0][1]); // 2
Console.Write(list[1][1]); // 5

3次元リスト

List<List<List<int>>> list = new List<List<List<int>>>();
list.Add(new List<List<int>>());
list[0].Add(new List<int>(new[] { 1, 2, 3 }));
list[0].Add(new List<int>(new[] { 4, 5 }));

list.Add(new List<List<int>>());
list[1].Add(new List<int>(new[] { 6, 7 }));
list[1].Add(new List<int>(new[] { 8, 9 }));

List<T>とT[]の相互変換

List<T>からT[] (配列) への変換は、ToArray()メソッドで行えます。

List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);

int[] array = list.ToArray();

逆にList<T>への変換は、List<T>のコンストラクタにT[]を渡すことで行えます。

int[] array = new[] { 1, 2, 3 };
List<int> list = new List<int>(array);

一方でIList<T>を介して処理するならば、共にこのインターフェイスを実装するため変換することなく渡せます。

int[] array = new[] { 1, 2, 3 };
List<int> list = new List<int> { 1, 2, 3 };

IList<int> iList1 = array;
IList<int> iList2 = list;

2次元リスト

List<List<T>>からT[][]への変換は、Select()を用いて各要素をToArray()で変換します。c# - how to convert List<List<int>> to multidimensional array - Stack Overflow

このとき「CS1061 'List<List<int>>' に 'Select' の定義が含まれておらず、型 'List<List<int>>' の最初の引数を受け付ける拡張メソッド 'Select' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください。」としてエラーとなるときには、System.Coreが参照されていることを確認しusing System.Linq;を追記します。

public void Method()
{
    List<List<int>> list = new List<List<int>>();

    list.Add(new List<int>(new[] { 1, 2, 3 }));
    list.Add(new List<int>(new[] { 4, 5 }));

    int[][] array1 = list.Select(Selector).ToArray();
    // int[][] array1 = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(list, Selector)); // usingを指定せず、このようにも記述できる
}

public int[] Selector(List<int> a)
{
    return a.ToArray();
}

上記の処理は、以下のように書き換えられます。

// 2. 匿名メソッド
Func<List<int>, int[]> selector2 = delegate (List<int> a)
{
    return a.ToArray();
};
int[][] array2 = list.Select(selector2).ToArray();

// 3. ラムダ式
Func<List<int>, int[]> selector3 = a => a.ToArray();
int[][] array3 = list.Select(selector3).ToArray();

// 4. ラムダ式 (一時変数をインライン化)
int[][] array4 = list.Select(a => a.ToArray()).ToArray();

さらに、Enumerable.ToArray()で次のようにも記述できます。

// 5.
int[][] array5 = list.Select(Enumerable.ToArray).ToArray();

3次元リスト

T[][][]への変換は2次元と同様に、Select()とToArray()で行えます。

List<List<List<int>>> list = new List<List<List<int>>>();
// 要素の追加...

// ラムダ式
Func<List<int>, int[]>         selector1 = b => b.ToArray();
Func<List<List<int>>, int[][]> selector2 = a => a.Select(selector1).ToArray();
int[][][] array1 = list.Select(selector2).ToArray();

// ラムダ式 (一時変数をインライン化)
int[][][] array2 = list.Select(a => a.Select(b => b.ToArray()).ToArray()).ToArray();
Microsoft Learnから検索