Comparison and Equality

How C# compares two values with <, >, == and !=, and why == means contents for string but identity for most objects.

Syntaxa == b a != b a < b a > b a <= b a >= b

Comparison operators ask a question about two values and answer true or false. There are six: < less than, > greater than, <= less than or equal, >= greater than or equal, == equal, and != not equal. Equality is two equals signs. One equals sign assigns a value instead, which is a different thing entirely.

Two meanings of equal

What == compares depends on the kind of type. For a value type such as int, double, char or bool, the contents are compared: two variables holding 5 are equal. For most reference types, == compares identity. Two objects are equal only when both variables point at the same object, even when their contents match exactly.

string is the exception

string is a reference type, but it defines its own == that compares the characters. Two separate string objects holding "hello" are equal. That comparison is case-sensitive, so "ADMIN" == "admin" is false. Use string.Equals(a, b, StringComparison.OrdinalIgnoreCase) when case should not matter.

What trips people up

A string's special == is chosen by the compiler from the declared types, so it disappears when the compiler cannot see strings. Put the same two strings in variables declared as object and == silently goes back to comparing identity. The Equals method is chosen by the object itself at run time, so it keeps comparing the text.

Example

Example · csharp
using System.Text;

int left = 5;
int right = 5;

// int is a value type, so == compares the numbers themselves.
Console.WriteLine(left == right);
Console.WriteLine(left >= right);
Console.WriteLine(left != right);

string first = "hello";
string second = "hel";
second = second + "lo";   // joined while the program runs, so a second object

// string defines its own ==, which compares the characters.
Console.WriteLine(first == second);
// They really are two different objects in memory, though.
Console.WriteLine(object.ReferenceEquals(first, second));

// StringBuilder does not define ==, so == asks "the same object?"
var one = new StringBuilder("hello");
var two = new StringBuilder("hello");
Console.WriteLine(one == two);
Console.WriteLine(one.ToString() == two.ToString());

// Output:
// True
// True
// False
// True
// False
// False
// True

When to use it

  • Check a submitted quantity is inside a range with amount >= 1 && amount <= 100.
  • Compare a typed username against a stored one without caring about case.
  • Decide whether two objects are the same instance or merely hold the same data.
  • Test a calculated average against an expected value in a unit test, within a tolerance.

More examples

The same strings, compared as object

This is the bug behind "my string comparison stopped working". It shows up when values travel through a variable, parameter or collection typed as object, or through a generic type that has not been told its values are strings. The fix is to compare with Equals, or to keep the values typed as string so the compiler can pick the right operator.

Example · csharp
string first = "hello";
string second = "hel";
second = second + "lo";

// The compiler sees two strings, so it uses string's ==: characters compared.
Console.WriteLine(first == second);

object boxedFirst = first;
object boxedSecond = second;

// Both variables are declared object, so the compiler uses object's ==,
// which compares references. The characters are never looked at.
Console.WriteLine(boxedFirst == boxedSecond);

// Equals is picked by the real object at run time, so the text wins again.
Console.WriteLine(boxedFirst.Equals(boxedSecond));

// Output:
// True
// False
// True

Ignoring case on purpose

Reach for this whenever the text came from a person: a typed username, a role name, a file extension, a header value. Ordinal means the characters are compared by their numeric codes rather than by the rules of a language, which is what you want for identifiers and anything you will compare again later on another machine.

Example · csharp
string typed = "ADMIN";
string expected = "admin";

// == on strings is case-sensitive.
Console.WriteLine(typed == expected);

// string.Equals takes a third argument saying how to compare.
Console.WriteLine(string.Equals(typed, expected, StringComparison.OrdinalIgnoreCase));

// Output:
// False
// True

Comparing two double values

== on double asks whether the two bit patterns represent the same number, and arithmetic on double rarely lands on the exact value you wrote down. Use a tolerance for measurements and averages. For money, do not use a tolerance at all: use decimal, where 0.1m + 0.2m really is 0.3m.

Example · csharp
double computed = 0.1 + 0.2;
double expected = 0.3;

// Binary fractions are approximate, so the exact test fails.
Console.WriteLine(computed == expected);

// Compare the size of the difference against a tolerance instead.
Console.WriteLine(Math.Abs(computed - expected) < 0.000001);

// Output:
// False
// True

Discussion

  • Be the first to comment on this lesson.