How to Resolve Warning "CS0660: Type defines operator == or != but does not override Object.Equals" in C#
The Compiler Warning CS0660 is a consistency warning regarding Equality Logic. The message reads: "Type defines operator == or operator != but does not override Object.Equals(object o)".
In C#, there are two main ways to check if two objects are equal:
- Operators:
a == b(Static, usually compile-time binding). - Method:
a.Equals(b)(Virtual, run-time binding).
If you manually implement the == operator, you are defining custom logic for what "equality" means for your type (e.g., comparing IDs instead of memory references). The compiler warns you that you have defined this logic for the operator, but you left the standard .Equals() method using the default behavior. This creates an inconsistent state where a == b might return true, but a.Equals(b) returns false.
This guide explains the "Equality Contract" in .NET and how to implement it correctly.
Understanding the Equality Contract
The fundamental rule of equality in C# is consistency.
- If
a == bis true, thena.Equals(b)must be true.
Standard .NET collections (like ArrayList or List<T>.Contains) and generic algorithms often use Equals(), not ==. If you only implement the operator, your object will work correctly in if statements but fail mysteriously when put into collections.
Scenario: The Inconsistent Comparison
This warning appears when you write the static operators but forget to update the virtual method.
Example of error:
public class Product
{
public int Id { get; set; }
// Custom operator logic: Equal if IDs are equal
public static bool operator ==(Product a, Product b)
{
// (Null checks omitted for brevity)
if (ReferenceEquals(a, b)) return true;
if (a is null || b is null) return false;
return a.Id == b.Id;
}
public static bool operator !=(Product a, Product b) => !(a == b);
// ⛔️ Warning CS0660: Type defines operator == but does not override Object.Equals.
// The default Object.Equals checks Reference Equality (memory address).
}
public class Program
{
static void Main()
{
var p1 = new Product { Id = 1 };
var p2 = new Product { Id = 1 };
// Inconsistent behavior:
Console.WriteLine(p1 == p2); // True (Uses your operator)
Console.WriteLine(p1.Equals(p2)); // False (Uses default Object.Equals)
}
}
Solution: Overriding Equals
To fix the warning and ensure consistency, override Object.Equals. Ideally, both the operator and the method should call shared logic.
Solution:
public class Product
{
public int Id { get; set; }
// 1. Override Equals to match operator logic
public override bool Equals(object obj)
{
// Check types and nulls
if (obj is Product other)
{
return this.Id == other.Id;
}
return false;
}
// 2. Ideally, override GetHashCode as well (fixes CS0659)
public override int GetHashCode() => Id.GetHashCode();
// 3. Operators call Equals or share logic
public static bool operator ==(Product a, Product b)
{
if (ReferenceEquals(a, b)) return true;
if (a is null) return false;
return a.Equals(b); // Reuse the logic from Equals
}
public static bool operator !=(Product a, Product b) => !(a == b);
}
If you fix CS0660 by overriding Equals, you will likely trigger CS0659 ("overrides Equals but not GetHashCode"). You should always implement Equals and GetHashCode together.
Best Practice: Implementing IEquatable<T>
If you are defining value equality (which is implied if you are overloading ==), you should implement the IEquatable<T> interface. This provides a strongly-typed Equals method that is faster (avoids casting) and is preferred by generic collections like Dictionary and List.
The Complete Pattern
using System;
// ✅ Correct: Implementing the full equality pattern
public class Product : IEquatable<Product>
{
public int Id { get; set; }
// Interface Implementation (Strongly Typed)
public bool Equals(Product other)
{
if (other is null) return false;
return this.Id == other.Id;
}
// Standard Object Override (Calls the strong version)
public override bool Equals(object obj)
{
return Equals(obj as Product);
}
public override int GetHashCode() => Id.GetHashCode();
// Operators
public static bool operator ==(Product a, Product b)
{
if (ReferenceEquals(a, b)) return true;
if (a is null) return false;
return a.Equals(b);
}
public static bool operator !=(Product a, Product b) => !(a == b);
}
Conclusion
CS0660 is a reminder that == and .Equals() are two sides of the same coin.
- The Rule: Do not implement
operator ==without overridingobject.Equals(). - The Risk: Failing to do so causes your object to behave inconsistently depending on whether it is compared via syntax (
==) or via collection methods (.Contains). - The Complete Fix: When defining custom equality, always implement the "Trinity":
Equals,GetHashCode, andOperators(plusIEquatable<T>).