## 8.3 Die Klasse »Hashtable« IList-Auflistungen verwalten ihre Elemente über Indizes. Dieses Konzept hat einen Nachteil: Wenn man nach einem bestimmten Element sucht und dessen Position nicht kennt, muss man die Liste so lange durchlaufen, bis man eine Übereinstimmung findet. Enthält die Auflistung sehr viele Einträge, kann das sehr zeitaufwendig sein und kostet Rechenleistung. Kommt es nicht auf die Reihenfolge der Elemente an, kann man sich für eine Auflistung entscheiden, die das Interface IDictionary implementiert. Zu dieser Gruppe gehört die Klasse Hashtable, die in diesem Abschnitt vorgestellt wird. In IDictionary-Auflistungen kann ein bestimmtes Element zwar schnell gefunden werden, allerdings muss man dabei in Kauf nehmen, keinen Einfluss auf die Positionierung der Elemente in der Liste zu haben, denn die Elemente werden in einer für sie passenden Reihenfolge sortiert. ### 8.3.1 Methoden und Eigenschaften der Schnittstelle »IDictionary« Die meisten der von IDictionary veröffentlichten Methoden sind uns bereits von der Schnittstelle IList her bekannt. Das erleichtert zwar einerseits die Einarbeitung, zwingt uns aber andererseits dennoch in einigen Fällen zu einer etwas genaueren Betrachtung. Jeder Listeneintrag in einer IDictionary-Auflistung wird durch ein Schlüssel-Werte-Paar beschrieben, was sich in der Parameterliste der Add-Methode niederschlägt:
    void Add(object key, object value);
Der erste Parameter wird als Schlüssel für das hinzuzufügende Element verwendet und sorgt für die Identifizierbarkeit innerhalb einer Liste. Der zweite Parameter ist die Referenz auf das hinzuzufügende Element. Wir stoßen hier zum ersten Mal auf die Tatsache, dass von IDictionary-Auflistungen anstelle eines Index ein Schlüssel verwendet wird. Der Schlüssel begleitet uns durch alle Methoden und wird auch von Remove verwendet, um ein Objekt aus der Auflistung zu entfernen:
    void Remove(object key);
Da IDictionary-Objekte nicht über Indizes verwaltet werden, brauchen nach dem Löschen eines Elements etwaige Folgeelemente auch keine Lücke zu schließen. Dem Indexer kommt nicht nur die Aufgabe zu, unter der Angabe des Schlüssels den Zugriff auf das gewünschte Element zu gewährleisten, vielmehr kann er auch dazu benutzt werden, den Wert eines Objekts zu verändern.
    object this[object key] {get; set;}
Gibt man einen Schlüssel an, der sich noch nicht in der Auflistung befindet, wird das Element hinzugefügt. Dabei bleibt der Wert leer, ist also null, was durchaus zulässig ist. Die Schlüssel und die Werte werden in eigenen Auflistungen verwaltet. Die Referenz auf diese internen Auflistungen liefern die Eigenschaften Keys und Values.
    ICollection Keys {get;}
    ICollection Values {get;}
Mit Clear kann eine IDictionary-Auflistung geleert werden, und mit Contains können Sie prüfen, ob ein bestimmter Schlüssel bereits in der Liste enthalten ist. Um nach einem Element in einer IDictionary-Auflistung zu suchen, wird eine Schlüsselinformation benötigt, der ein Wert zugeordnet ist. IDictionary-Auflistungen enthalten Elemente mit Schlüssel-Wert-Kombinationen. Der Schlüssel muss eindeutig sein und darf nicht den Inhalt null haben. ### 8.3.2 Beispielprogramm zur Klasse »Hashtable« Die wichtigste Auflistung, die das IDictionary-Interface implementiert, wird von der Klasse Hashtable beschrieben. Im folgenden Beispiel wird eine Hashtabelle verschiedene Objekte vom Typ Artikel verwalten. Für die wichtigsten Eigenschaften und Methoden einer Hashtable werden in diesem Beispielprogramm jeweils separate Methoden bereitgestellt.
    // ---------------------------------------------------------
    // Beispiel: ...\Kapitel 8\HashtableSample
    // ---------------------------------------------------------
    class Artikel {
      public int Artikelnummer { get; set; }
      public string Bezeichner { get; set; }
      public double Preis { get; set; }
      public Artikel(int artNummer, string bezeichner, double preis) {
        Artikelnummer = artNummer;
        Bezeichner = bezeichner;
        Preis = preis;
      }
    }
#### Listenelemente hinzufügen Die Hashtable wird durch den Aufruf der Methode GetFilledHashtable mit mehreren Artikel-Objekten gefüllt. Im Gegensatz zur ArrayList (oder präziser ausgedrückt, zur IList) stellt Hashtable mit Add nur eine Methode zur Verfügung, um der Auflistung Objekte hinzuzufügen. Üblicherweise wird für den Schlüssel eine Zeichenfolge verwendet, obwohl der schlüsselbeschreibende erste Parameter vom Typ Object ist. Das soll auch in unserem Beispiel nicht anders sein, wir verwenden dazu den Bezeichner des Artikels.
    //  Objekte der Hashtabelle hinzufügen
    public static Hashtable GetFilledHashtable() {
      Hashtable hash = new Hashtable();
      Artikel artikel1 = new Artikel(101, "Wurst", 1.98);
      Artikel artikel2 = new Artikel(45, "Käse", 2.98);
      Artikel artikel3 = new Artikel(126, "Kuchen", 3.50);
      Artikel artikel4 = new Artikel(6, "Fleisch", 7.48);
      Artikel artikel5 = new Artikel(22, "Milch", 0.98);
      Artikel artikel6 = new Artikel(87, "Schololade", 1.29);
      hash.Add(artikel1.Bezeichner, artikel1);
      hash.Add(artikel2.Bezeichner, artikel2);
      hash.Add(artikel3.Bezeichner, artikel3);
      hash.Add(artikel4.Bezeichner, artikel4);
      hash.Add(artikel5.Bezeichner, artikel5);
      hash.Add(artikel6.Bezeichner, artikel6);
      return hash;
    }
#### Listen der Schlüssel und Werte einer Hashtable Zur Ausgabe aller Schlüsselwerte wird die Liste aller Schlüssel mit der Eigenschaft Keys abgerufen. Da wir für die Schlüssel Zeichenfolgen verwendet haben, kann die Laufvariable der Schleife vom Typ string sein.
    // Ausgabe der Schlüsselliste
    public static void GetKeyList(Hashtable hash) {
      foreach (string item in hash.Keys)
        Console.WriteLine(item);
    }
Sehr ähnlich besorgen wir uns auch die Liste aller gespeicherten Werte. Die Werteliste wird von der Hashtable durch Aufruf der Eigenschaft Values bereitgestellt. Die Einzelwerte selbst sind in unserem Beispiel Artikel-Objekte, deren Eigenschaften wir in die Konsole schreiben.
    // Ausgabe der Wertliste
    public static void GetValueList(Hashtable hash) {
      foreach (Artikel item in hash.Values)
        Console.WriteLine("{0,-4}{1,-12}{2}",
                    item.Artikelnummer, item.Bezeichner, item.Preis);
    }
#### Auf Listenelemente zugreifen Wir haben bisher ganz ausdrücklich die in der Auflistung enthaltenen Schlüssel und Werte mit den Eigenschaften Keys und Values abgefragt. Nun interessiert uns ein Listeneintrag als Ganzes. Dabei treffen wir auf ein ganz besonderes Charakteristikum einer IDictionary-Auflistung, denn die Laufvariablen der Schleifen können nicht dazu benutzt werden, auf das Listenelement zuzugreifen. Daher wird zur Laufzeit eine Ausnahme ausgelöst, wenn Sie versuchen, die Laufvariable mit
    // Achtung: Falscher Zugriff auf die Hashtable
    foreach(Artikel item in hash)
      Console.WriteLine(item.Bezeichner);
auszuwerten oder mit
    // Achtung: Falscher Zugriff auf die Hashtable
    foreach(object item in hash)
      Console.WriteLine(((Artikel)item.Bezeichner;
zu konvertieren. Um auf ein Listenelement in einer foreach-Schleife zugreifen zu können, müssen Sie die Laufvariable vom Typ DictionaryEntry deklarieren. Von diesem Typ sind die Elemente in einer Hashtable. DictionaryEntry ist eine Struktur, die das Schlüssel-Wert-Paar für einen Hashtabelleneintrag enthält. Über die Eigenschaften Key und Value können wir die notwendigen Informationen beziehen. Während uns Key nur den Schlüssel liefert, können wir über den Rückgabewert von Value nach vorheriger Typumwandlung auf das Objekt zugreifen:
    // Schlüssel-Wert-Paar über ein DictionaryEntry-Objekt ausgeben
    public static void GetCompleteList(Hashtable hash) {
      foreach (DictionaryEntry item in hash) {
        Console.Write(item.Key);
        Console.WriteLine(" - {0}", item.Value);
      }
    }
#### Prüfen, ob ein Element bereits zur Hashtable gehört Eine Hashtable dient zur Verwaltung mehrerer meist gleichartiger Objekte und hat im Vergleich zu anderen Auflistungen den Vorteil, einen sehr schnellen Zugriff über den Indexer zu ermöglichen. Manchmal interessiert auch die Antwort auf die Frage, ob in einer Hashtable bereits ein bestimmtes Element eingetragen ist. Sie können dabei so vorgehen, dass Sie entweder nach einem Schlüssel suchen oder nach einem bestimmten Wert. Beginnen wir mit der Suche nach einem Schlüssel. Hierzu können wir zwei Methoden benutzen, die gleichwertig sind: Contains und ContainsKey. Beide liefern als Resultat einen booleschen Wert zurück.
    // prüfen, ob ein bestimmter Schlüssel enthalten ist
    public static void SearchForKey(Hashtable hash) {
      string text = "\n\nGeben Sie das auszuwertende Element an: ";
      string input;
      do {
        Console.Write(text);
        input = Console.ReadLine();
        // prüfen, ob sich der Schlüssel in der Hashtabelle befindet
        if (hash.Contains(input))
          Console.WriteLine("ArtikelNr.: {0,-4} Preis: {1}",
                              ((Artikel)hash[input]).Artikelnummer,
                              ((Artikel)hash[input]).Preis);
        else
          Console.WriteLine("Nicht Element der Hashtable");
        Console.WriteLine("Zum Beenden F12 drücken ...");
      }
      while (Console.ReadKey(true).Key != ConsoleKey.F12);
    }
Nicht nur über den Schlüssel lässt sich prüfen, ob ein Element Mitglied der Hashtabelle ist. Auch über den booleschen Rückgabewert von ContainsValue ist das möglich. Hierzu dient im Beispielprogramm die benutzerdefinierte Methode SearchForValue. Dieser Methode wird neben der Referenz auf die Auflistung auch noch das Artikel-Objekt übergeben, dessen Eintrag in der Liste zu prüfen ist.
    // prüfen, ob die Liste einen bestimmten Wert enthält
    public static void SearchForValue(Hashtable hash, Artikel artikel) {
      if (hash.ContainsValue(artikel))
        Console.WriteLine("Das Objekt '{0}' ist enthalten.",
                                                artikel.Artikelnummer);
      else
        Console.WriteLine("Das Objekt '{0}' ist nicht enthalten.",
                                                artikel.Artikelnummer);
    }
#### Testen der Methoden Zum Schluss zeige ich Ihnen an dieser Stelle auch noch das Beispielprogramm, in dem die zuvor gezeigten Methoden aufgerufen werden. Am Ende des Programms wird die Methode SearchForValue aufgerufen, um nach einem bestimmten Artikel zu suchen. Dabei wird ein neues Artikel-Objekt mit Daten erzeugt, die sich bereits in der Liste befinden. Trotzdem wird zur Laufzeit festgestellt, dass das Objekt noch kein Mitglied der Liste ist. Das Ergebnis verwundert nicht, da von unserer Hashtable nach Objektreferenzen bewertet wird, und nicht nach den darin enthaltenen Daten. Hier klicken, um das Bild zu vergrößern Abbildung 8.4 Ausgabe des Beispielprogramms »HashtableSample«
    static void Main(string[] args){
      Hashtable hash = GetFilledHashtable();
      // Liste der Schlüssel ausgeben
      Console.WriteLine("===== Schlüsselliste =====");
      GetKeyList(hash);
      // Liste der Werte ausgeben
      Console.WriteLine();
      Console.WriteLine("===== Werteliste =====");
      GetValueList(hash);
      // Liste der Schlüssel und Werte ausgeben
      Console.WriteLine();
      Console.WriteLine("===== Schlüssel-Werte-Paare =====");
      GetCompleteList(hash);
      // Suchen nach einem bestimmten Schlüssel
      SearchForKey(hash);
      // Suchen nach einem bestimmten Wert
      SearchForValue(hash, new Artikel(45, "Käse", 2.98));
      Console.ReadLine();
    }