Tip
この記事は、少なくとも 1 つのプログラミング言語を既に知っており、C# を学習している開発者向けの 基礎 セクションの一部です。 プログラミングを初めて使用する場合は、最初に「 はじめ に」チュートリアルから始めてください。 コレクションの詳細については、「言語リファレンスの コレクション 」を参照してください。 配列の詳細については、言語リファレンスの「配列参照型」を参照してください。 コレクション式の詳細については、言語リファレンスの コレクション式 (コレクション リテラル) を参照してください。
別の言語から来ていますか? C# 配列は、Javaや C++ の配列など、固定サイズの順序付けされたコレクションです。
List<T>は、JavaのArrayList、JavaScript の配列、Pythonのリストなど、日常的に拡張可能なシーケンスです。
Dictionary<TKey,TValue> は、多くの言語のマップや辞書などのキーによって値を格納します。
コレクションは、複数の関連値を格納するオブジェクトです。 コレクション内の各値は 要素です。 作業項目トラッカーが常に使用する状態名など、セットが固定されている場合は配列を選択します。 新しい作業項目が追加され、完了した項目が外れるバックログなど、項目の追加や削除が発生する場合は List<T> を選択します。 ID による作業項目の検索や名前による設定など、キーで値を検索する場合は、 Dictionary<TKey,TValue> を選択します。 次のセクションでは、これらの例を簡単な意思決定モデルに変換します。
コレクションの形式を選択する
コードでのデータの使用方法に一致するコレクションを選択します。 シーケンスは、位置によって要素に到達できるように、要素を順番に格納します。 シーケンスに必要な位置の数がわかっている場合は、Arrayを使用します。これはで表されます。 配列の長さは作成後は変更できませんが、既存の位置に格納されている要素を置き換えることができます。 要素を追加または削除できるシーケンスが必要な場合は、 List<T> を使用します。 ディクショナリには、位置ではなくキーで到達した値が格納されます。 各要素に名前、ID、コードなどの検索キーがある場合は、 Dictionary<TKey,TValue> を使用します。 この例では、角かっこの間の式からコレクションを作成するコレクション式を使用します。 「 コレクション式を使用したコレクションの作成 」セクションでは、その構文について詳しく説明します。
string[] sprintPlan = ["design", "code", "test"];
List<string> backlog = ["design", "code"];
Dictionary<string, int> priorities = new()
{
["docs"] = 2,
["tests"] = 1
};
backlog.Add("test");
Console.WriteLine($"Array: {string.Join(", ", sprintPlan)}");
Console.WriteLine($"List count: {backlog.Count}");
Console.WriteLine($"Priority for docs: {priorities["docs"]}");
// This example produces the following output:
//
// Array: design, code, test
// List count: 3
// Priority for docs: 2
//
List<T> および Dictionary<TKey,TValue> は ジェネリック型です。 ジェネリック型は、 string や intなどの型引数を使用して、格納される値の種類を示します。 ジェネリックを使用すると、リストやディクショナリなどの同じコレクション図形を、 string、 int、カスタム型など、さまざまな要素型で再利用できます。 また、 型の安全性も提供されます。コンパイラはすべての要素が宣言された型であることを保証するため、キャストは必要ありません。誤って間違った型を格納することはできません。 ジェネリック型の詳細については、基礎の 「ジェネリック型とメソッド」 を参照してください。
固定サイズのデータを配列に格納する
配列は、固定長の順序付きコレクションです。 配列要素には、配列内の 0 から始まる位置である インデックスを使用してアクセスします。 インデックス 0 が最初の要素、インデックス 1 が 2 番目の要素などです。
string[] stages = ["design", "code", "test", "review"];
Console.WriteLine($"First: {stages[0]}");
Console.WriteLine($"test index: {Array.IndexOf(stages, "test")}");
// This example produces the following output:
//
// First: design
// test index: 2
//
すべての要素を順番に読み取る場合は、 foreach を使用します。 最初の要素、最後の要素、または Array.IndexOfによって返される位置が必要な場合など、位置が重要な場合はインデックスを使用します。
配列の長さは固定されていますが、その配列内の要素は変更される可能性があります。 位置が同じままで、格納されている値に更新が必要な場合は、既存のインデックスに新しい値を割り当てます。
string[] stages = ["design", "code", "test"];
stages[0] = "plan";
Console.WriteLine(string.Join(", ", stages));
Console.WriteLine($"Length: {stages.Length}");
// This example produces the following output:
//
// plan, code, test
// Length: 3
//
List<T> を使用してシーケンスを拡大および縮小します。
List<T>は、要素を順番に格納し、プログラムの実行時に増減できます。 Addを使用して要素を追加し、Removeを使用して一致する要素を削除し、Containsを使用して要素が存在するかどうかをテストし、IndexOfを使用して要素の位置を検索します。 リスト インデクサーを使用して、既存の位置にある値を置き換えます。
List<string> workItems = ["design", "code", "test"];
workItems.Add("review");
workItems.Remove("code");
workItems[1] = "verify";
Console.WriteLine(string.Join(", ", workItems));
Console.WriteLine($"Has review: {workItems.Contains("review")}");
Console.WriteLine($"Index of verify: {workItems.IndexOf("verify")}");
// This example produces the following output:
//
// design, verify, review
// Has review: True
// Index of verify: 1
//
List<T>では、アイテムを追加または削除しても、残りの要素は順序どおりに保たれます。 要素を削除すると、後の要素は下位インデックスに移動します。
Insertを使用して、特定の位置に 1 つの要素を追加し、InsertRangeを使用して、位置に複数の要素を追加し、RemoveAtを使用して、インデックスで要素を削除します。
List<string> workItems = ["design", "test"];
workItems.Insert(1, "code");
Console.WriteLine($"Insert middle: {string.Join(", ", workItems)}");
workItems.Insert(0, "plan");
Console.WriteLine($"Insert front: {string.Join(", ", workItems)}");
workItems.InsertRange(workItems.Count, ["review", "deploy"]);
Console.WriteLine($"Insert range at end: {string.Join(", ", workItems)}");
workItems.RemoveAt(workItems.Count - 1);
Console.WriteLine($"Remove end: {string.Join(", ", workItems)}");
workItems.RemoveAt(2);
Console.WriteLine($"Remove middle: {string.Join(", ", workItems)}");
workItems.RemoveAt(0);
Console.WriteLine($"Remove front: {string.Join(", ", workItems)}");
// This example produces the following output:
//
// Insert middle: design, code, test
// Insert front: plan, design, code, test
// Insert range at end: plan, design, code, test, review, deploy
// Remove end: plan, design, code, test, review
// Remove middle: plan, design, test, review
// Remove front: design, test, review
//
List<T>の末尾での追加または削除は高速です。 Addでの追加は平均して O(1) の操作であり、RemoveAtで最後の要素を削除する操作も O(1) です。 後のすべての要素が新しいインデックスにシフトするため、前面または中央に挿入または削除するのは O(n) です。 コードで先頭への挿入や削除を頻繁に行う場合、List<T> は適切なコレクションの種類ではない可能性があります。
Tip
Big-O 表記は、コレクション サイズ ( n) が大きくなるにつれて作業がどのように大きくなるかを表します。 O(1) は、"order one" と発音され、コレクションに含まれる要素の数に関係なく、操作に約同じ量の作業が必要です。 O(n) は、"order n" と発音され、コレクション内の要素の数にほぼ比例して作業が増加することを意味します。
キーで Dictionary<TKey,TValue> に値を関連付ける
Dictionary<TKey,TValue>には、キーと値のペアが格納されます。 キーと値のペアは、1 つのキーとそのキーに関連付けられた値です。.NET は、1 組を KeyValuePair<TKey,TValue> で表します。 ディクショナリ インデクサーを使用してキーで値を追加または更新し、キーが存在しない可能性がある場合は TryGetValue を使用します。
Dictionary<string, int> priorities = new()
{
["docs"] = 2,
["tests"] = 1
};
priorities["review"] = 3;
priorities.Remove("review");
if (priorities.TryGetValue("docs", out int docsPriority))
{
Console.WriteLine($"docs priority: {docsPriority}");
}
else
{
Console.WriteLine("docs missing");
}
if (priorities.TryGetValue("deploy", out int deployPriority))
{
Console.WriteLine($"deploy priority: {deployPriority}");
}
else
{
Console.WriteLine("deploy missing");
}
Console.WriteLine($"count: {priorities.Count}");
// This example produces the following output:
//
// docs priority: 2
// deploy missing
// count: 2
//
インデクサーを使用すると、角かっこ構文を使用してオブジェクトから値にアクセスできます。 ディクショナリ インデクサーは、キーが存在する必要がある場合、または値を割り当てるときに便利です。 TryGetValue は、キーが存在するかどうかを確認し、存在する場合は値を提供します。 インデクサー演算子の詳細については、メンバー アクセス演算子については、言語リファレンスを参照してください。
既に存在するキーに関連付けられている値を変更することもできます。 インデクサーを介して代入し、キーを追加するか、既存のキーの値を置き換えます。 置換が現在の値に依存する場合は、最初に値を読み取り、次に新しい値を割り当てます。
Dictionary<string, int> priorities = new()
{
["docs"] = 2,
["tests"] = 1
};
priorities["docs"] = 1;
if (priorities.TryGetValue("tests", out int testsPriority))
{
priorities["tests"] = testsPriority + 1;
}
priorities["deploy"] = 3;
Console.WriteLine($"docs priority: {priorities["docs"]}");
Console.WriteLine($"tests priority: {priorities["tests"]}");
Console.WriteLine($"deploy priority: {priorities["deploy"]}");
// This example produces the following output:
//
// docs priority: 1
// tests priority: 2
// deploy priority: 3
//
コレクション式を使用してコレクションを作成する
コレクション式は、角かっこの間の式からコレクションを作成します。 コレクション式では、配列、リスト、およびその他のコレクション型を作成できます。 スプレッド要素は、別のコレクションから新しいコレクションに要素をコピーします。
string[] planned = ["design", "code"];
// The spread element .. planned copies each element from planned.
string[] upcoming = [.. planned, "test"];
List<string> blocked = ["docs"];
Console.WriteLine($"Upcoming: {string.Join(", ", upcoming)}");
Console.WriteLine($"Blocked: {string.Join(", ", blocked)}");
// This example produces the following output:
//
// Upcoming: design, code, test
// Blocked: docs
//
コレクション式は、初期化を簡潔に保ちます。 コレクション式に独自の型がありません。 コンパイラは、式をコンテキストが必要とするコレクションの形に変換します。 受け取る変数またはパラメーターは、配列、 List<T>、またはその他のサポートされているコレクション型に変換できます。
インデックスと範囲を使って位置から読み取る
インデックスは「どの 1 つの要素か」を示します。範囲は「どの連続したスライスか」を示します。コードで必要なのが 1 つの要素である場合はインデックスを使用し、作業対象のデータの一部としてサブセクションを扱う場合は範囲を使用します。 配列と List<T> では、インデックスと範囲の両方がサポートされます。
Indexは、シーケンスの先頭または末尾からカウントできます。 最初からカウントするには、数値を直接使用します。 phases[0] は最初の要素を読み取り、 phases[1] は 2 番目の要素を読み取ります。
^を使用して末尾からカウントします。phases[^1]は最後の要素を読み取り、phases[^2]は次から最後の要素を読み取ります。
Rangeは、.. (2 つのドット) を使用してスライスを選択します。 範囲演算子は、どちらもドットを使用しますが、コレクション式で前述の拡散要素と同じではありません。 インデックスbからインデックスのaの要素が必要な場合は、範囲a..bを使用します。 式 phases[1..3] では、インデックス 1 とインデックス 2の要素を含む新しい配列が作成されます。
一方または両方の範囲インデックスを省略できます。 最初の 2 つの要素の phases[..2] のように、スライスが先頭から始まるときに開始インデックスを省略します。 インデックスphases[2..]から末尾までの要素の2のように、スライスが最後の要素まで続く場合は、終了インデックスを省略します。
phases[..]のように、配列全体のコピーが必要な場合は、両方のインデックスを省略します。 範囲はインデックスの種類を混在させることができます。 式 phases[1..^1] は、開始位置インデックスと from-end インデックスを組み合わせて、中央の要素( code および test)を返します。
string[] phases = ["design", "code", "test", "deploy"];
List<string> checklist = ["design", "test", "review"];
Console.WriteLine($"Last: {phases[^1]}");
Console.WriteLine($"Middle: {string.Join(", ", phases[1..3])}");
Console.WriteLine($"Last two: {string.Join(", ", phases[^2..])}");
Console.WriteLine($"Without ends: {string.Join(", ", phases[1..^1])}");
Console.WriteLine($"First two: {string.Join(", ", phases[..2])}");
Console.WriteLine($"From third: {string.Join(", ", phases[2..])}");
Console.WriteLine($"All phases: {string.Join(", ", phases[..])}");
Console.WriteLine($"List last: {checklist[^1]}");
Console.WriteLine($"List first two: {string.Join(", ", checklist[0..2])}");
// This example produces the following output:
//
// Last: deploy
// Middle: code, test
// Last two: test, deploy
// Without ends: code, test
// First two: design, code
// From third: test, deploy
// All phases: design, code, test, deploy
// List last: review
// List first two: design, test
//
インデックスと範囲の詳細については、チュートリアルの 「インデックスと範囲を使用してデータの範囲を調べる」 を参照してください。
参照
.NET