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

ComparingUse
numbers, bool, char==
strings, case matters==
strings, case-insensitiveEquals(…, OrdinalIgnoreCase)
anything that might be null== (or IsNullOrEmpty)
your own content-equality typesmake it a record, or override Equals