for WPF developers
Home Profile Tips 全記事一覧

ToDictionary 拡張メソッドでシーケンスから Dictionary<TKey, TSource> を生成する

(2017/03/14 11:09:41 created.)

ToDictionary 拡張メソッドは元のシーケンスから Dictionary<TKey, TSource> クラスを生成します。サンプルコードを以下に掲載します。

Person.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.  
  5.     /// <summary>
  6.     /// 人物データを表します。
  7.     /// </summary>
  8.     public class Person
  9.     {
  10.         /// <summary>
  11.         /// 固有識別子を取得または設定します。
  12.         /// </summary>
  13.         public Guid Id { get; set; }
  14.  
  15.         /// <summary>
  16.         /// 氏名を取得または設定します。
  17.         /// </summary>
  18.         public string Name { get; set; }
  19.  
  20.         /// <summary>
  21.         /// 年齢を取得または設定します。
  22.         /// </summary>
  23.         public int Age { get; set; }
  24.     }
  25. }
Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Collections.Generic;
  5.     using System.Linq;
  6.  
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             var people = GetPeople();
  12.  
  13.             var personDictionary = people.ToDictionary(x => x.Id);
  14.             foreach (var p in personDictionary)
  15.             {
  16.                 Console.WriteLine("Key = " + p.Key.ToString() + "\tValue = " + p.Value.Name);
  17.             }
  18.  
  19.             var person = personDictionary[new Guid("{00000000-0000-0000-0000-000000000002}")];
  20.             Console.WriteLine(person.Name);
  21.  
  22.             Console.ReadKey();
  23.         }
  24.  
  25.         /// <summary>
  26.         /// 人物コレクションの列挙子を取得します。
  27.         /// </summary>
  28.         /// <returns></returns>
  29.         static IEnumerable<Person> GetPeople()
  30.         {
  31.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000001}"), Name = "田中 淳平", Age = 37 };
  32.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000002}"), Name = "鈴木 ほのか", Age = 26 };
  33.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000003}"), Name = "小池 哲司", Age = 22 };
  34.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000004}"), Name = "恩田 進", Age = 42 };
  35.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000005}"), Name = "中津山 亜希子", Age = 20 };
  36.         }
  37.     }
  38. }


ToDictionary 拡張メソッドの入力引数には結果として得られるディクショナリでキーとして扱うためのプロパティ値を指定するデリゲートを渡します。サンプルコードでは Person.Id プロパティを渡しています。

得られたディクショナリの中身を 14 ~ 20 行目で確認しています。Id に対して正しい Person クラスのインスタンスが登録されている様子が出力結果からわかります。

ToDictionary 拡張メソッドはディクショナリを生成するため、指定するプロパティの値に重複するインスタンスが存在する場合、実行時に ArgumentException 例外が発生してアプリケーションが停止してしまいます。Distinct 拡張メソッドで重複するインスタンスを除外するなどして、重複がないことを保証されているシーケンスに対してのみ使用するようにしましょう。

ディクショナリのキーを指定できるように、その値も指定できます。次のサンプルでは、キーとして Id プロパティ、値として Name プロパティを指定してディクショナリを生成しています。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Collections.Generic;
  5.     using System.Linq;
  6.  
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             var people = GetPeople();
  12.  
  13.             var nameDictionary = people.ToDictionary(x => x.Id, x => x.Name);
  14.             foreach (var p in nameDictionary)
  15.             {
  16.                 Console.WriteLine("Key = " + p.Key.ToString() + "\tValue = " + p.Value);
  17.             }
  18.  
  19.             var name = nameDictionary[new Guid("{00000000-0000-0000-0000-000000000002}")];
  20.             Console.WriteLine(name);
  21.  
  22.             Console.ReadKey();
  23.         }
  24.  
  25.         /// <summary>
  26.         /// 人物コレクションの列挙子を取得します。
  27.         /// </summary>
  28.         /// <returns></returns>
  29.         static IEnumerable<Person> GetPeople()
  30.         {
  31.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000001}"), Name = "田中 淳平", Age = 37 };
  32.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000002}"), Name = "鈴木 ほのか", Age = 26 };
  33.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000003}"), Name = "小池 哲司", Age = 22 };
  34.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000004}"), Name = "恩田 進", Age = 42 };
  35.             yield return new Person() { Id = new Guid("{00000000-0000-0000-0000-000000000005}"), Name = "中津山 亜希子", Age = 20 };
  36.         }
  37.     }
  38. }


ToDictionary 拡張メソッドの第 2 入力引数に値として扱うプロパティを指定するデリゲートを渡しています。出力結果は先ほどと同じになっていますが、値が string 型の Name プロパティそのものになっているため、16 行目や 20 行目が若干異なります。

ここではキーとして System.Guid 構造体を指定してるため、デフォルトで比較することができますが、キーとして独自クラスなどを指定した場合、同一のインスタンスかどうかで比較してキーを生成してしまうため、たとえすべてのプロパティ値が同じ値を持っていたとしても別のインスタンスであれば別のキーとして生成されてしまいます。このような状況を避けたい場合は、対象とするクラスに IEqualityComparer<T> インターフェースを実装するか、ToDictionary 拡張メソッドにキーを比較する方法を IEqualityComparer<T> として入力引数にデリゲートとして渡します。IEqualityComparer<T> による等値比較については「Contains 拡張メソッドで指定した要素が含まれているかどうかを確認する」などを参照してください。また、ToDictionary 拡張メソッドには等値比較を指定する場合でもキーと値を指定するオーバーロードも用意されています。