Tdd Apps Blog

Compiler Warning CS0659

Jan 30, 2016 8 minute read

The majority of C# developers have found Compiler Warning CS0659 at some point of their careers. Ignoring this warning can produce unexpected program behavior. The following is summary of the impact this warning can have on a codebase and how to prevent it.

TL;DR: Override GetHashCode whenever Equals is overridden

Reference Equality

Let’s consider the following Dog class in C#.

public class Dog
{
    public int Weight { get; set; }
    public string Name { get; set; }
}

And the following usage of it.

var fido1 = new Dog { Name = "Fido", Weight = 12 };
var fido2 = new Dog { Name = "Fido", Weight = 12 };
Console.WriteLine(fido1.Equals(fido2));

The snippet of code from above prints False. For reference types the default implementation of Equals compares the references instead of the values where they are pointing to. 1

Github Commit 5d488e6d6127111167e9a8f36fe83a771e36cb9c contains the sample source code up to this point

Semantic Equality

Let’s enhance the Dog class so that comparisons between Dogs have a better semantic meaning.

public class Dog
{
    public int Weight { get; set; }
    public string Name { get; set; }
    
    public override bool Equals(object obj)
    {
        var that = (Dog)obj;
        return this.Weight == that.Weight && this.Name == that.Name;
    }
}

After overriding the Equals method the following code prints True.

var fido1 = new Dog { Name = "Fido", Weight = 12 };
var fido2 = new Dog { Name = "Fido", Weight = 12 };
Console.WriteLine(fido1.Equals(fido2));

Github Commit 7abeb0d9b8dbf7c09124485fb0f7e3bd18257b18 contains the sample source code up to this point

Warning CS0659

Once we compile the code we get the following warning.

warning CS0659: ‘Dog’ overrides Object.Equals(object o) but does not override Object.GetHashCode()

Compiler Warning (level 3) CS0659 is straightforward and direct to the point: If you override Equals you need to override GetHashCode.

The Problem

This is not one of those warnings that can safely be ignored. 2 It will come back to bite you. Let’s consider the following usages of the Dog class.

var dogShelter = new Dictionary<Dog, int>{
    {new Dog { Name = "Fido", Weight = 12 }, 100},
    {new Dog { Name = "Pete", Weight = 5 }, 10}
};
var fido = new Dog { Name = "Fido", Weight = 12 };
Console.WriteLine(dogShelter.ContainsKey(fido));

The code from above prints False because fido cannot be found in dogShelter.
There comes the bite in the ass

Why?

Overriding Equals is not enough. The GetHashCode method provides a numeric value for quick equality checks such as those used in collections like Dictionary and HashTable. 3

Github Commit d91504bfedcb60ab86fffe72dcac88fb775405de contains the sample source code up to this point

Solution

Override GetHashCode whenever Equals is overridden. The following is an implementation of Dog where GetHashCode is overridden. 4

public class Dog
{
    public int Weight { get; set; }
    public string Name { get; set; }
    
    public override bool Equals(object obj)
    {
        var that = (Dog)obj;
        return this.Weight == that.Weight && this.Name == that.Name;
    }
    
    public override int GetHashCode()
    {
        unchecked
        {
            return (Weight * 397) ^ (Name != null ? Name.GetHashCode() : 0);
        }
    }
}

With this implementation of Dog in place, fido can be found in the dogShelter.

var dogShelter = new Dictionary<Dog, int>{
    {new Dog { Name = "Fido", Weight = 12 }, 100},
    {new Dog { Name = "Pete", Weight = 5 }, 10}
};
var fido = new Dog { Name = "Fido", Weight = 12 };
Console.WriteLine(dogShelter.ContainsKey(fido));

Github Commit 98c9adc1fff2aee2362fd494f3a43cdb7f4d3d8a contains the sample source code up to this point

In a nutshell

Object.GetHashCode is as important as Object.Equals for equality matters. Never override one without the other.

Update: Equality is Hard 5

Since Dog is a mutable object GetHashCode will return different values when either of its properties change making it impossible to be found in dictionaries. Down the rabbit hole we go. To overcome this issue we have to make its properties readonly.

public class Dog
{
    public int Weight { get; }
    public string Name { get; }

    public Dog(string name, int weight)
    {
        this.Name = name;
        this.Weight = weight;
    }

    public override bool Equals(object obj)
    {
        var that = (Dog)obj;
        return this.Weight == that.Weight && this.Name == that.Name;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (Weight * 397) ^ (Name != null ? Name.GetHashCode() : 0);
        }
    }
}

Now our Program looks as follows:

var dogShelter = new Dictionary<Dog, int>{
    {new Dog("Fido", 12), 100},
    {new Dog("Pete", 5), 10}
};
var fido = new Dog("Fido", 12);

Console.WriteLine(dogShelter.ContainsKey(fido));

Github Commit dffbd97927070b05c623192a788a33b97e7101c7 contains the sample source code up to this point

Value Types to the Rescue

An easier way to get rid of all of this Equality nonsense would be to make Dog a value type. That way we’d get equality for free.

public struct Dog
{
    public int Weight { get; }
    public string Name { get; }

    public Dog(string name, int weight)
    {
        this.Name = name;
        this.Weight = weight;
    }
}

Github Commit b756e87a63abec9ea8c069bc5d9c09b2a2376311 contains the sample source code up to this point