Dictionaryはキー(Key)/値(Value)を1対1ペア形式で保持するジェネリック型の配列です。内部にハッシュ配列アルゴリズムに基づいて実装されており、.NETプログラミングにおける汎用性の高い中堅クラスです。
このクラスを熟知し、様々な場面で活用すれば、効率をよくわかりやすいプログラミングの作成ができます。これらのクラスの継承およびインターフェースの活用について、ネコ技術自身の経験と発想も皆様に共有したいと思います。
基本の使用方法
以下の例では人の名前/年齢というキー/値のオブジェクトを保持します。
必要な引用
DictionaryとIDictionaryの利用には、以下のnamespaceを利用する必要があります。
using System.Collections.Generic; using System.Linq;
配列の作成
Dictionary<string, int> dict = new Dictionary<string, int>();
キーを利用して値を設定
dict["John"] = 25; dict["Tom"] = 30; dict["Mack"] = 45;
キーを利用して値を取得
Console.WriteLine(dict["Tom"]); // 出力:30
暗黙の型変換
byte age = 10; dict["Malley"] = age;
但し、大き目のデータから小さめのデータに転換する際に一部のデータが失われる恐れがあり、暗黙の型変換ができない場合もあります。
long oldAge = 87; dict["Peter"] = oldAge;
キーと値の列挙
配列に全てのキーや値を列挙することも可能です。Keysプロパティは全てのキーを列挙するEnumerator、Valuesプロパティは全ての値を列挙するEnumeratorです。
foreach (var key in dict.Keys) { Console.WriteLine(key); }
キーの存在チェック
var exist = dict.ContainsKey("John"); // True var exist = dict.ContainsKey("Smith"); // False
Linqの拡張メソッドによって確認も可能です。
var exist = dict.Keys.Contains("Mack"); // True
値の存在チェック
var exist = dict.ContainsValue(30); // True var exist = dict.ContainsValue(-10); // False
チェックおよび取得
配列に存在しないキーを利用して値を取得するとエラーが起きます。この場合先にチェックしたほうが安全です。
// NG: KeyNotFoundExceptionが発生します var age = dict["Clark"];
下記のように安全な方法を利用しましょう。
// OK: 安全な方法 int age = 0; if (dict.ContainsKey("Clark")) { age = dict["Clark"]; }
但し、チェックを付けると配列の検索を2回行いますので性能が落ちます。この場合TryGetValue
メソッドを利用します。
int age = 0; if (dict.TryGetValue("Clark", out age)) { // 取得が成功の場合のみ値を出力します Console.WriteLine("age = " + age); }
TryGetValue
メソッドを利用すると1回で値を安全に取得できます。また、多くの場合 if を利用しなくてもかまいません。
int age = 0; dict.TryGetValue("Clark", out age);
キーClarkが存在しなければ、ageは0となります。
元素の数を取得
var keyValueParis = dict.Count;
KeysとValuesのそれぞれの数も取得できますが、キー/値ペアの保持ですので必ずCountと同じ結果になります。
var keys = dict.Keys.Count; var values = dict.Values.Count;
キーまたは値のList化
var keyList = dict.Keys.ToList(); var valueList = dict.Values.ToList();
初期化
Dictionary配列の初期化
以下のコードは基本的な初期化を示します。
Dictionary<string, int> dict = new Dictionary<string, int>(); dict["John"] = 25; dict["Tom"] = 30; dict["Mack"] = 45; ...
簡略した初期化
上記のような基本の初期化は、よりソースコードを簡単化できます。
Dictionary<string, int> dict = new Dictionary<string, int>() { { "John", 25 }, { "Tom", 30 }, { "Mack", 45 }, };
作成したdictの内容は同じですが、ソースコードが分かりやすくなりました。
*C# 6.0の新仕様によって、以下の書き方も利用できます。
Dictionary<string, int> dict = new Dictionary<string, int>() { ["John"] = 25, ["Tom"] = 30, ["Mack"] = 45, };
拡張
継承して拡張
一部の処理を変えたい、値を監視したい、もしくはログを出力したい場合、Dictionaryを継承して自分のカスタマイズクラスを作成できます。
class ExtendedDictionary<TKey, TValue> : Dictionary<TKey, TValue> { public new void Add(TKey key, TValue value) { base.Add(key, value); Console.WriteLine("{0}が追加された", key); } public new TValue this[TKey key] { get { return base[key]; } set { base[key] = value; Console.WriteLine("{0}が変更された", key); } } }
ただ既存のメソッドをoverride
ではなく、new
キーワードを利用して上書きします。
IDictionaryインターフェース
IDictionaryはDictionaryの基本外観を定義していますので、利用者は自分の実現方法でDictionaryと互換できるクラスを作成できます。
IDictionary と Dictionary の違い
IDictionary はインターフェイス(Interface)ですので、C#の他のインターフェイスと同じく、クラスの外観(外からみる様子)を定義しています。中身は空で、値を保存しません。インターフェイスはそのまま実例化して利用できません。他のクラスの継承先としてしか利用できません。
インターフェース定義
public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable { ICollection<TKey> Keys { get; } ICollection<TValue> Values { get; } TValue this[TKey key] { get; set; } void Add(TKey key, TValue value); bool ContainsKey(TKey key); bool Remove(TKey key); bool TryGetValue(TKey key, out TValue value); }
カスタマイズ実現
IDictionary<TKey, TValue>
に定義しているプロパティ、メソッド以外、IDictionary<TKey, TValue>
の継承先のインターフェースに定義しているプロパティ、メソッドの実装も必要です。
class MyDictionary<TKey, TValue> : IDictionary<TKey, TValue> { ... }
マルチ値の対応
学生の各科目の成績をリストとして保持できるDictionaryを作成します。
値の型をListに指定
Dictionary<string, List<int>> listDict = new Dictionary<string, List<int>>(); listDict["John"] = new List<int>(new int[] { 85, 95, 70 });
簡略した初期化
Dictionary<string, List<int>> listDict = new Dictionary<string, List<int>>() { { "John", new List<int>(new int[] { 85, 95, 70 }) }, { "Tom", new List<int>(new int[] { 98, 76, 85 }) }, };
Dictionaryの継承に基づいて実現
継承先のTValue定義を List<TValue> に変更して、外部からList型を見えないようにします。
class MultiValueDictionary1<TKey, TValue> : Dictionary<TKey, List<TValue>> { }
継承したクラスの使用
MultiValueDictionary<string, int> myDictEx1 = new MultiValueDictionary<string, int>(); myDictEx1["John"] = new List<int>() { 85, 95, 70 }; myDictEx1["Tom"] = new List<int>() { 98, 76, 85 };
Johnの値の数を表示、3つの成績が保持されています。
Console.WriteLine(myDictEx1["John"].Count); // 出力:3
二つ目の成績を出力します。
Console.WriteLine(myDictEx1["John"][1]); // 出力:95
カスタマイズの初期化方法
TValueをList型にした継承のクラスは、初期化する際にnew List<int>()
の記述が必要です。このコードをさらに簡単化できます。
Addメソッドを実装してカスタマイズの初期化方法を拡張できます。(Addメソッドはインターフェースの約束ではなく、C#コンパイラの内蔵機能です)
class MultiValueDictionary1<TKey, TValue> : Dictionary<TKey, List<TValue>> { public void Add(TKey key, params TValue[] values) { this[key] = new List<TValue>(values); } }
上記Addメソッドの実装の後、以下の簡略した初期化の利用が可能になります。
MultiValueDictionary<string, int> myDictEx2 = new MultiValueDictionary<string, int>() { { "John", 85, 95, 70 }, { "Tom", 98, 76, 85 }, };
new List<int>()
の記述が省略され、ソースコードが大幅に簡略化できました。
型固定のカスタマイズDictionary配列
該当クラスのジェネリック型を隠し、継承元の型を明確に指定すると、型固定のカスタマイズDictionaryを作成できます。
class FixedTypeDictionary : Dictionary<string, List<int>> { public void Add(string key, int score1, int score2, int score3) { this[key] = new List<int>(new int[] { score1, score2, score3 }); } }
型固定のカスタマイズ配列の場合初期化には型の指定がなく、より使いやすくなります。
var fixedDict = new FixedTypeDictionary() { {"John", 85, 95, 70}, {"Tom", 98, 76, 85}, };
また、実装したAddメソッドには三つのintパラメータを定義していますので、簡略した初期化にも三つのintパラメータの指定が必要です。それ以外の場合コンパイラエラーになります。
(最後更新:2019年3月28日)