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つまたは件数だけなら、リストを格納する代わりにループ内で値を更新します。