ここでは非ジェネリックなコレクション型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