HOWTO · Csharp

C# でリストを辞書に変換する

C#のリストを辞書に変換する方法を、LINQとforeachループを使って解説します。重複キーの扱い方も紹介します。

このページの内容

各ソース項目を辞書の1つのエントリにするには、LINQ ToDictionary() を使います。エントリを明示的に作成する場合は foreach ループを使います。どちらの方法でも、一意になるキーを選んでください。キーが重複する可能性がある場合は、辞書を作成する前に項目をグループ化するか、どの値を残すかを決めます。

C# でリストを辞書に変換する

辞書はキーを使って値を格納するため、リストを変換するには、どのプロパティをキーにし、どの値を対応させるかを決める必要があります。たとえば、シーズンのリストでは、各シーズンの Id をキーにし、Season オブジェクト全体を値として保持できます。変換後の Dictionary<TKey, TValue> のキー型と値型は、この選択によって決まります。完全な例では、必要に応じて小さな Season クラスを定義しています。

LINQ ToDictionary() を使ってリストを辞書に変換する

LINQ を使うには System.Linq をインポートします。ToDictionary() にはキーセレクターと、必要に応じて要素セレクターを指定します。最初のセレクターがキーを選び、2つ目が格納する値を選びます。要素セレクターを省略すると、ソース項目全体が値になります。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

using System.Collections.Generic;
using System.Linq;

class Season
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

class Program
{
    static void Main()
    {
        var seasons = new List<Season>
        {
            new Season { Id = 1, Name = "Spring" },
            new Season { Id = 2, Name = "Summer" }
        };

        Dictionary<int, Season> seasonsById = seasons.ToDictionary(s => s.Id);
        Dictionary<int, string> namesById = seasons.ToDictionary(s => s.Id, s => s.Name);
        System.GC.KeepAlive(seasonsById);
        System.GC.KeepAlive(namesById);
    }
}

名前だけが必要な場合は、キーに s => s.Id、値に s => s.Name を使います。セレクターを1つだけ指定するオーバーロードでは、seasonsById に各 Season オブジェクト全体が格納されます。この変換を成功させるには、キーが一意でなければなりません。

foreach ループでリストを辞書に変換する

ループを使うと、対応付けを明示でき、各項目に対する処理も追加できます。この例では、LINQ の例と同じ、IDからシーズンへの対応付けを作成します。

このセクションのコードと表示されるラベルや出力は英語のままです。

using System.Collections.Generic;

class Season
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

class Program
{
    static void Main()
    {
        var seasons = new List<Season>
        {
            new Season { Id = 1, Name = "Spring" },
            new Season { Id = 2, Name = "Summer" }
        };
        var seasonsById = new Dictionary<int, Season>();

        foreach (var season in seasons)
        {
            seasonsById.Add(season.Id, season);
        }
    }
}

Add は season.Id をキーにし、season オブジェクトを値として使います。どちらの方法を選ぶかは、読みやすさと辞書の作成中に必要な処理に応じて決めてください。パフォーマンスはワークロードによって異なるため、ループが常に速いとは言えません。

重複キーを理解して処理する

辞書では、同じキーに複数の値を格納できません。選択したキーが複数回現れると ToDictionary() は ArgumentException をスローします。また、そのキーがすでに存在すると Dictionary.Add(key, value) も ArgumentException をスローします。どちらも既存の値を暗黙に置き換えることはありません。

ソース内でキーの重複が有効な場合は、辞書の各値に何を表させるかを決めます。たとえば、件数、一致する項目すべてのコレクション、または選択した1つの項目です。先にグループ化すれば、キーごとに1つのグループができるため、その後安全に辞書へ変換できます。

重複キーの件数を数える

重複する値でグループ化し、各グループの Count() を辞書の値にします。この例では、最初の出現も含め、各文字列の出現回数をすべて数えます。そのため、件数が1より大きければキーが重複していると分かります。

このセクションのコードと表示されるラベルや出力は英語のままです。

using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var words = new List<string> { "You", "Breaking Bad", "Marvel", "You" };
        var countsByWord = words.GroupBy(word => word)
            .ToDictionary(group => group.Key, group => group.Count());

        foreach (var entry in countsByWord)
        {
            System.Console.WriteLine($"{entry.Key}={entry.Value}");
        }
    }
}

出力例(辞書の反復順序は変わる場合があります)
Breaking Bad=1
Marvel=1
You=2

You=2
Breaking Bad=1
Marvel=1

この処理で Dictionary<string, int> が作成されます。異なる単語がそれぞれキーとなり、値にはその単語の出現回数が入ります。最初の出現後に追加された分だけを数えたい場合は、結果を解釈するときに各件数から1を引きます。

キーごとにすべての値を保持する

キーに対応する項目をすべて保持するには、そのキーでグループ化し、各グループをリストに変換します。こうすると、辞書で各年を、その年に公開されたすべてのシーズンに対応付けられます。

このセクションのコードと表示されるラベルや出力は英語のままです。

using System.Collections.Generic;
using System.Linq;

class Season
{
    public int YearOfRelease { get; set; }
    public string Name { get; set; } = "";
}

class Program
{
    static void Main()
    {
        var seasons = new List<Season>
        {
            new Season { YearOfRelease = 2020, Name = "North" },
            new Season { YearOfRelease = 2020, Name = "South" },
            new Season { YearOfRelease = 2021, Name = "West" }
        };
        Dictionary<int, List<Season>> seasonsByYear = seasons
            .GroupBy(season => season.YearOfRelease)
            .ToDictionary(group => group.Key, group => group.ToList());

        foreach (var entry in seasonsByYear)
        {
            System.Console.WriteLine(
                $"{entry.Key}={string.Join(", ", entry.Value.ConvertAll(season => season.Name))}");
        }
    }
}

辞書では各キーが1回だけ現れ、その値のリストにはグループ内のソース項目がすべて含まれます。グループを1つの値にまとめず、後続のコードで項目をすべて確認または処理する必要がある場合に、この形式を使います。

キーごとに1つの値を保持する

代表となる値が1つあればよい場合は、各グループから項目を1つ選びます。First() はグループのシーケンス内で最初の項目を選ぶため、結果はソースの順序に依存します。特定の基準で代表値を決める必要がある場合は、プロパティで並べ替えてから First() を使うなど、別の選択ルールを指定します。

このセクションのコードと表示されるラベルや出力は英語のままです。

using System.Collections.Generic;
using System.Linq;

class Season
{
    public int YearOfRelease { get; set; }
    public string Name { get; set; } = "";
}

class Program
{
    static void Main()
    {
        var seasons = new List<Season>
        {
            new Season { YearOfRelease = 2020, Name = "North" },
            new Season { YearOfRelease = 2020, Name = "South" },
            new Season { YearOfRelease = 2021, Name = "West" }
        };
        Dictionary<int, Season> firstSeasonByYear = seasons
            .GroupBy(season => season.YearOfRelease)
            .ToDictionary(group => group.Key, group => group.First());
        System.GC.KeepAlive(firstSeasonByYear);
    }
}

グループ化すると年ごとに辞書エントリが1つでき、First() がそのエントリの値を指定します。ソースシーケンスの順序が用途にとって意味を持たない場合は、代表値を選ぶ前に必要な順序を明示してください。

ループで重複キーを処理する

ループでグループ化する場合は、キーごとにリストを格納します。キーが初めて現れたときだけリストを作成し、その後の項目をすべて追加します。これにより、すべての値を保持しながら、同じキーに対して Add を2回呼ぶことを避けられます。

このセクションのコードと表示されるラベルや出力は英語のままです。

using System.Collections.Generic;

class Season
{
    public int YearOfRelease { get; set; }
    public string Name { get; set; } = "";
}

class Program
{
    static void Main()
    {
        var seasons = new List<Season>
        {
            new Season { YearOfRelease = 2020, Name = "North" },
            new Season { YearOfRelease = 2020, Name = "South" },
            new Season { YearOfRelease = 2021, Name = "West" }
        };
        var seasonsByYear = new Dictionary<int, List<Season>>();

        foreach (var season in seasons)
        {
            if (!seasonsByYear.ContainsKey(season.YearOfRelease))
            {
                seasonsByYear.Add(season.YearOfRelease, new List<Season>());
            }
            seasonsByYear[season.YearOfRelease].Add(season);
        }
    }
}

作成される辞書には年ごとに1つのリストがあり、各シーズンは対応するリストに追加されます。必要なのが値1つまたは件数だけなら、リストを格納する代わりにループ内で値を更新します。