ハッシュテーブル(連想配列)を使うには?(Dictionaryクラス編)[C#/VB、.NET 2.0].NET TIPS

Dictionaryクラスを利用して、キーと値のペアで構成される項目を1つ以上格納する「連想配列」を取り扱う方法を解説する。

» 2018年08月08日 05時00分 公開
「.NET TIPS」のインデックス

連載「.NET TIPS」

本稿は2006/03/31に初版公開した記事を改訂し、Visual Studio 2017でコードの動作検証、図版の追加、全般的な構成の変更などを行ったものです。


本稿は「TIPS:ハッシュテーブル(連想配列)を使うには?」に加筆・修正を行い、.NET Framework 2.0に対応させた改訂版です。


 ハッシュテーブルとは、キー(key)と値(value)のペアを保持しているコレクションである。通常の配列やリストがインデックス番号により各値(各要素)にアクセスできるのに比べて、ハッシュテーブルでは、インデックス番号の代わりにキーを用いて、その各値にアクセスすることができる。キーと、そのキーから連想される(キーに対応付けられている)値のペアを保持しているため、ハッシュテーブルは「連想配列」とも呼ばれる。ハッシュテーブルの特長は、指定したキーから、それに対応した値を高速に得られることである。

 .NET Framework 1.xのクラスライブラリでは、ハッシュテーブルはHashtableクラス(System.Collections名前空間)で実装されていたが、.NET Framework 2.0ではDictionaryジェネリッククラス(System.Collections.Generic名前空間)が追加され、より効率的かつ安全にハッシュテーブルを扱えるようになっている。本稿では、このDictionaryジェネリッククラスの基本的な利用方法についてまとめる。

POINT Dictionaryクラスの使い方

Dictionaryクラスの使い方まとめ Dictionaryクラスの使い方まとめ


 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

ハッシュテーブルのインスタンス作成

 Dictionaryジェネリッククラスは、コレクションの要素となるキーと値の型を型パラメーターにより指定してインスタンスを作成する。以下は、キーも値も文字列の場合の記述例だ。

Dictionary<string, string> dict = new Dictionary<string, string>();

Dim dict As New Dictionary(Of String, String)

Dictionaryジェネリッククラスのインスタンス生成(上:C#、下:VB)

 この場合には、以降のハッシュテーブル(この例ではdict)への操作で、文字列でないキーや値を扱うコードはコンパイルエラーとなる。

ハッシュテーブルへの項目の追加

 ハッシュテーブルへの項目(キーと値のペア)の追加は、インデクサ(VBの場合は既定のプロパティとなっているItemプロパティ)か、Addメソッドにより行う。

dict["japan"] = "日本";
dict["america"] = "アメリカ";

dict.Add("china", "中国");
dict.Add("india", "インド");

dict("japan") = "日本"
dict("america") = "アメリカ"

dict.Add("china", "中国")
dict.Add("india", "インド")

ハッシュテーブルへの項目の追加(上:C#、下:VB)

 指定したキーがハッシュテーブルに存在しない場合には、新しい要素として登録される。キーがすでに存在する場合には、インデクサではそのキーに対応する値が置き換えられるが、Addメソッドでは例外が発生する。重複するキーが存在しないと分かっている初期化の際などにはAddメソッドを使い、後から追加/更新するときにはインデクサを使うとよいだろう。

 なお、C# 3/VB 10以降ではDictionaryジェネリッククラスのインスタンス作成と同時に項目を追加可能だ。C#での実際の利用例については以下の「ソート、リスト化、逆引き(値からキーを検索)[.NET 3.5]」を参照してほしい。

ハッシュテーブル内の値の取得

 ハッシュテーブル内のあるキーに対する値の取得は、上記と同様にインデクサによりキーを指定して行う(逆に、値からキーを直接取得することはできない)。Hashtableクラスでは取り出した値はObject型となるため通常はキャストが必要となるが、Dictionaryジェネリッククラスではキャストは不要だ。

string val = dict["japan"];

Dim val As String = dict("japan")

ハッシュテーブル内の値の取得(上:C#、下:VB)

 また、Hashtableクラスでは、指定したキーがハッシュテーブルに存在しない場合にはnull(VBではNothing)が返されるが、Dictionaryジェネリッククラスでは例外が発生する。

 このため、指定したキーが存在するかどうかをチェックし、もし存在すればその値を取得するTryGetValueメソッドがDictionaryジェネリッククラスには追加されている(記述例は下のサンプルプログラムを参照)。

全てのキーや値の列挙

 ハッシュテーブルに格納されている全てのキーあるいは値は、コレクションとしてKeysプロパティあるいはValuesプロパティから取得できる。このため、foreachステートメント(VBではFor Each……Nextステートメント)などを使って、全てのキーや値を列挙することができる(記述例は下のサンプルプログラムを参照)。

キーや値の存在チェック

 特定のキーや値を持つ項目がハッシュテーブルに格納されているかどうかを調べるには、ContainsKeyメソッドあるいはContainsValueメソッドを使用する。この2つのメソッドはbool型(VBではBoolean型)の値(TrueかFalse)を返す(記述例は下のサンプルプログラムを参照)。

個々の要素の列挙

 ハッシュテーブル内の要素を列挙する場合には、各要素はKeyValuePairジェネリック構造体(System.Collections.Generic名前空間)のオブジェクトとして扱われる。この構造体は2つの型パラメーターを持つが、それらはDictionaryジェネリッククラスのインスタンス作成時に指定したものと同一となる。そして、KeyValuePairジェネリック構造体のオブジェクトからは、KeyおよびValueプロパティによりキーと値を取得できる(記述例は下のサンプルプログラムを参照)。

 なお通常の配列などと異なり、ハッシュテーブルでは格納した要素の順序は保持されない。このためキーや値や要素を列挙した場合に、どのような順で要素が取り出されるかは不定である。

ハッシュテーブルを使ったサンプルプログラム

 ここまでの解説をまとめたサンプルプログラムを以下に示す。Visual Studio 2017でVBプロジェクトを新規作成して、以下のコードを試す場合には、Mainプロシージャ内のコードをコピー&ペーストしてほしい。

// dictionary.cs

using System;
using System.Collections.Generic;

class DictionaryTest {
  static void Main() {

    Dictionary<string, string> dict = new Dictionary<string, string>();

    ////////////////////////////////////////
    // 要素の追加その1
    dict["japan"] = "日本";
    dict["america"] = "アメリカ";

    // 要素の追加その2
    dict.Add("china", "中国");
    dict.Add("india", "インド");

    ////////////////////////////////////////
    // 値の取得その1
    string val = dict["japan"];
    Console.WriteLine(val); // 出力:日本

    // string sss = dict["russia"]; // 例外発生

    // 値の取得その2
    string value = "";
    if (dict.TryGetValue("america", out value)) {
      Console.WriteLine(value); // 出力:アメリカ
    }

    ////////////////////////////////////////
    // キーの列挙
    foreach (string key in dict.Keys) {
      Console.WriteLine(key);
    }
    // 出力例:
    // japan
    // america
    // china
    // india

    // 値の列挙
    foreach (string v in dict.Values) {
      Console.WriteLine(v);
    }
    // 出力例:
    // 日本
    // アメリカ
    // 中国
    // インド

    ////////////////////////////////////////
    // キーの存在チェック
    if (!dict.ContainsKey("france")) {
      // 存在しない場合
      dict["france"] = "フランス";
    }

    // 値の存在チェック
    Console.WriteLine(dict.ContainsValue("日本")); // 出力:True

    ////////////////////////////////////////
    // 項目(キーと値)の列挙
    foreach (KeyValuePair<string, string> kvp in dict) {
      Console.WriteLine("{0} : {1}", kvp.Key, kvp.Value);
    }
    // 出力例:
    // japan : 日本
    // america : アメリカ
    // china : 中国
    // india : インド
    // france : フランス

    ////////////////////////////////////////
    // ソート済みのハッシュテーブルの利用

    SortedDictionary<string, string> sdict = new SortedDictionary<string, string>(dict);

    foreach (KeyValuePair<string, string> kvp in sdict) {
      Console.WriteLine("{0} : {1}", kvp.Key, kvp.Value);
    }
    // 出力例:
    // america : アメリカ
    // china : 中国
    // france : フランス
    // india : インド
    // japan : 日本
  }
}

// コンパイル方法:csc dictionary.cs

' dictionary.vb

Imports System
Imports System.Collections.Generic

Class DictionaryTest
  Shared Sub Main()

    Dim dict As New Dictionary(Of String, String)

    ''''''''''''''''''''''''''''''''''''''' 
    ' 要素の追加その1
    dict("japan") = "日本"
    dict("america") = "アメリカ"

    ' 要素の追加その2
    dict.Add("china", "中国")
    dict.Add("india", "インド")

    '''''''''''''''''''''''''''''''''''''''
    ' 値の取得その1
    Dim val As String = dict("japan")
    Console.WriteLine(val) ' 出力:日本

    ' Dim sss As String = dict("russia") ' 例外発生

    ' 値の取得その2
    Dim value As String = ""
    If dict.TryGetValue("america", value)
      Console.WriteLine(value) ' 出力:アメリカ
    End If

    '''''''''''''''''''''''''''''''''''''''
    ' キーの列挙
    For Each key As String In dict.Keys
      Console.WriteLine(key)
    Next
    ' 出力例:
    ' japan
    ' america
    ' china
    ' india

    ' 値の列挙
    For Each v As String in dict.Values
      Console.WriteLine(v)
    Next
    ' 出力例:
    ' 日本
    ' アメリカ
    ' 中国
    ' インド

    '''''''''''''''''''''''''''''''''''''''
    ' キーの存在チェック
    If Not dict.ContainsKey("france")
      ' 存在しない場合
      dict("france") = "フランス"
    End If

    ' 値の存在チェック
    Console.WriteLine(dict.ContainsValue("日本")) ' 出力:True

    '''''''''''''''''''''''''''''''''''''''
    ' 項目(キーと値)の列挙
    For Each kvp As KeyValuePair(Of String, String) In dict
      Console.WriteLine("{0} : {1}", kvp.Key, kvp.Value)
    Next
    ' 出力例:
    ' japan : 日本
    ' america : アメリカ
    ' china : 中国
    ' india : インド
    ' france : フランス

    '''''''''''''''''''''''''''''''''''''''
    ' ソート済みのハッシュテーブルの利用

    Dim sdict As New SortedDictionary(Of string, string)(dict)

    For Each kvp As KeyValuePair(Of String, String) In sdict
      Console.WriteLine("{0} : {1}", kvp.Key, kvp.Value)
    Next
    ' 出力例:
    ' america : アメリカ
    ' china : 中国
    ' france : フランス
    ' india : インド
    ' japan : 日本
  End Sub
End Class

' コンパイル方法:vbc dictionary.vb

ハッシュテーブルを使用したサンプルプログラム(上:C#、下:VB)
C#版サンプルプログラムのダウンロード
VB版サンプルプログラムのダウンロード

 ちなみに.NET Framework 2.0のクラスライブラリには、キーによりコレクションの要素が自動的に並べ替えられるSortedDictionaryジェネリッククラス(System.Collections.Generic名前空間)も用意されている。

 上記のサンプルプログラムの最後の部分では、すでに存在するハッシュテーブルを基にこのクラスのインスタンスを作成し、その全要素を列挙している。その出力例から各項目はキーによりソートされているのが分かる。

ソート、リスト化、逆引き(値からキーを検索)[.NET 3.5]

 .NET Framework 3.5で導入されたLINQを使えば、ハッシュテーブルの要素の並べ替え、リストなどへの変換、および、逆引きが簡単に書ける(次のコード、C#のみ示すがVBでも同様にできる)。

// ソースコード冒頭に次の宣言が必要
// using System;
// using System.Collections.Generic;
// using System.Linq;

var dict = new Dictionary<string, string> {
  { "japan", "日本" }, { "america", "アメリカ" },
  { "china", "中国" }, { "india", "インド" },
};
foreach (var kv in dict)
  Console.WriteLine($"{kv.Key}:{kv.Value}");
// 出力例:
// japan:日本
// america:アメリカ
// china:中国
// india:インド

// キーでソート
var sorted1 = dict.OrderBy(kv => kv.Key);
foreach (var kv in sorted1)
  Console.WriteLine($"{kv.Key}:{kv.Value}");
// 出力:
// america:アメリカ
// china:中国
// india:インド
// japan:日本

// 値でソート
var sorted2 = dict.OrderBy(kv => kv.Value);
foreach (var kv in sorted2)
  Console.WriteLine($"{kv.Key}:{kv.Value}");
// 出力:
// america:アメリカ
// india:インド
// china:中国
// japan:日本

// 値のリスト化(同様にしてキーのリスト化も可能)
List<string> valueList = dict.Select(kv => kv.Value).ToList();
for (int i = 0; i < valueList.Count; i++)
  Console.WriteLine($"{i}:{valueList[i]}");
// 出力例:
// 0:日本
// 1:アメリカ
// 2:中国
// 3:インド

// 値が"日本"のキーを逆引き
var kvp = dict.FirstOrDefault(kv => kv.Value == "日本");
Console.WriteLine($@"値が""{kvp.Value}""である最初のキーは""{kvp.Key}""");
// 出力:
// 値が"日本"である最初のキーは"japan"

LINQを使ってソートと逆引きを行う例(C# 6.0)
このコードはC# 6.0の機能も使っているため、コンパイルするにはVisual Studio 2015以降が必要である。

Dictionaryクラスをインスタンス化するとき、C# 3.0以降ではこのサンプルコードのように一緒に初期化できる。詳しくは「Dictionaryクラスを簡単に初期化するには?」をご覧いただきたい。

また、先頭に「$」記号を付けた文字列リテラルは、C# 6.0/VB 14で導入された「補間文字列」である。詳しくは「数値を右詰めや0埋めで文字列化するには?[C#、VB]」の後半部分を参照してほしい。


列挙するときにキーと値を直接取得する[.NET Core 2.0]

 ハッシュテーブル内の要素を列挙するのに、新しい環境ではKeyValuePairジェネリック構造体を介さずにキーと値を直接取得できる(次のコード)。

 これは、.NET Core 2.0以降とC# 7.0以降の組み合わせで可能となる。または、未対応の.NET Frameworkであっても、KeyValuePairジェネリック構造体に対する拡張メソッドを用意しておくことで、C# 7.0以降であれば可能である。詳しくは「Dictionaryのキー/値をforeachで簡単に扱うには?[C#/VB]」をご覧いただきたい。

// ソースコード冒頭に次の宣言が必要
// using System.Collections.Generic;
// using System.Linq;
// using static System.Console;

var dic = new Dictionary<int, string> {
                { 1, "Eureka"}, { 2, "Nadia"}, { 3, "002"},
              };
foreach (var (id, name) in dic)
  WriteLine($"{id}:{name}");
// 出力:
// 1:Eureka
// 2:Nadia
// 3:002

.NET Core 2.0ではタプルで受け取れる(C# 7.0)
Dictionaryコレクションを列挙するとき、その要素をタプルとして取り出して自動的にキーと値に分解できるので、コードとしてはこのようにキーと値を直接受け取る形に書ける。キーと値を受け取る変数名は、自由に決められる(ここでは、キーを変数idで、値を変数nameで受けている)。

利用可能バージョン:.NET Framework 2.0以降
カテゴリ:クラスライブラリ 処理対象:コレクション
使用ライブラリ:Dictionaryクラス(System.Collections.Generic名前空間)
使用ライブラリ:KeyValuePair構造体(System.Collections.Generic名前空間)
使用ライブラリ:SortedDictionaryクラス(System.Collections.Generic名前空間)
関連TIPS:ハッシュテーブル(連想配列)を使うには?
関連TIPS:Dictionaryクラスを簡単に初期化するには?[C# 3.0]
関連TIPS:ハッシュテーブル(Dictionaryクラス)を値でソートするには?
関連TIPS:要素を重複なく管理するには?(HashSetクラス編)[3.5、C#、VB]
関連TIPS:Dictionaryのキー/値をforeachで簡単に扱うには?[C#/VB]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]


更新履歴

【2018/08/08】Visual Studio 2017でコードの動作検証、図版の追加、全般的な構成の変更などを行いました。

【2006/03/31】初版公開。


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。