コレクション (collection)

コレクションとは、配列を補足するデータ構造です。

System.Collections名前空間

コレクションに関する型は、System.Collections名前空間で宣言されています。

名前空間  
System.Collections.ObjectModel 再利用可能なライブラリのオブジェクト モデルでコレクションとして使用できるクラスが含まれる。

これらのクラスは、プロパティまたはメソッドがコレクションを返す場合に使用する。

System.Collections.Generic ジェネリック コレクションを定義するインターフェイスとクラスが含まれる。

このコレクションを使用することにより、ユーザーは、汎用的ではない厳密に型指定されたコレクションに比べてタイプ セーフでパフォーマンスが高い、厳密に型指定されたコレクションを作成できるようになる。

System.Collections.Specialized 特殊な厳密に型指定されたコレクションが含まれる。

たとえば、リンクされたリスト ディクショナリ、ビット ベクター、文字列だけを含むコレクションなどがある。

System.Collections.Concurrent スレッド セーフなコレクション クラスが含まれる。

複数のスレッドがコレクションに同時にアクセスするときに、これらのコレクション クラスを名前空間および名前空間の対応する型の代わりに使用する必要がある。ただし、拡張メソッドや明示的なインターフェイスの実装を介したコレクション オブジェクトの要素へのアクセスは、スレッドセーフであるという保証はなく、呼び出し元による同期が必要になる場合がある。

.NET API ブラウザー | Microsoft Learn

コレクション インターフェイス (Collection interface)

以下のインターフェイスで、コレクションの列挙、データの格納や作成のための標準的な方法が提供されます。

インターフェイス 実装 機能
IEnumerator   単純な反復処理の支援
IDictionaryEnumerator IEnumerator  
IEnumerable   単純な反復処理を支援する列挙子を提供
ICollection IEnumerable サイズと列挙子、それに同期オブジェクトの提供
IList ICollection  
IDictionary ICollection  
     
インターフェイス - System.Collections 名前空間 | Microsoft Learn

IEnumerator

public interface IEnumerator
{
    object Current { get; }

    bool MoveNext();
    void Reset();
}
IEnumerator インターフェイス (System.Collections) | Microsoft Learn
int[] array = { 0, 1 };
IEnumerator e = array.GetEnumerator();

//object obj = e.Current; // InvalidOperationException「列挙は開始していません。MoveNext を呼び出してください。」

bool r0 = e.MoveNext();  // true
object obj0 = e.Current; // 0

bool r1 = e.MoveNext();  // true
object obj1 = e.Current; // 1

bool r2 = e.MoveNext();  // false
object obj2 = e.Current; // InvalidOperationException「列挙は既に完了しています。」 
MoveNext()

列挙子を、コレクションの次の要素へ進められます。

public bool MoveNext ();
IEnumerator.MoveNext Method (System.Collections) | Microsoft Learn

次の要素へ進められたときは、trueが返されます。

このIEnumeratorのインスタンスが取得された後にコレクションが変更されていると、InvalidOperationExceptionが投げられます。

List<int> list = new List<int>();
IEnumerator e = list.GetEnumerator();

bool r0 = e.MoveNext(); // false
list.Add(1);
bool r1 = e.MoveNext(); // InvalidOperationException「コレクションが変更されました。列挙操作は実行されない可能性があります。」
MoveNext - list.cs
Queue<int> queue = new Queue<int>();
IEnumerator e = queue.GetEnumerator();

bool r0 = e.MoveNext(); // false
queue.Enqueue(1);
bool r1 = e.MoveNext(); // InvalidOperationException「列挙子がインスタンス化されてから、コレクションが変更されました。」
MoveNext - queue.cs

IEnumerable

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}
IEnumerable インターフェイス (System.Collections) | Microsoft Learn
GetEnumerator()

コレクションを反復 (iterate) するための、列挙子 (enumerator) を得られます。

public System.Collections.IEnumerator GetEnumerator ();
IEnumerable.GetEnumerator メソッド (System.Collections) | Microsoft Learn

ICollection

public interface ICollection : IEnumerable
{
    int Count { get; }
    bool IsSynchronized { get; }
    object SyncRoot { get; }

    void CopyTo(Array array, int index);
}
ICollection インターフェイス (System.Collections) | Microsoft Learn

IList

public interface IList : ICollection, IEnumerable
{
    object this[int index] { get; set; }

    bool IsFixedSize { get; }
    bool IsReadOnly { get; }

    int Add(object value);
    void Clear();
    bool Contains(object value);
    int IndexOf(object value);
    void Insert(int index, object value);
    void Remove(object value);
    void RemoveAt(int index);
}
IList インターフェイス (System.Collections) | Microsoft Learn

コレクション クラス (Collection Class)

これらはジェネリックではない古いクラスであるため、型の安全性とパフォーマンスを考えるならば、新しいジェネリックなコレクション クラスを使用すべきです。

基底クラス

  • CollectionBase
  • DictionaryBase
  • ReadOnlyCollectionBase

比較

  • Comparer
  • CaseInsensitiveComparer
  • CaseInsensitiveHashCodeProvider
  • StructuralComparisons

反復子 (Iterators)

yieldキーワード

反復子を定義できます。コンテキスト キーワード yield - C# リファレンス | Microsoft Learn

yieldにより反復子として定義するメソッド、演算子、getアクセサの戻り値は、以下のいずれかでなければなりません。コンパイラ エラー CS1624 | Microsoft Learn

構文 作用
yield return expression;
expressionを返し、呼び出し元へ処理を戻す。再呼び出し後は、この位置から実行が再開される
yield break;
反復を終了し、呼び出し元へ処理を戻す

たとえばメソッドが次のように定義されているとき、

public IEnumerable<int> Method()
{
    yield return 1;
    Console.Write("a");

    yield return 2;
    Console.Write("b");

    yield break;
    Console.Write("c");
}

これを呼び出すと、

IEnumerable<int> a = Method();
foreach (int b in a)
{
    Console.Write(b);
}

1、a、2、bの順に出力されます。

foreachステートメント

foreach ( local_variable_type identifier in expression )
    embedded_statement
12.9.5 The foreach statement - Statements - C# language specification | Microsoft Learn
int[] p = { 1, 2, 3 };
foreach (int x in p)
{
    Console.Write(x);
}

foreachで処理しているコレクションは、そのブロック内で項目の追加や削除はできません。foreach (Visual Basic では For Each) ブロック内のステートメントによって、反復処理中のコレクションが変更される - 例外のトラブルシューティング : System.InvalidOperationException | Microsoft Learn

List<int> list = new List<int>(new int[] { 1, 2, 3 });
foreach (int item in list) // 2回目のループで、System.InvalidOperationException
{                          // 「コレクションが変更されました。列挙操作は実行されない可能性があります。(Collection was modified; enumeration operation may not execute.)」
    list.Remove(item);
}

またコレクションの要素に対して、割り当てることもできません。

foreach (int x in new[] { 1, 2, 3 })
{
    x = 10; // error CS1656: 'x' は 'foreach 繰り返し変数' であるため、これに割り当てることはできません。
}

実装

次のようなforeachの処理は、

int[] p = { 1, 2, 3 };
foreach (int v in p)
{
    // vを用いた文
}

次のように処理するのと同等です。

int[] p = { 1, 2, 3 };
IEnumerator e = p.GetEnumerator();
try
{
    while (e.MoveNext())
    {
        int v = (int)e.Current;
        // vを用いた文
    }
}
finally
{
    IDisposable d = e as IDisposable;
    if (d != null) d.Dispose();
}
12.9.5 The foreach statement - Statements - C# language specification | Microsoft Learn

コレクションがIDisposableを実装していることが自明ならば、usingを用いることで簡潔に記述できます。

using (IEnumerator<T> en = collection.GetEnumerator())
{
    while (en.MoveNext())
    {
        // en.Current;
    }
}
List - list.cs

スレッド セーフ (thread safe)

ConcurrentGenericで定義されているクラスが要件を満たすならば、それを用います。

ICollectionインターフェイスを実装するならば、それのSyncRootプロパティを同期用のオブジェクトとして用います。

ICollection iCollection = collection;
lock (iCollection.SyncRoot)
{
}
Remarks - ICollection.SyncRoot Property (System.Collections) | Microsoft Learn SyncRoot - iCollection.SyncRoot

ICollectionを実装するが、そのクラスのプロパティとしてSyncRootを実装していないクラスでは、ICollectionへキャストした上で取得します。c# - List<T> doesn't implements SyncRoot! - Stack Overflow

List<int> list = new List<int>();
object obj = ((ICollection)list).SyncRoot;

ただしConcurrentのクラスからは取得できません。

ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
object obj = ((ICollection)queue).SyncRoot; // NotSupportedException「SyncRoot プロパティは、同時コレクションの同期には使用できません。(The SyncRoot property may not be used for the synchronization of concurrent collections.)」

なおArrayListのような非ジェネリック クラスで定義されていた、スレッド セーフなコレクションを返すSynchronized()は、ジェネリック版では廃止されています。

参考

参考書

Microsoft Learnから検索