Warum GetHashCode() wichtig ist

Frank Eller    08.07.2017    00:00

Learning

Wenn man eine eigene Klasse erstellt, überschreibt man oftmals die Equals()-Methode, um sicherzustellen dass der Inhalt der Klasse (bzw. des späteren Objekts) bei einem Vergleich herangezogen wird. Equals() ist aber nicht die einzige wichtige Komponente hierbei, genauso wichtig ist GetHashCode(). Deshalb warnt das Visual Studio auch wenn man Equals() überschreibt, GetHashCode() aber nicht. Dieser Post zeigt, warum das unter Umständen wichtig sein kann.

Die Idee zu diesem Post stammt übrigens von meinem Kollegen Robert Großmann, der auch eine erste Version des nun folgenden Benchmarks geschrieben hatte. Ein Benchmark deshalb, weil man daran genau sieht, wenn es ein Performance-Problem geben könnte. In diesem Fall machen wir etwas sehr einfaches: Wir erzeugen ein Dictionary, befüllen es mit einer vorgegebenen Anzahl von Werten, entnehmen diese Werte wieder und messen die Zeit. Die Methode für die Zeitmessung wurde allgemeingültig geschrieben, wir sehen sie hier:

private static TimeSpan CheckDictionary<K>( IList<K> keys )
{
  Dictionary<K, string> dictionary = new Dictionary<K, string>();

  Stopwatch watch = Stopwatch.StartNew();

  foreach ( K key in keys )
  {
    dictionary.Add( key, "Value" );
  }

  foreach ( K key in keys )
  {
    string currentValue = dictionary[key];
  }

  watch.Stop();

  return watch.Elapsed;
}
Die Methode ist sehr einfach gehalten. Ich übergebe hier einfach eine Liste von Keys. In der Methode wird ein Dictionary erzeugt (außerhalb der Zeitmessung), mit dem Typ des Keys und einem String als Wert. Der Wert ist uninteressant für den Benchmark, wir schreiben da also einfach hart immer den gleichen Wert hinein. Die Zeit für das Schreiben und Lesen wird gemessen und das Ergebnis zurück geliefert. die Hauptmethode der Anwendung ist daher ebenfalls denkbar einfach. Für einen ersten Test habe ich einen String als Datentyp für den Dictionary-Schlüssel verwendet.
static void Main( string[] args )
{
  // number of keys
  int[] keyCounts = { 10, 100, 1000, 10000 };

  foreach ( int count in keyCounts )
  {
    List<int> keyIds = Enumerable.Range( 1, count ).ToList();

    List<string> stringKeys = keyIds.Select( k => k.ToString() ).ToList();

    Console.WriteLine( $"Number of keys: {count}" );
    TimeSpan stringKeyTimeSpan = CheckDictionary( stringKeys );

    Console.WriteLine( $"String:\t\t{stringKeyTimeSpan}" );

    Console.WriteLine();
  }
}

Wenn man diesen Code ausführt ergibt sich folgendes Ergebnis:

Eine eigene Klasse als Key

Das erste Element das wir hinzufügen wollen ist die Verwendung einer eigenen Klasse, komplett ohne Überladungen, als Key. Die Klasse ist wie folgt definiert:
public class DefaultKey
{
  public string FirstValue { get; set; }
  public string SecondValue { get; set; }

  public DefaultKey( string firstValue, string secondValue )
  {
    FirstValue = firstValue;
    SecondValue = secondValue;
  }
}
Die Klasse DefaultKey beinhaltet keine Überladung der Vergleichsmethoden (was aber auch bedeutet, dass ein Vergleich zweier Objekte möglicherweise nicht so abläuft wie wir das gerne in der Anwendung hätten). Deshalb ist zu erwarten, dass die Performance ähnlich ist wie bei der String-Variante. Wir erweitern die Main()-Methode um das neue Element.
static void Main( string[] args )
{
  // number of keys
  int[] keyCounts = { 10, 100, 1000, 10000 };

  foreach ( int count in keyCounts )
  {
    List<int> keyIds = Enumerable.Range( 1, count ).ToList();

    List<string> stringKeys = keyIds.Select( k => k.ToString() ).ToList();
    List<DefaultKey> defaultKeys = keyIds.Select( k => new DefaultKey( k.ToString(), k.ToString() ) ).ToList();

    Console.WriteLine( $"Number of keys: {count}" );
    TimeSpan stringKeyTimeSpan = CheckDictionary( stringKeys );
    TimeSpan defaultKeyTimeSpan = CheckDictionary( defaultKeys );

    Console.WriteLine( $"String:\t\t{stringKeyTimeSpan}" );
    Console.WriteLine( $"Default:\t{defaultKeyTimeSpan}" );

    Console.WriteLine();
  }
}

Es sollte kein Problem sein, weitere Key-Varianten hinzuzufügen, im Prinzip bleibt der Code ja identisch. Deshalb werde ich in der Folge die Main()-Methode nicht nochmal posten. Führt man diesen Code aus, ergibt sich ein erwartetes Ergebnis:

Next Step: Überschreiben von Equals

Möchte man sicherstellen, dass der Vergleich zweier Objekte nach der eigenen Vorstellung passiert, überschreibt man die Equals-Methode. Hierzu habe ich eine weitere Key-Klasse erstellt. Allerdings, wie bereits in der Einleitung gesagt, wird das Visual Studio sich beschweren - wenngleich nicht mit einem Error, sondern einer Warning - dass man auch GetHashCode überschreiben sollte. Die Grundregel ist folgende: Wenn gilt a.Equals(b) == true dann muss auch gelten a.GetHashCode().Equals(b.GetHashCode()) == true. Und das kann man nur sicherstellen, indem man GetHashCode() überschreibt.

Es ist allerdings nicht so wahnsinnig einfach, eine sinnvolle GetHashCode()-Implementierung aus dem Hut zu zaubern, weshabl häufig ein Fehler gemacht wird: Man liefert einfahc immer den gleichen Wert zurück. Oder mit anderen Worten: 0. Denn damit ist ja sichergestellt, obige zwei Gleichungen stimmen (da a.GetHashCode() immer gleich b.GetHashCode() ist). Und genau hier liegt das Problem, wie wir gleich sehen werden. Eine entsprechende Implementierung eines Keys sieht so aus:

public class NoHashcodeKey
{
  public string FirstValue { get; set; }
  public string SecondValue { get; set; }

  protected bool Equals( NoHashcodeKey other )
  {
    return string.Equals( FirstValue, other.FirstValue ) && string.Equals( SecondValue, other.SecondValue );
  }

  public override bool Equals( object obj )
  {
    if ( obj == null )
    {
      return false;
    }
    if ( ReferenceEquals( this, obj ) )
    {
      return true;
    }
    if ( obj.GetType() != this.GetType() )
    {
      return false;
    }
    return Equals( (NoHashcodeKey)obj );
  }

  public override int GetHashCode()
  {
    return 0;  // Hashcode not checked
  }

  public NoHashcodeKey( string firstValue, string secondValue )
  {
    FirstValue = firstValue;
    SecondValue = secondValue;
  }
}

Das Ergebnis bei der Ausführung des Codes ist allerdings nicht so schön:



Für 10000 Elemente benötigt das Dictionary jetzt schon ganz 4 Sekunden - im Vergleich zum Millisekundenbereich anderer Implementierungen.

Volle Implementierung

Bedeutet das jetzt, dass man GetHashCode() nicht überschreiben sollte? (Denn wenn man die Methode nicht überschreibt, ergibt sich ein ähnliches Bild wie bei den beiden ersten Varianten). Natürlich nicht. Man sollte GetHashCode() überschreiben, aber sinnvoll. Es gibt Tools, die eine entsprechende Implementierung vorgeben, die man wiederverwenden kann. Der folgende Code zeigt zum Abschluss eine voll implementierte Key-Klasse inklusive GetHashCode().

public class FullKey : IEquatable<FullKey>
{
  public string FirstValue { get; set; }
  public string SecondValue { get; set; }


  public bool Equals( FullKey other )
  {
    if ( other == null )
    {
      return false;
    }
    return String.Equals( FirstValue, other.FirstValue ) && 
      String.Equals( SecondValue, other.SecondValue );
  }

  public override bool Equals( object obj )
  {
    return Equals( obj as FullKey );
  }

  public override int GetHashCode()
  {
    unchecked
    {
      int firstHashCode = FirstValue != null ? FirstValue.GetHashCode() : 0;
      int secondHashCode = SecondValue != null ? SecondValue.GetHashCode() : 0;
      return ( firstHashCode * 397 ) ^ secondHashCode;
    }
  }

  public FullKey( string firstValue, string secondValue )
  {
    FirstValue = firstValue;
    SecondValue = secondValue;
  }
}

Die gezeigte Implementierung ist natürlich nur ein Beispiel - je nachdem wie die Equals-Methode aufgebaut ist, kann es hier Unterschiede geben. Aber sie kann auch als Vorlage dienen. Das Ergebnis dieser Implementierung zeigt die folgende Grafik:

Kommentare

Eric   07.02.2019   15:14
Hello,

Running a website like frankeller.de is a lot like trying to fill a leaky bucket.

You promote, get website visitor, they drop out... you promote, get more website visitors, they drop out...

But today I wanted to address the "leaky bucket" effect that keeps website owners and online businesses running on a treadmill without reaching their income goals.

I've identified 5 ongoing challenges that can keep you from reaching your business goals with your online business offering.

Ongoing Challenge 1: Attracting Leads and Getting People to Opt-In

In order to sustain your website and sell your product, you need people who are interested enough to actually consider your offers.

Building an engaged list of subscribers is the key here... but did you know there's a way your website can help you with that?

Ongoing Challenge 2: Converting Leads into Happy Paying Customers

Once you've figured out a way to get more people to join your email list, you need to tell them about your paid product and offerings in a way that converts.

The standard email marketing funnel works... but if they don't buy, there are more organic ways to get them to purchase your paid offerings right inside your website.

Ongoing Challenge 3: Having a Leaky Checkout Experience & Losing Out On Sales

Once you've done all of the hard work of getting people to say yes to your paid offerings... you definitely don't want to lose out on sales by having a leaky checkout experience.

Statistics show that 77% of people who click the “Buy Now” button never complete their order, but there's a way to recoup these sales.

Ongoing Challenge 4: Increasing Return Customers and Recurring Revenue

Once you've got customers... your job is done, right? Not so fast!

You can offer more solutions, products, and ongoing access to these same customers over time... because the probability of selling to a new prospect is 5-20% and the probability of selling to an existing customer is 60-70%.

Ongoing Challenge 5: Customers Who Fall Off The Wagon and do not buy

Customer retention is key to the long-term success of your website. But it's normal for customers to fall off the wagon, get busy, and stop logging into your website.

Or is it?

If you use smart nurturing strategies, you can keep your customers engaged and actively benefitting from your website to reduce drop offs and refunds.

How our software overcomes each of these ongoing challenges:

These are real challenges that never seem to go away...

I know because I've been there myself. I've put in thousands of dollars and hundreds of hours figuring out what works, what doesn't, and how to fill my own website over the past 10 years.

And I built all of the strategies to overcome these obstacles right into our best-selling lead converting software tool: http://www.talkwithcustomer.com

Talk With Website Visitors is a widget which captures a website visitor’s Name, Email address and Phone Number and then calls you immediately, so that you can talk to the website visitors exactly when they are live on your website — while they're hot! Best feature of all, we offer FREE International Long Distance Calling!

When targeting website visitors, speed is essential - there is a 100x decrease in Leads when a website visitors is contacted within 30 minutes vs being contacted within 5 minutes.

I'm so sure that Talk With Website Visitors will help you in your business endeavors that I'm offering you a special incentive to get started today.

Sign up for Talk With Website Visitors and get a 14 days free trial today. Visit: http://www.talkwithcustomer.com

This is a one-time only promotion that expires in 14 days.

Why am I offering you this time sensitive 14 days free trial incentive?

Because I know that people who use Talk With Website Visitors get their sites up, running, and selling...

So, if you know its time to get serious about your online website frankeller.de, and you want the tool that's built by someone who has been in your shoes and understands the realities of selling online...

Then take advantage of this special coupon code today. Visit: http://www.talkwithcustomer.com

I'm so excited to share my life's work with you, and I know you understand that there's work ahead to make your digital business successful. But having the right tools and the right strategies to overcome the unavoidable challenges makes it all the more doable!

Thanks so much for reading - and next time I'll be talking about my biggest pet peeve when it comes to technology! (You might be surprised about this one.)

With appreciation,
-Eric "simplifying tech" Jones

If you'd like to unsubscribe click here. http://liveserveronline.com/talkwithcustomer.aspx?d=frankeller.de
Eric   15.02.2019   13:36
Hello,

Running a website like frankeller.de is a lot like trying to fill a leaky bucket.

You promote, get website visitor, they drop out... you promote, get more website visitors, they drop out...

But today I wanted to address the "leaky bucket" effect that keeps website owners and online businesses running on a treadmill without reaching their income goals.

I've identified 5 ongoing challenges that can keep you from reaching your business goals with your online business offering.

Ongoing Challenge 1: Attracting Leads and Getting People to Opt-In

In order to sustain your website and sell your product, you need people who are interested enough to actually consider your offers.

Building an engaged list of subscribers is the key here... but did you know there's a way your website can help you with that?

Ongoing Challenge 2: Converting Leads into Happy Paying Customers

Once you've figured out a way to get more people to join your email list, you need to tell them about your paid product and offerings in a way that converts.

The standard email marketing funnel works... but if they don't buy, there are more organic ways to get them to purchase your paid offerings right inside your website.

Ongoing Challenge 3: Having a Leaky Checkout Experience & Losing Out On Sales

Once you've done all of the hard work of getting people to say yes to your paid offerings... you definitely don't want to lose out on sales by having a leaky checkout experience.

Statistics show that 77% of people who click the “Buy Now” button never complete their order, but there's a way to recoup these sales.

Ongoing Challenge 4: Increasing Return Customers and Recurring Revenue

Once you've got customers... your job is done, right? Not so fast!

You can offer more solutions, products, and ongoing access to these same customers over time... because the probability of selling to a new prospect is 5-20% and the probability of selling to an existing customer is 60-70%.

Ongoing Challenge 5: Customers Who Fall Off The Wagon and do not buy

Customer retention is key to the long-term success of your website. But it's normal for customers to fall off the wagon, get busy, and stop logging into your website.

Or is it?

If you use smart nurturing strategies, you can keep your customers engaged and actively benefitting from your website to reduce drop offs and refunds.

How our software overcomes each of these ongoing challenges:

These are real challenges that never seem to go away...

I know because I've been there myself. I've put in thousands of dollars and hundreds of hours figuring out what works, what doesn't, and how to fill my own website over the past 10 years.

And I built all of the strategies to overcome these obstacles right into our best-selling lead converting software tool: http://www.talkwithcustomer.com

Talk With Website Visitors is a widget which captures a website visitor’s Name, Email address and Phone Number and then calls you immediately, so that you can talk to the website visitors exactly when they are live on your website — while they're hot! Best feature of all, we offer FREE International Long Distance Calling!

When targeting website visitors, speed is essential - there is a 100x decrease in Leads when a website visitors is contacted within 30 minutes vs being contacted within 5 minutes.

I'm so sure that Talk With Website Visitors will help you in your business endeavors that I'm offering you a special incentive to get started today.

Sign up for Talk With Website Visitors and get a 14 days free trial today. Visit: http://www.talkwithcustomer.com

This is a one-time only promotion that expires in 14 days.

Why am I offering you this time sensitive 14 days free trial incentive?

Because I know that people who use Talk With Website Visitors get their sites up, running, and selling...

So, if you know its time to get serious about your online website frankeller.de, and you want the tool that's built by someone who has been in your shoes and understands the realities of selling online...

Then take advantage of this special coupon code today. Visit: http://www.talkwithcustomer.com

I'm so excited to share my life's work with you, and I know you understand that there's work ahead to make your digital business successful. But having the right tools and the right strategies to overcome the unavoidable challenges makes it all the more doable!

Thanks so much for reading - and next time I'll be talking about my biggest pet peeve when it comes to technology! (You might be surprised about this one.)

With appreciation,
-Eric "simplifying tech" Jones

If you'd like to unsubscribe click here. http://liveserveronline.com/talkwithcustomer.aspx?d=frankeller.de

Kommentar hinzufügen

16 + 1 =