Kolumne: C# im Fokus

Richtlinien für das Überschreiben von Equals und GetHashCode
Kommentare

In der letzten C#-Kolumne wurden Regeln und Richtlinien für die korrekte Implementierung der Methode Equals aufgezeigt. In der heutigen Ausgabe dreht sich alles um die richtige Umsetzung der GetHashCode-Methode.

In der C#-Kolumne, in der Ausgabe 11.2010 des dot.NET Magazins, wurde die Equals-Methode im Detail betrachtet. Wie erläutert wurde, ist die korrekte Implementierung der Methode wichtig und darf nicht vernachlässigt werden. Neben der Equals-Methode spielt auch die GetHashCode-Methode eine zentrale Rolle, und auch hier gilt es, diese .NET-konform zu überschreiben. Wie im Folgenden demonstriert wird, kann andernfalls keine oder nicht vollständige Überschreibung der Methode zu unerwünschten und falschen Ergebnissen führen. Generell sollten immer beide Methoden paarweise überschrieben werden, d. h. sobald eine Methode angepasst wurde, muss auch die verbleibende Methode entsprechend überschrieben werden.

„Equals“ und „GetHashCode“ im Doppelpack

Die Einhaltung dieser Grundsatzregel wird schon vom Compiler überprüft, der bei einem Verstoß gegen diese Regeln die Warnung ‚ClassX‘ overrides Object.Equals(object o) but does not override Object.GetHashCode() ausgibt. Dies liegt in der Art und Weise begründet, wie die Methoden im .NET Framework verwendet werden. Beide Methoden kommen zum Einsatz, wenn die Objekte als Schlüssel für eine Hashtable-Auflistung verwendet werden. Sind dabei die Methoden Equals und GetHashCode nicht richtig implementiert, können die Objekte nicht als Schlüsselwerte verwendet werden. Intern verwenden die Klassen System.Collections.Hashtable und System.Collection.Generic.Dictionary diese Methoden, um Objekte zu identifizieren. Die beiden Klassen gehen davon aus, dass wenn zwei Objekte gleich sind, diese auch den gleichen Hash-Wert besitzen. Ergibt also die Prüfung mittels Equals für zwei Objekte true, muss auch der Hash-Wert beider Objekte übereinstimmen. Wird diese Regel nicht eingehalten, können verschiedene Objekte innerhalb einer Hashtable nicht eindeutig identifiziert und gefunden werden (Listing 1). Die Klasse Person überschreibt die beiden Methoden Equals und GetHashCode. Die Equals-Methode verwendet zum Vergleich lediglich die Eigenschaft LastName, die GetHashCode-Methode nutzt die gleiche Eigenschaft, um daraus einen Hash-Wert zu ermitteln. In der Methode ExampleHash1 werden dann zwei Objekte der Personen-Klasse erzeugt und einer Hashtable hinzugefügt. Bei dem Versuch, das zweite Objekt der Hashtable hinzuzufügen, kommt es allerdings zu einer Ausnahme vom Typ System.ArgumentException, mit der Meldung Das Element wurde bereits hinzugefügt, da der Schlüssel schon in der Hashtable-Instanz vorhanden ist.

Implementierungsrichtlinien für „GetHashCode“

Wie auch für die Equals-Methode existieren einige Richtlinien, die eingehalten werden müssen, um später Probleme bei der Verwendung der eigenen Klassen zu vermeiden:

  1. Zur Bestimmung des Hash-Werts muss mindestens eine Eigenschaft des Objekts verwendet werden.
  2. Sind zwei Objekte gemäß der Equals-Methode gleich, so müssen beide Objekte auch den gleichen Hash-Wert zurückliefern.
  3. Sind zwei Objekte gemäß der Equals-Methode nicht gleich, können die Objekte dennoch den gleichen Hash-Wert besitzen.
  4. Die GetHashCode-Methode muss innerhalb einer laufenden Programminstanz für ein Objekt bei jedem Aufruf den gleichen Wert zurückliefern. Bei einem erneuten Programmstart kann einmalig ein neuer Hash-Wert errechnet und zurückgegeben werden. Der Hash-Wert kann sich jedoch innerhalb einer Anwendung ändern, sobald der interne Objektzustand verändert wird. Das heißt, werden Eigenschafts- oder Variablenwerte verändert, die innerhalb der Equals-Methode verwendet werden, muss sich zwangsläufig der Hash-Wert des Objekts ändern.

Am einfachsten können diese Regeln eingehalten werden, wenn zur Bestimmung des Hash-Werts die gleichen Objekteigenschaften verwendet werden, die auch bei der Equals-Methode zur Anwendung kommen.

Anwendung der Regeln

Listing 2 zeigt die Methoden GetHashcode und Equals der Personenklasse aus Listing 1, angepasst an die im vorherigen Abschnitt erläuterten Richtlinien. Die Equals-Methode wertet alle Felder aus, um die Gleichheit zweier Objekte zu bestimmen. Genau die gleichen Felder werden innerhalb der GetHashCode-Methode verwendet, um den Hash-Wert zu ermitteln. Wie anhand der Implementierung erkennbar ist, wurde die GetHashCode-Methode der enthaltenen Variablen verwendet, um den gesamten Hash-Wert des Objekts zu berechnen. Dabei werden die einzelnen Hash-Werte allerdings nicht einfach summiert, sondern per XOR-Verknüpfung kombiniert, da so eine bessere Verteilung der Hash-Werte erreicht wird.

Listing 1

class Person
{
  public int UniqueID { get; set; }
  public string LastName { get; set; }
  public string FirstName { get; set; }

  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.LastName.Equals(this.LastName);
  }
  public override int GetHashCode() {
    return this.LastName.GetHashCode();
  }
}
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 });  
}  

/Listing

Listing 2

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
          && toCompare.FirstName.Equals(this.FirstName)
          && toCompare.LastName.Equals(this.LastName)); }
public override int GetHashCode() {
  return this.LastName.GetHashCode() 
          ^ this.FirstName.GetHashCode()
          ^ this.UniqueID.GetHashCode();
}  

/Listing

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -