コレクション

.NET ランタイムでは、関連するオブジェクトのグループを格納し、管理する多くのコレクション型が提供されています。 System.ArraySystem.Span<T>System.Memory<T> などのコレクション型の一部は、C# 言語で認識されます。 さらに、System.Collections.Generic.IEnumerable<T> のようなインターフェイスは、コレクションの要素を列挙するための言語として認識されます。

コレクションは、オブジェクトのグループを柔軟に処理できます。 こうした特徴により、さまざまなコレクションを分類できます。

  • 要素アクセス: すべてのコレクションは、各要素に順番にアクセスするために列挙できます。 コレクションによっては、index(順序のあるコレクションにおける要素の位置) で要素にアクセスするものもあります。 System.Collections.Generic.List<T> の最も一般的な例は、次のとおりです。 他のコレクションは、が単一のキーに関連付けられるキーで要素にアクセスします。 最も一般的な例は System.Collections.Generic.Dictionary<TKey,TValue> です。 これらのコレクション型は、アプリが要素にアクセスする方法に基づいて選択します。
  • パフォーマンス プロファイル: すべてのコレクションには、要素の追加、要素の検索、要素の削除などのアクションに対するさまざまなパフォーマンス プロファイルがあります。 アプリで最もよく使用される操作に基づいて、コレクション型を選択できます。
  • 動的な増減: ほとんどのコレクションは、動的な要素の追加または削除をサポートしています。 特に、ArraySystem.Span<T>System.Memory<T> はそうではありません。

こうした特徴に加えて、ランタイムでは、要素の追加や削除、コレクションの要素の変更を防ぐための特殊なコレクションが提供されています。 その他の特殊なコレクションでは、マルチスレッド アプリでの同時アクセスの安全性が提供されています。

すべてのコレクション型は、.NET API リファレンスにあります。 詳細については、「一般的に使用されるコレクション型」、「コレクション クラスの選択」を参照してください。

Note

この記事の例では、System.Collections.Generic 名前空間および System.Linq 名前空間の using ディレクティブを追加する必要がある場合があります。

配列System.Array で表され、C# 言語の構文サポートがあります。 この構文では、配列変数をより簡潔に宣言できます。

System.Span<T> は、要素のシーケンスをコピーせずにスナップショットを提供する ref struct 型です。 コンパイラーは、Span で参照されるシーケンスがスコープから外れた後にアクセスできないように安全ルールを適用します。 パフォーマンスを向上させるために、多くの .NET API で使用されています。 Memory<T> は、ref struct 型を使用できない場合に同様の動作をします。

C# 12 以降では、コレクション式を使用して、すべてのコレクション型を初期化できます。

インデックス可能なコレクション

インデックス可能なコレクションとは、インデックスを使用して各要素にアクセスできるものです。 そのインデックスは、シーケンス内でその前の要素の数です。 したがって、インデックス 0 で参照される要素は最初の要素であり、インデックス 1 は 2 番目の要素であり、以下同様です。 これらの例では、List<T> クラスを使用します。 最も一般的なインデックス可能なコレクションです。

以下の例では、文字列のリストを作成して初期化し、要素を削除して、リストの最後に要素を追加します。 各変更の後、foreach ステートメントまたは for ループを使用して文字列を繰り返します。

// Create a list of strings by using a
// collection initializer.
List<string> salmons = ["chinook", "coho", "pink", "sockeye"];

// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook coho pink sockeye

// Remove an element from the list by specifying
// the object.
salmons.Remove("coho");


// Iterate using the index:
for (var index = 0; index < salmons.Count; index++)
{
    Console.Write(salmons[index] + " ");
}
// Output: chinook pink sockeye

// Add the removed element
salmons.Add("coho");
// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook pink sockeye coho

次の例では、リストからインデックスごとに要素を削除ます。 foreach ステートメントの代わりに、降順に反復する for ステートメントを使用します。 RemoveAt メソッドを実行すると、削除された要素の後にある各要素のインデックス値が小さくなります。

List<int> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

// Remove odd numbers.
for (var index = numbers.Count - 1; index >= 0; index--)
{
    if (numbers[index] % 2 == 1)
    {
        // Remove the element by specifying
        // the zero-based index in the list.
        numbers.RemoveAt(index);
    }
}

// Iterate through the list.
// A lambda expression is placed in the ForEach method
// of the List(T) object.
numbers.ForEach(
    number => Console.Write(number + " "));
// Output: 0 2 4 6 8

List<T> の要素の型は、独自のクラスでも定義できます。 次の例では、Galaxy が使用する List<T> クラスがコードに定義されます。

private static void IterateThroughList()
{
    var theGalaxies = new List<Galaxy>
    {
        new (){ Name="Tadpole", MegaLightYears=400},
        new (){ Name="Pinwheel", MegaLightYears=25},
        new (){ Name="Milky Way", MegaLightYears=0},
        new (){ Name="Andromeda", MegaLightYears=3}
    };

    foreach (Galaxy theGalaxy in theGalaxies)
    {
        Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears);
    }

    // Output:
    //  Tadpole  400
    //  Pinwheel  25
    //  Milky Way  0
    //  Andromeda  3
}

public class Galaxy
{
    public string Name { get; set; }
    public int MegaLightYears { get; set; }
}

キーと値のペアのコレクション

これらの例では、Dictionary<TKey,TValue> クラスを使用します。 最も一般的なディクショナリ コレクションです。 ディクショナリ コレクションでは、各要素のキーを使用してコレクションの要素にアクセスできます。 ディクショナリに追加される各エントリは、値とその値に関連付けられたキーで構成されます。

次の例では Dictionary のコレクションを作成し、foreach ステートメントを使用してディクショナリを反復処理します。

private static void IterateThruDictionary()
{
    Dictionary<string, Element> elements = BuildDictionary();

    foreach (KeyValuePair<string, Element> kvp in elements)
    {
        Element theElement = kvp.Value;

        Console.WriteLine("key: " + kvp.Key);
        Console.WriteLine("values: " + theElement.Symbol + " " +
            theElement.Name + " " + theElement.AtomicNumber);
    }
}

public class Element
{
    public required string Symbol { get; init; }
    public required string Name { get; init; }
    public required int AtomicNumber { get; init; }
}

private static Dictionary<string, Element> BuildDictionary() =>
    new ()
    {
        {"K",
            new (){ Symbol="K", Name="Potassium", AtomicNumber=19}},
        {"Ca",
            new (){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        {"Sc",
            new (){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        {"Ti",
            new (){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };

次の例では、キーによって項目をすばやく検索するために、ContainsKeyItem[] メソッドと Dictionary プロパティを使用します。 Item プロパティでは、C# の elements[symbol] を使用して elements コレクションの項目にアクセスできます。

if (elements.ContainsKey(symbol) == false)
{
    Console.WriteLine(symbol + " not found");
}
else
{
    Element theElement = elements[symbol];
    Console.WriteLine("found: " + theElement.Name);
}

次の例では、キーによって項目をすばやく検索するために、TryGetValue メソッドを代わりに使用します。

if (elements.TryGetValue(symbol, out Element? theElement) == false)
    Console.WriteLine(symbol + " not found");
else
    Console.WriteLine("found: " + theElement.Name);

Iterators

反復子は、コレクションに対するカスタム イテレーションを実行するために使用されます。 反復子は、メソッドまたは get アクセサーのいずれかです。 反復子は、yield return ステートメントを使用して、コレクションの各要素を 1 回に 1 つ返します。

foreach ステートメントを使用して、反復子を呼び出します。 foreach ループの各イテレーションは、反復子を呼び出します。 yield return ステートメントが反復子に到達すると、式が戻され、コードの現在の位置が保持されます。 次回、反復子が呼び出されると、この位置から実行が再開されます。

詳細については、「反復子 (C#)」を参照してください。

次の例は、反復子メソッドを使用します。 反復子メソッドには、for ループ内に yield return ステートメントがあります。 ListEvenNumbers メソッドでは、foreach ステートメント本文の各イテレーションが、反復子メソッドの呼び出しを作成し、これが次の yield return ステートメントに続行されます。

private static void ListEvenNumbers()
{
    foreach (int number in EvenSequence(5, 18))
    {
        Console.Write(number.ToString() + " ");
    }
    Console.WriteLine();
    // Output: 6 8 10 12 14 16 18
}

private static IEnumerable<int> EvenSequence(
    int firstNumber, int lastNumber)
{
    // Yield even numbers in the range.
    for (var number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

LINQ とコレクション

統合言語クエリ (LINQ) を使用してコレクションにアクセスできます。 LINQ クエリは、フィルター処理、並べ替え、およびグループ化の機能を提供します。 詳細については、C# での LINQ の概要に関するページを参照してください。

次の例では、ジェネリック List に対して LINQ クエリを実行します。 LINQ クエリは、結果が格納されている別のコレクションを戻します。

private static void ShowLINQ()
{
    List<Element> elements = BuildList();

    // LINQ Query.
    var subset = from theElement in elements
                 where theElement.AtomicNumber < 22
                 orderby theElement.Name
                 select theElement;

    foreach (Element theElement in subset)
    {
        Console.WriteLine(theElement.Name + " " + theElement.AtomicNumber);
    }

    // Output:
    //  Calcium 20
    //  Potassium 19
    //  Scandium 21
}

private static List<Element> BuildList() => new()
    {
        { new(){ Symbol="K", Name="Potassium", AtomicNumber=19}},
        { new(){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        { new(){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        { new(){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };