ここでは非ジェネリックなコレクション型System.Collections.Hashtableクラスについて解説します。 Hashtableクラスを使用するよりも、Hashtableに相当するジェネリックなコレクション型System.Collections.Generic.Dictionary<TKey, TValue>クラスを使用することを強く推奨します。

Hashtable

System.Collections.HashtableクラスはPerlやJavaScriptなどの言語で連想配列と呼ばれるものに相当します。 コレクション内の要素数を動的に増減できる点はArrayListと同じですが、キーを対(ペア)で登録することで、インデックスではなくキーによってコレクション内の要素にアクセスする点でArrayListとは異なります。 Hashtableでは、任意のobject(厳密にはGetHashCodeメソッドが一意なハッシュ値を返すオブジェクト)をキーとして指定することができます。

基本的な操作

Hashtableを使った例によって基本的な操作について見ていきます。

using System;
using System.Collections;

class Sample {
  static void Main()
  {
    Hashtable hash = new Hashtable();

    hash.Add("foo", 16); // キーを"foo"として値16を追加

    hash["bar"] = 72; // キー"bar"の値として値72を設定
    hash["baz"] = 42; // キー"baz"の値として値42を設定

    Console.WriteLine(hash["foo"]); // キー"foo"の値を取得
    Console.WriteLine(hash["bar"]); // キー"bar"の値を取得
    Console.WriteLine(hash["baz"]); // キー"baz"の値を取得

    Console.WriteLine(hash["hoge"] == null); // キー"hoge"の値を取得

    Console.WriteLine(hash[0] == null); // 数値0はインデックスではなくキーとして扱われる

    Console.WriteLine(hash.Count); // 現在のキーと値の対の数を取得
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    Dim hash As New Hashtable()

    hash.Add("foo", 16) ' キーを"foo"として値16を追加

    hash("bar") = 72 ' キー"bar"の値として値72を設定
    hash("baz") = 42 ' キー"baz"の値として値42を設定

    Console.WriteLine(hash("foo")) ' キー"foo"の値を取得
    Console.WriteLine(hash("bar")) ' キー"bar"の値を取得
    Console.WriteLine(hash("baz")) ' キー"baz"の値を取得

    Console.WriteLine(hash("hoge") Is Nothing) ' キー"hoge"の値を取得

    Console.WriteLine(hash(0) Is Nothing) ' 数値0はインデックスではなくキーとして扱われる

    Console.WriteLine(hash.Count) ' 現在のキーと値の対の数を取得
  End Sub
End Class
実行結果
16
72
42
True
True
3

この例ではキーとして文字列、値として数値を使ってHashtableを操作しています。 Hashtable内の要素を取得・設定するには、インデクサにキーを指定します。 設定の際、キーに該当するの要素が無ければ、追加されます。 取得の際、キーに該当する要素がない場合は、null(Nothing)が返されます(IndexOutOfRangeExceptionがスローされることはありません)。 また、ArrayList同様、Add, Remove, Clearなどのメソッドによってキーと値の対を追加、削除、クリアすることも出来ます。

列挙操作

Hashtableの場合も、foreach文によって要素を列挙することができますが、ArrayListなどとは異なり常にキーと値のペアが列挙されます。 Hashtableの内部では個々のペアはDictionaryEntry構造体として格納されるので、foreach文ではDictionaryEntry構造体を使います。 DictionaryEntry構造体のKeyプロパティとValueプロパティを参照することで、キーと値を参照できます。 列挙の際、必ずしも追加した順で列挙されるとは限らない点に注意が必要です。

また、Hashtableに格納されているすべてのキーはKeysプロパティ、値はValuesプロパティを通して参照できます。

using System;
using System.Collections;

class Sample {
  static void Main()
  {
    Hashtable hash = new Hashtable();

    hash["foo"] = 16;
    hash["bar"] = 72;
    hash["baz"] = 42;

    // Hashtableに格納されているキーと値のペアを列挙
    foreach (DictionaryEntry entry in hash) {
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value);
    }

    // Hashtableに格納されているすべてのキーを列挙
    Console.WriteLine("[Keys]");

    foreach (object key in hash.Keys) {
      Console.WriteLine(key);
    }

    // Hashtableに格納されているすべての値を列挙
    Console.WriteLine("[Values]");

    foreach (object val in hash.Values) {
      Console.WriteLine(val);
    }
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    Dim hash As New Hashtable()

    hash("foo") = 16
    hash("bar") = 72
    hash("baz") = 42

    ' Hashtableに格納されているキーと値のペアを列挙
    For Each entry As DictionaryEntry In hash
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value)
    Next

    ' Hashtableに格納されているすべてのキーを列挙
    Console.WriteLine("[Keys]")

    For Each key As Object In hash.Keys
      Console.WriteLine(key)
    Next

    ' Hashtableに格納されているすべての値を列挙
    Console.WriteLine("[Values]")

    For Each val As Object In hash.Values
      Console.WriteLine(val)
    Next
  End Sub
End Class
実行結果
foo => 16
baz => 42
bar => 72
[Keys]
foo
baz
bar
[Values]
16
42
72

追加した順序を維持した状態で列挙したい場合はOrderedDictionaryクラスを使うことができます。

配列へのコピー

列挙の場合と同様、CopyToメソッドでHashtableの内容を配列にコピーする場合も、DictionaryEntry構造体としてコピーされます。 キーのみをコピーしたい場合はKeysプロパティのCopyTo、値のみをコピーしたい場合はValuesプロパティのCopyToメソッドを使う必要があります。 当然ながら、キー・値の型とコピー先配列の型が合わない場合はInvalidCastExceptionがスローされます。

using System;
using System.Collections;

class Sample {
  static void Main()
  {
    Hashtable hash = new Hashtable();

    hash["foo"] = 16;
    hash["bar"] = 72;
    hash["baz"] = 42;

    // Hashtableのすべてのペアを配列にコピー
    DictionaryEntry[] entries = new DictionaryEntry[hash.Count];

    hash.CopyTo(entries, 0);

    for (int i = 0; i < entries.Length; i++) {
      Console.WriteLine("Entries[{0}]: {1} => {2}", i, entries[i].Key, entries[i].Value);
    }

    // Hashtableのすべてのキーを配列にコピー
    string[] keys = new string[hash.Count];

    hash.Keys.CopyTo(keys, 0);

    for (int i = 0; i < keys.Length; i++) {
      Console.WriteLine("Keys[{0}]: {1}", i, keys[i]);
    }

    // Hashtableのすべての値を配列にコピー
    int[] values = new int[hash.Count];

    hash.Values.CopyTo(values, 0);

    for (int i = 0; i < values.Length; i++) {
      Console.WriteLine("Values[{0}]: {1}", i, values[i]);
    }
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    Dim hash As New Hashtable()

    hash("foo") = 16
    hash("bar") = 72
    hash("baz") = 42

    ' Hashtableのすべてのペアを配列にコピー
    Dim entries(hash.Count - 1) As DictionaryEntry

    hash.CopyTo(entries, 0)

    For i As Integer = 0 To entries.Length - 1
      Console.WriteLine("Entries[{0}]: {1} => {2}", i, entries(i).Key, entries(i).Value)
    Next

    ' Hashtableのすべてのキーを配列にコピー
    Dim keys(hash.Count - 1) As String

    hash.Keys.CopyTo(keys, 0)

    For i As Integer = 0 To keys.Length - 1
      Console.WriteLine("Keys[{0}]: {1}", i, keys(i))
    Next

    ' Hashtableのすべての値を配列にコピー
    Dim values(hash.Count - 1) As Integer

    hash.Values.CopyTo(values, 0)

    For i As Integer = 0 To values.Length - 1
      Console.WriteLine("Keys[{0}]: {1}", i, values(i))
    Next
  End Sub
End Class
実行結果
Entries[0]: foo => 16
Entries[1]: baz => 42
Entries[2]: bar => 72
Keys[0]: foo
Keys[1]: baz
Keys[2]: bar
Values[0]: 16
Values[1]: 42
Values[2]: 72

ContainsKey, ContainsValue

Hashtableに指定したキーを持つ要素が格納されているかどうかを調べるにはContainsKeyメソッド、値が格納されているか調べるにはContainsValueメソッドを使います。

using System;
using System.Collections;

class Sample {
  static void Main()
  {
    Hashtable hash = new Hashtable();

    hash["foo"] = 16;
    hash["bar"] = 72;
    hash["baz"] = 42;

    // キー"foo"を持つ要素があるかどうか
    Console.WriteLine(hash.ContainsKey("foo"));

    // キー"hoge"を持つ要素があるかどうか
    Console.WriteLine(hash.ContainsKey("hoge"));

    // 値16を持つ要素があるかどうか
    Console.WriteLine(hash.ContainsValue(16));

    // 値9を持つ要素があるかどうか
    Console.WriteLine(hash.ContainsValue(9));
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    Dim hash As New Hashtable()

    hash("foo") = 16
    hash("bar") = 72
    hash("baz") = 42

    ' キー"foo"を持つ要素があるかどうか
    Console.WriteLine(hash.ContainsKey("foo"))

    ' キー"hoge"を持つ要素があるかどうか
    Console.WriteLine(hash.ContainsKey("hoge"))

    ' 値16を持つ要素があるかどうか
    Console.WriteLine(hash.ContainsValue(16))

    ' 値9を持つ要素があるかどうか
    Console.WriteLine(hash.ContainsValue(9))
  End Sub
End Class
実行結果
True
False
True
False

なお、IDictionaryインターフェイスのメソッドを実装したContainsメソッドも用意されていますが、これとContainsKeyの動作は全く同じです。

キー比較のカスタマイズ

コンストラクタで適切なIEqualityComparerインターフェイスを指定することで、Hashtableのキー比較時の動作をカスタマイズ出来ます。 例えば、文字列をキーとした場合に大文字小文字を無視するようにするといったことが出来ます。 以下は、StringComparerクラスを使って、大文字小文字を無視するHashtableを作成する例です。

using System;
using System.Collections;

class Sample {
  static void Main()
  {
    // 大文字小文字の違いを無視しないHashtableを作成
    // (キーの比較にStringComparer.CurrentCultureを使用)
    Hashtable caseSensitiveHash = new Hashtable(StringComparer.CurrentCulture);

    caseSensitiveHash["foo"] = 1;
    caseSensitiveHash["bar"] = 2;
    caseSensitiveHash["FOO"] = 3;
    caseSensitiveHash["BAR"] = 4;

    // 大文字小文字の違いを無視するHashtableを作成
    // (キーの比較にStringComparer.CurrentCultureIgnoreCaseを使用)
    Hashtable caseInsensitiveHash = new Hashtable(StringComparer.CurrentCultureIgnoreCase);

    caseInsensitiveHash["foo"] = 1;
    caseInsensitiveHash["bar"] = 2;
    caseInsensitiveHash["FOO"] = 3;
    caseInsensitiveHash["BAR"] = 4;

    Console.WriteLine("caseSensitiveHash");

    foreach (DictionaryEntry entry in caseSensitiveHash) {
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value);
    }

    Console.WriteLine("caseInsensitiveHash");

    foreach (DictionaryEntry entry in caseInsensitiveHash) {
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value);
    }
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    ' 大文字小文字の違いを無視しないHashtableを作成
    ' (キーの比較にStringComparer.CurrentCultureを使用)
    Dim caseSensitiveHash As New Hashtable(StringComparer.CurrentCulture)

    caseSensitiveHash("foo") = 1
    caseSensitiveHash("bar") = 2
    caseSensitiveHash("FOO") = 3
    caseSensitiveHash("BAR") = 4

    ' 大文字小文字の違いを無視するHashtableを作成
    ' (キーの比較にStringComparer.CurrentCultureIgnoreCaseを使用)
    Dim caseInsensitiveHash As New Hashtable(StringComparer.CurrentCultureIgnoreCase)

    caseInsensitiveHash("foo") = 1
    caseInsensitiveHash("bar") = 2
    caseInsensitiveHash("FOO") = 3
    caseInsensitiveHash("BAR") = 4

    Console.WriteLine("caseSensitiveHash")

    For Each entry As DictionaryEntry In caseSensitiveHash
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value)
    Next

    Console.WriteLine("caseInsensitiveHash")

    For Each entry As DictionaryEntry In caseInsensitiveHash
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value)
    Next
  End Sub
End Class
実行結果
caseSensitiveHash
BAR => 4
bar => 2
foo => 1
FOO => 3
caseInsensitiveHash
bar => 4
foo => 3