Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


C# von Eric Gunnerson
Die neue Sprache für Microsofts .NET-Plattform
C# - Zum Katalog
gp Kapitel 28 Freunde finden mit den .NET-Frameworks
  gp 28.1 Was alle Objekte tun
  gp 28.2 Hashes und GetHashCode()

Kapitel 28 Freunde finden mit den .NET-Frameworks

Die Informationen der vorangegangenen Kapitel reichen aus, um Objekte zu schreiben, die in der .NET-Laufzeitumgebung funktionieren, diese Objekte eignen sich jedoch nicht besonders für das Framework. In diesem Kapitel wird erläutert, wie benutzerdefinierte Objekte so geschrieben werden, dass sie sich gut in die .NET-Laufzeitumgebung und in die Frameworks einfügen.


Galileo Computing

28.1 Was alle Objekte tun  downtop

Das Außerkraftsetzen der ToString()-Funktion der object-Klasse führt zu einer Darstellung der Werte in einem Objekt. Andernfalls gibt object.ToString() lediglich den Namen der Klasse zurück.

Die Equals()-Funktion für object wird durch die .NET-Frameworks-Klassen aufgerufen, um zu bestimmen, ob zwei Objekte gleich sind.

Eine Klasse kann auch operator==() und operator!=() außer Kraft setzen, um dem Benutzer statt eines Aufrufs von Equals() die Verwendung integrierter Operatoren für Objektinstanzen zu ermöglichen.


Galileo Computing

28.1.1 ToString  downtop

Hier ein Beispiel dazu, was normalerweise geschieht:

using System;
public class Employee
{
    public Employee(int id, string name)
    {
        this.id = id;
        this.name = name;
    }
    int id;
    string name;
}
class Test
{
    public static void Main()
    {
        Employee herb = new Employee(555, "Herb");
        Console.WriteLine("Employee: {0}", herb);
    }
}

Dieser Code erzeugt die folgende Ausgabe:

Employee: Employee

Durch Überladen von ToString() kann die Darstellung verbessert werden:

using System;
public class Employee
{
    public Employee(int id, string name)
    {
        this.id = id;
        this.name = name;
    }
    public override string ToString()
    {
        return(String.Format("{0}({1})", name, id));
    }
    int id;
    string name;
}
class Test
{
    public static void Main()
    {
        Employee herb = new Employee(555, "Herb");
        Console.WriteLine("Employee: {0}", herb);
    }
}

So ist das Ergebnis sehr viel brauchbarer:

Employee: Herb(555)

Wenn Console.WriteLine() ein Objekt in eine Zeichenfolgendarstellung konvertieren muss, wird die virtuelle Funktion ToString() aufgerufen, die eine Weiterleitung an die spezifische Objektimplementierung vornimmt. Ist eine feinere Steuerung der Formatierung gewünscht, beispielsweise die Implementierung einer Gleitkommaklasse mit unterschiedlichen Formaten, kann die Schnittstelle IFormattable außer Kraft gesetzt werden. IFormattable wird in Kapitel 30, Überblick über die .NET-Frameworks, im Abschnitt »Benutzerdefinierte Objektformatierung« besprochen.


Galileo Computing

28.1.2 Equals  downtop

Mit der Funktion Equals() wird bestimmt, ob zwei Objekte über den gleichen Inhalt verfügen. Diese Funktion wird durch die Auflistungsklassen aufgerufen (z. B. Array oder Hashtable), um zwei Objekte miteinander zu vergleichen. Nachfolgend wird das Mitarbeiterbeispiel erweitert:

using System;
public class Employee
{
    public Employee(int id, string name)
    {
        this.id = id;
        this.name = name;
    }
    public override string ToString()
    {
        return(name + "(" + id + ")");
    }
    public override bool Equals(object obj)
    {
        Employee emp2 = (Employee) obj;
        if (id != emp2.id)
            return(false);
        if (name != emp2.name)
            return(false);
        return(true);
    }
    public static bool operator==(Employee emp1, Employee emp2)
    {
        return(emp1.Equals(emp2));
    }
    public static bool operator!=(Employee emp1, Employee emp2)
    {
        return(!emp1.Equals(emp2));
    }
    int id;
    string name;
}
class Test
{
    public static void Main()
    {
        Employee herb = new Employee(555, "Herb");
        Employee herbClone = new Employee(555, "Herb");
        Console.WriteLine("Equal: {0}", herb.Equals(herbClone));
        Console.WriteLine("Equal: {0}", herb == herbClone);
    }
}

Dieser Code erzeugt die folgende Ausgabe:

Equal: true
Equal: true

In diesem Fall wurden operator==() und operator!=() ebenfalls überladen, wodurch in der letzten Zeile von Main() die Operatorsyntax eingesetzt werden kann. Diese Operatoren müssen paarweise überladen werden; eine separate Überladung ist nicht möglich.


Galileo Computing

28.2 Hashes und GetHashCode()  toptop

Die Frameworks schließen Hashtable-Klassen ein, die für das schnelle Auffinden von Objekten über einen Schlüssel äußerst nützlich sind. Eine Hashtabelle funktioniert durch den Einsatz einer Hashfunktion, die einen ganzzahligen »Schlüssel« für eine bestimmte Klasseninstanz erstellt. Dieser Schlüssel ist eine komprimierte Version der Instanzeninhalte. Obwohl Instanzen den gleichen Hashcode aufweisen können, ist dies sehr unwahrscheinlich.

Eine Hashtabelle verwendet diesen Schlüssel, um die Anzahl der zu durchsuchenden Objekte drastisch zu verringern. Hierbei wird zunächst der Hashwert des Objekts abgerufen, wodurch alle Objekte mit abweichendem Hashcode ausgeschlossen werden. Es werden nur die Objekte mit gleichem Hashcode durchsucht. Da die Zahl der Instanzen mit Hashcode eher gering ist, können Suchläufe sehr schnell durchgeführt werden.

Dies ist die zugrunde liegende Idee – eine genauere Erläuterung entnehmen Sie bitte einem guten Algorithmenbuch. Hashes sind extrem hilfreich. Die Hashtable-Klasse speichert Objekte, daher können leicht beliebige Typen gespeichert werden.

Die GetHashCode()-Funktion sollte in benutzererstellten Klassen außer Kraft gesetzt werden, da die von GetHashCode() zurückgegebenen Werte mit den von Equals() zurückgegebenen Werten in Beziehung stehen müssen. Zwei Objekte, die laut Equals() gleich sind, müssen immer denselben Hashcode zurückgeben.

Die Standardimplementierung von GetHashCode() arbeitet anders und muss daher außer Kraft gesetzt werden, um eine ordnungsgemäße Funktion zu gewährleisten. Findet keine Außerkraftsetzung statt, ist der Hashcode nur für gleiche Objektinstanzen identisch, ein Suchlauf nach einem Objekt gleichen Inhalts, bei dem es sich jedoch nicht um die gleiche Instanz handelt, schlägt dagegen fehl.

Befindet sich ein eindeutiges Feld im Objekt, ist dies meistens ein guter Platz für den Hashcode:

using System;
using System.Collections;
public class Employee
{
    public Employee(int id, string name)
    {
        this.id = id;
        this.name = name;
    }
    public override string ToString()
    {
        return(String.Format("{0}({1})", name, id));
    }
    public override bool Equals(object obj)
    {
        Employee emp2 = (Employee) obj;
        if (id != emp2.id)
            return(false);
        if (name != emp2.name)
            return(false);
        return(true);
    }
    public static bool operator==(Employee emp1, Employee emp2)
    {
        return(emp1.Equals(emp2));
    }
    public static bool operator!=(Employee emp1, Employee emp2)
    {
        return(!emp1.Equals(emp2));
    }
    public override int GetHashCode()
    {
        return(id);
    }
    int id;
    string name;
}
class Test
{
    public static void Main()
    {
        Employee herb = new Employee(555, "Herb");
        Employee george = new Employee(123, "George");
        Employee frank = new Employee(111, "Frank");
        Hashtable    employees = new Hashtable();
        employees.Add(herb, "414 Evergreen Terrace");
        employees.Add(george, "2335 Elm Street");
        employees.Add(frank, "18 Pine Bluff Road");
        Employee herbClone = new Employee(555, "Herb");
        string address = (string) employees[herbClone];
        Console.WriteLine("{0} lives at {1}", herbClone, address);
    }
}

In der Employee-Klasse ist das id-Mitglied eindeutig und wird daher für den Hashcode verwendet. In der Main()-Funktion werden verschiedene Mitarbeiter erstellt. Diese werden anschließend als Schlüsselwerte zur Speicherung der Mitarbeiteradressen verwendet.

Ist kein eindeutiger Wert vorhanden, könnte der Hashcode aus den in einer Funktion enthaltenen Werten erstellt werden. Würde die Mitarbeiterklasse nicht über einen eindeutigen Bezeichner, sondern über Felder für Name und Adresse verfügen, könnten diese von der Hashfunktion eingesetzt werden. Es könnte beispielsweise die folgende Hashfunktion verwendet werden:

using System;
using System.Collections;
public class Employee
{
    public Employee(string name, string address)
    {
        this.name = name;
        this.address = address;
    }
    public override int GetHashCode()
    {
        return(name.GetHashCode() + address.GetHashCode());
    }
    string name;
    string address;
}

Bei dieser Implementierung von GetHashCode() wird der Hashcode der Elemente einfach zusammengefügt und zurückgegeben.






1    Diese Anforderung hat zwei Gründe. Erstens kann ein Benutzer bei Verwendung von == davon ausgehen, dass != ebenfalls funktioniert. Zweitens werden so Typen unterstützt, die einen null-Wert tragen können und für die a = b nicht !(a !=b) impliziert.

2    Ich kann als Einführung »Algorithmen in C« von Robert Sedgewick empfehlen.

3    Dies heißt keinesfalls, dass dies die einzige Funktion ist, die verwendet werden kann, oder dass es sich um eine besonders gute Funktion handelt. Informationen zum Erstellen wirklich guter Hashfunktionen finden Sie in einem Algorithmenhandbuch.

   

Select * from SQL Server 2000




Copyright © Galileo Press GmbH 2001 - 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de