Types · comparison
== vs .Equals() in C#
They agree in most beginner code — and the differences are exactly where bugs hide: null handling, and what "equal" means for each type.
For primitives: identical behavior
int x = 5;
int y = 5;
bool f = false;
Console.WriteLine(x == y); // value comparison
Console.WriteLine(x.Equals(y)); // same answer for int
Console.WriteLine(f.Equals(false));
For int, double, bool, char there is no meaningful difference — use ==, it reads better.
For strings: same result, different mechanism
string a = "apple";
Console.WriteLine(a == "apple"); // True — C# makes == compare CONTENT for strings
Console.WriteLine(a == "Apple"); // False — case matters, always
Console.WriteLine(a.Equals("apple")); // True — content again
Console.WriteLine(a.Equals("Apple")); // False — same case rule
Both compare character content and both are case-sensitive. (In other languages == compares references for strings — a classic C#/Java confusion; in C# the operator was overloaded to do the useful thing.)
Difference 1: null handling
The real behavioral split. == is comfortable with null; calling a method on null explodes:
string s = null;
// == is a static check — null on either side is fine
Console.WriteLine(s == null);
// .Equals() is a method CALL on s — s is null, so guard it first:
if (s != null)
{
Console.WriteLine(s.Equals("x"));
}
else
{
Console.WriteLine("safe"); // this runs — calling s.Equals would have thrown
}
Practical rule: when either side might be null, use == (or string.IsNullOrEmpty), not .Equals().
Difference 2: what "equal" means is overridable
Equals() is a method a class can redefine; == is an operator a class may or may not overload. For your own classes, neither does content comparison by default — both are identity (the struct vs class article's a == b → False demo):
class Dog { public string Name; }
Dog d1 = new Dog { Name = "Rex" };
Dog d2 = new Dog { Name = "Rex" };
Console.WriteLine(d1 == d2); // False — different objects
Console.WriteLine(d1.Equals(d2)); // False — same, unless Equals is overridden
Records (C# 9+) and structs change this default to value comparison — one reason records took over for data types.
The everyday winner: case-insensitive compare
User input doesn't respect your casing. The idiomatic tool isn't == or plain Equals — it's Equals with a comparison type (in Visual Studio; the browser engine doesn't have the overload yet):
string input = "YES";
// these three are the real-world patterns:
input == "yes" // False — case-sensitive
input.Equals("yes", StringComparison.OrdinalIgnoreCase) // True
input.ToLower() == "yes" // True — the quick-and-dirty version
Prefer StringComparison.OrdinalIgnoreCase over ToLower() chains — it allocates nothing and states the intent.
Decision table
| Comparing | Use |
|---|---|
| numbers, bool, char | == |
| strings, case matters | == |
| strings, case-insensitive | Equals(…, OrdinalIgnoreCase) |
| anything that might be null | == (or IsNullOrEmpty) |
| your own content-equality types | make it a record, or override Equals |