Menu

C#のList:Add、Remove、Contains、Find、Sortの使い方

List<T>はC#の伸縮する配列です。リストの作成、要素の追加と挿入、値、インデックス、条件による削除、ContainsとFindによる検索、プロパティによる並べ替え、そしてforeachの中でリストを変更したときのエラーの避け方を学びます。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

List<T> は、要素の追加や削除に合わせて伸び縮みする、順序付きのコレクションです。System.Collections.Generic にあり、配列と同じようにインデックスでアクセスでき(list[0])、C#で「ものの集まり」を扱うときの標準的な選択肢です。

リストの作成と要素の追加

山かっこの中の型引数が、リストが何を保持するかを表します:List<string>、List<int>、List<Order>。

出力:

2
Zoe, Ana, Ben, Chloe, Dev
Ana
Ana B.
3

Count はメソッドではなくプロパティです(配列は Length を使い、LINQには Count() メソッドがありますが、リストではプロパティを使います)。Insert は後ろの要素をすべて1つずつずらすので、大きなリストの先頭に挿入すると、サイズに比例した時間がかかります。末尾への Add は低コストな操作です。

要素の削除

要素について何がわかっているかに応じて、削除の方法は4つあります。

出力:

True: 85, 62, 40, 91, 55
False
62, 40, 91, 55
2 removed: 62, 91
Caught ArgumentOutOfRangeException
0

Remove は値を受け取り、最初に一致したものだけを削除します。ある値のすべてのコピーを削除するには RemoveAll(s => s == 40) を使います。リストで不正なインデックスを使うと、list[i] でも RemoveAt(i) でも ArgumentOutOfRangeException が投げられます(配列では代わりに IndexOutOfRangeException)。RemoveRange(index, count) はまとまった範囲を削除します。

CountとCapacity

リストは要素を内部の配列に保持しています。Capacity はその配列のサイズで、Count は使われている枠の数です。Count が Capacity に達すると、次の Add が2倍の大きさの配列を確保し、すべてをコピーします。

出力:

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 0, Capacity 1000

2倍にしていくので、ほとんどの Add の呼び出しは空いている枠に書き込むだけです。おおよその要素数がわかっているなら、コンストラクターにその数を渡すと途中のコピーを省けます。new List<int>(1000) でも Count は0なので、list[0] は例外になることに注意してください。容量は場所であって、要素ではありません。

検索:Contains、IndexOf、Find、Exists

出力:

True
2
-1
32.00
2 cheap items
False
2
True
False

Find は最初に一致したものを返し、何も一致しなければその型の既定値(クラスなら null)を返すので、結果を使う前に確認します。最後の行が False と表示されるのは、Contains が Equals を使い、Equals をオーバーライドしていないクラスは参照を比較するからです。同じフィールドを持つ新しい Product は別のオブジェクトです。代わりに Exists や Find でプロパティによって検索するか、クラスで Equals と GetHashCode をオーバーライドします。

これらのメソッドはどれもリストを先頭からたどるので、各呼び出しはO(n)です。キーによる検索を繰り返すなら、Dictionary なら定数時間で答えが出ます。

リストの並べ替え

Sort() は要素の自然な順序でその場で並べ替えます。他の基準で並べ替えるには、比較のラムダを渡します。

出力:

1, 2, 5, 9
9, 5, 2, 1
Ben 95, Ana 120, Chloe 150
Chloe 150, Ben 95, Ana 120
Ben 95, Ana 120, Chloe 150

比較は、a を先にすべきなら負の数、b を先にすべきなら正の数、同順ならゼロを返します。CompareTo はまさにそれを返します。実際には Sort とLINQの OrderBy の2つの違いが重要です。

  • Sort はリストを変更します。OrderBy は新しいシーケンスを返し、リストには触れません。
  • Sort は安定ではありません。等しいと比較される要素が入れ替わることがあります。OrderBy は安定で、ThenBy で2つ目のキーを追加できます:players.OrderBy(p => p.Score).ThenBy(p => p.Name)。

自分で作ったクラスのリストに比較なしで Sort() を呼ぶと、リストは要素の並べ方を知らないので InvalidOperationException が投げられます。比較を渡すか、クラスに IComparable<T> を実装します。

Reverse() はその場で反転します。System.Linq にも Reverse 拡張メソッドがありますが、List<T> の list.Reverse() は何も返さないその場で反転する版が選ばれます。

ループと反復中の削除

foreach はすべての要素を順に読みます。for ループならインデックスも得られます。できないのは、同じリストの foreach の途中で要素を追加したり削除したりすることです。

出力:

Caught InvalidOperationException
120, 80, 60
120, 80, 60

例外は InvalidOperationException で、メッセージは「Collection was modified; enumeration operation may not execute.」です。前から進む for ループは例外を投げませんが、削除のたびにすべてが1つ前のインデックスにずれるので、削除した要素の直後の要素を何も言わずに飛ばしてしまいます。逆順にループすればこれを避けられます。3つ目の直し方は、コピーを反復することです:foreach (var t in orders.ToList())。

foreach の中で要素のプロパティを変更すること(たとえば order.Status = "sent")は問題ありません。要素の追加や削除、そしてインデクサーによる置き換え(orders[i] = 0)は列挙子を壊します。

リストと配列の変換

出力:

4
3
4, 4, 4, 4
Oslo / Lima / Pune / Kyiv

どちらの方向も要素を新しい格納場所にコピーするので、後でリストを変更しても配列には影響しません。ConvertAll は、LINQの Select(...).ToList() のリスト版です。Console.WriteLine(list) でリストを表示すると型名(System.Collections.Generic.List`1[System.String])が表示されるので、string.Join を使います。

早見表

やりたいことコード
作成var l = new List<int>(); または new List<int> { 1, 2 }
末尾に追加l.Add(x)、l.AddRange(items)
位置を指定して挿入l.Insert(i, x)
最初に一致したものを削除l.Remove(x)(bool を返す)
インデックスで削除l.RemoveAt(i)
条件で削除l.RemoveAll(x => ...)(削除数を返す)
要素数l.Count
含まれているかl.Contains(x)、l.Exists(x => ...)
検索l.Find(...)、l.FindAll(...)、l.FindIndex(...)
その場で並べ替えl.Sort()、l.Sort((a, b) => ...)
並べ替えたコピーl.OrderBy(x => ...).ToList()
配列に変換l.ToArray()

よくある間違い

  • foreach の中で削除する。 InvalidOperationException が投げられます。RemoveAll か逆順の for ループを使います。
  • Remove(x) がすべてのコピーを削除すると思う。 削除するのは最初の1つです。RemoveAll を使います。
  • Equals のないオブジェクトに Contains を使う。 参照が比較されます。Exists でプロパティによって検索します。
  • 容量を指定して作ったリストにインデックスでアクセスする。 new List<int>(10) は空です。先に要素を追加します。
  • Sort が同順の要素の順序を保つと思う。 保ちません。等しい要素の順序が重要なら OrderBy を使います。
  • リストを共有するつもりで代入し、変更に驚く。 var copy = list; は同じリストです。new List<T>(list) がコピーです。

よくある質問

C#のListに要素を追加するには?

list.Add(item) は末尾に1つ追加し、list.AddRange(otherCollection) は複数を追加し、list.Insert(index, item) は指定した位置に要素を置いて残りを後ろにずらします。作成時に埋めることもできます:var names = new List<string> { "Ana", "Ben" };。

C#のListから要素を削除するには?

Remove(value) は最初に一致した要素を削除し、見つかれば true を返します。RemoveAt(index) は位置で削除します。RemoveAll(x => condition) は一致するすべての要素を削除し、削除した数を返します。Clear() はリストを空にします。

C#のListに値が含まれているか確認するには?

list.Contains(value) は true か false を返します。Equals で比較するので、自分で作ったクラスでは、Equals をオーバーライドしていない限り参照が比較されます。プロパティで確認するには、list.Exists(p => p.Name == "Ana") かLINQの list.Any(...) を使います。

C#のListをプロパティで並べ替えるには?

Sort に比較を渡します。people.Sort((a, b) => a.Age.CompareTo(b.Age)) はその場で並べ替えます。降順なら a と b を入れ替えます。あるいは、新しい並べ替え済みのシーケンスを返し、等しい要素の元の順序を保つLINQを使います:people.OrderBy(p => p.Age).ToList()。

「Collection was modified; enumeration operation may not execute」になるのはなぜですか?

foreach が同じリストを反復している最中に、要素を追加、削除、または置き換え(list[i] = x)たので、列挙子が次のステップで InvalidOperationException を投げたのです。list.RemoveAll(condition) を使うか、for ループで逆順にループするか、list.ToList() のようなコピーを反復します。

CountとCapacityの違いは何ですか?

Count はリストの要素数です。Capacity は内部の配列のサイズで、少なくとも Count 以上です。配列がいっぱいになると、リストは2倍の大きさの新しい配列を確保して要素をコピーするので、Add は平均すると高速です。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める