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.
28.1 Was alle Objekte tun
 
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.
28.1.1 ToString
 
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.
28.1.2 Equals
 
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öglich1 .
28.2 Hashes und GetHashCode()
 
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 Algorithmenbuch2 . 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 werden3 :
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.
|