Richtlinien für das Überschreiben von Equals und GetHashCode (Teil 2)
Kommentare

Unveränderlicher Objektzustand
Weiter oben wurden vier Richtlinien erläutert, die es gilt, bei der Implementierung der GetHashCode-Methode einzuhalten. Die ersten drei Punkte sind in der Regel problemlos

Unveränderlicher Objektzustand

Weiter oben wurden vier Richtlinien erläutert, die es gilt, bei der Implementierung der GetHashCode-Methode einzuhalten. Die ersten drei Punkte sind in der Regel problemlos umsetzbar, die letzte Regel dagegen Bedarf jedoch ein wenig mehr Überlegung. Zunächst darf sich laut Definition der Hash-Wert eines Objekts nicht ändern. Er ändert sich jedoch dann, wenn sich der Objektzustand ändert und von der Änderung interne Objektwerte betroffen sind, die zur Bestimmung der Objektgleichheit (Equals-Methode) und somit zur Berechnung des Hash-Werts verwendet wurden. Eine Änderung des Hash-Werts führt allerdings zu Problemen, wenn ein Objekt bereits einer Hashtable als Schlüssel hinzugefügt wurde, denn eine nachträgliche Modifizierung bewirkt, dass das Objekt nicht mehr aus der Hashtable ermittelt werden kann (Listing 3). Zunächst werden zwei Personenobjekte erzeugt und einer Hashtable hinzugefügt, wobei die Personenobjekte als Schlüssel verwendet werden. Danach wird bei einer Person der Vorname (FirstName) modifiziert. Nach dieser Änderung kann das Objekt nicht mehr aus der Hashtable ermittelt werden. Die Einhaltung des unveränderlichen Objektzustands ist innerhalb von fachlichen Anwendungen oft schwer umsetzbar, da in der Regel eigene Geschäftsobjekte ständig modifiziert werden. Jedoch treten die beschriebenen Probleme nur auf, wenn sich das Objekt in einer Hashtable befindet. Zur Lösung des Problems und zur Sicherstellung des unveränderlichen Objektzustands existieren mehrere Möglichkeiten. Die .NET-Klasse String beispielsweise gibt bei jeder Modifizierung eine neue Instanz zurück, somit bleibt die ursprüngliche Instanz unverändert. Innerhalb eigener Objekte kann z. B. ein ReadOnly Flag verwendet werden, das die Modifizierung von Objekteigenschaften unterbindet, solange sich das Objekt in einer Hashtable befindet (Listing 4). Innerhalb von Geschäftsobjekten kann z. B. auch nur die eindeutige (Datenbank-)ID verwendet werden, um Objekte mittels Equals und GetHashCode auf Gleichheit zu prüfen (Listing 5). Die Methoden GetHashCode und Equals aus der Personenklasse (Listing 1) prüfen lediglich die eindeutige UniqueID des Objekts. Dabei sollte die entsprechende ID nur über den Konstruktor übergeben und die zugehörige Eigenschaft als schreibgeschützt festgelegt werden.

Listing 3

public static void ExampleHash1() {
  Person person1 = new Person() {
    UniqueID = 1,FirstName = "Hans", LastName = "Schmitt" };
  Person person2 = new Person() {
    UniqueID = 2, FirstName = "Julia", LastName = "Schmitt" };
  Hashtable ht = new Hashtable();
  ht.Add(person1, new Revenue() { Year = 2010, Value = 123 });
  ht.Add(person2, new Revenue() { Year = 2010, Value = 321 });
  person2.FirstName = "Tina";
  Revenue revPer2 = ht[person2] as Revenue;
  }  

/Listing

Listing 4

class Person
{
  private bool isReadOnly = false;
  private int uniqueID;
  private string lastName;
  private string firstName;
  public string FirstName 
  {
    get {
      return this.firstName;
    }
    set {
      if (!isReadOnly) this.firstName = value;
      else throw new Exception("Objektzustand derzeit nicht änderbar!");
    }
  }
  /*Eigenschaften: Lastname und UniqueID analaog FirstName */
  public override bool Equals(object obj) /* siehe Listing 2 */
  public override int GetHashCode() {
    isReadOnly = true;
    return this.lastName.GetHashCode() ^ this.firstName.GetHashCode()
           ^ this.uniqueID.GetHashCode();
  }
  public void Reset() {
    isReadOnly = false;
  }
}  

/Listing

Listing 5

public override bool Equals(object obj)
{
  if (obj == null)
    return false;
  if ((obj as Person) == null)
    return false;
  if (Object.ReferenceEquals(this, obj))
    return true;
  Person toCompare = obj as Person;
  return toCompare.UniqueID == this.UniqueID;
}

public override int GetHashCode()
{
  return this.UniqueID.GetHashCode();
}  

/Listing

Der Hash-Algorithmus

Bei der Implementierung der GetHashCode-Methode sind darüber hinaus noch einige Punkte zu beachten. Wie in dem Beispiel aus Listing 2 gezeigt, können die Hash-Werte der enthaltenen Objekteigenschaften verwendet werden. Auf die Verwendung der GetHashCode-Methode der Wurzelobjekte (System.Object und Valuetype) sollte jedoch aus Performancegründen verzichtet werden. Wie deutlich wurde, sollten zur Berechnung nur unveränderliche Felder verwendet werden. Es ist darauf zu achten, dass der eigene Algorithmus bei gleichen Eingangswerten jeweils das gleiche Ergebnis liefert. Um die Performance der Anwendung nicht zu beinträchtigen, muss der Algorithmus so schnell wie möglich ein Ergebnis zurückliefern.

Hash-Werten nicht dauerhaft vertrauen

Der Hash-Wert der GetHashCode-Methode eines Objekts bleibt nur innerhalb einer Programminstanz gleich. Bei einem erneuten Programmstart kann sich der Hash-Wert ändern. Ebenfalls weist Microsoft darauf hin, dass sich die interne Berechnung des Hash-Werts innerhalb des .NET Frameworks ändern kann und somit bei zukünftigen .NET-Versionen andere Werte errechnet werden könnten. Aus diesen Gründen sollte der Hash-Wert nicht für eigene Zwecke gespeichert und ausgewertet werden.

Zusammenfassung

Die richtige und korrekte Implementierung der Equals– sowie GetHashCode-Methode spielt eine wichtige Rolle, damit eigene Objekte reibungslos mit dem .NET Framework interagieren können.  Es ist allerdings nicht immer zwingend notwendig, die Methoden zu überschreiben. Die Methoden sollten für eigene fachliche (Entitäts-)Klassen überschrieben werden. Generell müssen die Methoden überschrieben werden, sobald eigene Objekte als Schlüssel innerhalb einer Hash-basierten Auflistungsklasse verwendet werden sollen.

Marc André Zhou arbeitet als Senior Consultant bei der Logica Deutschland GmbH & Co. KG. Seine Schwerpunkte liegen im Bereich Softwarearchitekturen und Frameworks, hier hauptsächlich im .NET-Umfeld.
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -