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.
- Default
GetHashCode: Returns an ID based on the object's memory location (Reference Identity). - Custom
==: Compares internal fields (Value Equality).
If you have two distinct objects with the same data:
a == bis 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);
}
}
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.
- The Rule: If
x == yis true, thenx.GetHashCode() == y.GetHashCode()must be true. - The Cause: You defined custom equality (
==) but left the hash generation to the default memory-based logic. - The Fix: Override
GetHashCodeand generate the hash using the same properties used in your==operator.