Skip to main content

How to Resolve Warning "CS0661: Type defines operator == or != but does not override Object.GetHashCode()" in C#

The Compiler Warning CS0661 is a consistency warning regarding Equality Logic. The message reads: "Type 'MyType' defines operator == or operator != but does not override Object.GetHashCode()".

This warning is very similar to CS0659, but it is triggered specifically when you implement Operator Overloading (==) instead of overriding the Equals method.

In .NET, the contract for objects used in hash-based collections (like Dictionary<TKey, TValue> or HashSet<T>) is strict: If two objects are considered equal (via == or Equals), they MUST return the identical value from GetHashCode(). If you define custom logic for == (e.g., comparing database IDs), but you leave GetHashCode as the default (which typically returns a value based on the object's memory address), you break this contract.

This guide explains why this breaks collections and how to implement the missing method.

Understanding the Equality Contract​

When you redefine operator ==, you are telling the compiler: "Treat instances of this class as equal if their values match, not just if they are the same object in memory."

However, Dictionary and HashSet do not use your == operator immediately. They first call GetHashCode() to find the "bucket" where the object lives.

  1. Default GetHashCode: Returns an ID based on the object's memory location (Reference Identity).
  2. Custom ==: Compares internal fields (Value Equality).

If you have two distinct objects with the same data:

  • a == b is True.
  • a.GetHashCode() == b.GetHashCode() is False (by default).

Because the HashCodes differ, the Dictionary looks in the wrong bucket and fails to find the item, even though your == operator says they are the same.

Scenario: The Broken Collection Lookup​

In this scenario, we define == to compare objects by ID, but we forget GetHashCode.

using System;
using System.Collections.Generic;

public class User
{
public int Id { get; set; }
public string Name { get; set; }

// Custom Operator Logic
public static bool operator ==(User a, User b)
{
if (ReferenceEquals(a, b)) return true;
if (a is null || b is null) return false;
return a.Id == b.Id;
}

public static bool operator !=(User a, User b) => !(a == b);

// We also override Equals to silence CS0660, but we forgot GetHashCode (CS0661).
public override bool Equals(object obj) => this == (obj as User);

// ⛔️ Warning CS0661: Type defines operator == but does not override Object.GetHashCode()
}

public class Program
{
static void Main()
{
var u1 = new User { Id = 1, Name = "Alice" };
var u2 = new User { Id = 1, Name = "Alice" };

Console.WriteLine($"u1 == u2: {u1 == u2}"); // True

var dict = new Dictionary<User, string>();
dict.Add(u1, "Employee Data");

// The Lookup Fails!
// u2 is equal to u1, but it has a different HashCode.
bool found = dict.ContainsKey(u2);

Console.WriteLine($"Dictionary contains u2? {found}"); // False (Unexpected!)
}
}

Solution 1: Modern Implementation (HashCode.Combine)​

To fix this, you must override GetHashCode and ensure it uses the exact same fields that you used in your == logic.

If you are using .NET Core 2.1+, .NET 5+, or .NET Standard 2.1+, use the HashCode struct.

public class User
{
public int Id { get; set; }
public string Name { get; set; }

// ... operators and Equals implementation ...

// ✅ Correct: Derive the hash code from the ID.
// Since '==' only compares Id, GetHashCode should only use Id.
public override int GetHashCode()
{
return HashCode.Combine(Id);
}
}
note

If your == operator compared both Id and Name, your hash code should be HashCode.Combine(Id, Name). The logic must be symmetrical.

Solution 2: Legacy Implementation​

If you are on an older version of the .NET Framework, or cannot use the HashCode struct, use a standard hashing algorithm involving XOR (^) and prime numbers.

public class User
{
public int Id { get; set; }

// ... operators and Equals implementation ...

// ✅ Correct: Legacy implementation
public override int GetHashCode()
{
// Simple implementation for a single field
return Id.GetHashCode();

// If you had multiple fields:
// unchecked
// {
// int hash = 17;
// hash = hash * 23 + Id.GetHashCode();
// hash = hash * 23 + (Name != null ? Name.GetHashCode() : 0);
// return hash;
// }
}
}

Conclusion​

CS0661 is a critical warning for data integrity in collections.

  1. The Rule: If x == y is true, then x.GetHashCode() == y.GetHashCode() must be true.
  2. The Cause: You defined custom equality (==) but left the hash generation to the default memory-based logic.
  3. The Fix: Override GetHashCode and generate the hash using the same properties used in your == operator.