It can take an entire career to master C#, but sometimes it is the small things that can trip up .NET developers. I do code reviews within our team and see the == operator often, and I also see the Equals method used for specific cases. What’s the difference between the two approaches, and when should you use them?

Generally, the == operator is a comparison between similar C# primitive types and their values. For integral values, the comparison is arithmetic, while boolean comparisons are logical. The Equals method exists on most types derived from the object class, and each type may choose to determine what its equality means.

Overriding behavior in C# is possible, but generally, its a good rule of thumb to use the == operator on basic primitives and use the .Equals method when you are clear on the expected behavior of the instance types.

Basic Usage Of == Operator

If you have any background in programming, you have compared two values together. In C#, it is no different. Given two values, you want to know whether they are equal.

Comparing Numeric Types With ==

Numeric values include types like int, double, long, and many others. The thing to remember about numerical comparison is that .NET can easily compare different numerical types by overloading the behavior of the == operator. The overloaded operator makes comparing numbers in .NET frictionless. Let’s take a look at some examples.

int x = 1;
decimal y = 1m;
long z = 1;

Console.WriteLine($"x == x is {x == x}");
Console.WriteLine($"x == y is {x == y}");
Console.WriteLine($"x == z is {x == z}");

In the example above, I am comparing various numeric types with each other with the expected result of True. Running the code above returns the following results.

x == x is True
x == y is True
x == z is True

Comparing String Types With ==

Strings are one of the most potent types in .NET. While they look like and behave like primitives, they are not. For the sake of this post, let us categorize them with numeric and logical primitives. Below is an example of comparing strings to each other. Note the difference in the casing for the value of z.

string x = "john";
string y = "john";
string z = "John";

Console.WriteLine($"x == x is {x == x}");
Console.WriteLine($"x == y is {x == y}");
Console.WriteLine($"x == z is {x == z}");

Running the program gives us a mostly expected outcome.

x == x is True
x == y is True
x == z is False

The result is as expected, except for the last comparison, which results in a value of False. Why is that? In C#, string equality comparisons are direct value comparisons, which means any casing or whitespace can impact the perceived value of the variable.

Nullable Boolean Examples

While booleans are simple logical comparisons, it is essential to mention its complicated sibling bool? also known as Nullable<bool>. While booleans exist in a binary state, a bool? can have three states: null, true, and false. Sometimes, it is vital to understand a user intended one of the binary states and that you aren’t just dealing with a default state of false.

In C#, we are comfortable putting booleans in if statements. While bool? generally behave like common booleans, they can cause issues when they run up against our habits. Take the following example, which will not compile.

bool? x = null;
if (x)
{
  // do something
}

To fix this issue, you can use the GetValueOrDefault method provided by the Nullable class, or my preferred method of using the == operator.

bool? x = null;
if (x == true)
{
  // I'm happy now!
}

Basic Usage Of Equals Method

Like I mentioned above, the Equals method derives from the base object type. Changing the behavior of the method can be accomplished by any implemented class type in C#. If the implementor does not override the behavior in a new class, determining equality is done by reference. In other words, are the objects being compared occupying the same space in memory?

var x = 1;
var result = x.Equals(1);

Comparing Numeric Types With Equals

int x = 1;
decimal y = 1m;
long z = 1;

Console.WriteLine($"x equals x is {x.Equals(x)}");
Console.WriteLine($"x equals y is {x.Equals(y)}");
Console.WriteLine($"x equals z is {x.Equals(z)}");

Using the Equals method with numbers gets us a confusing result.

x equals x is True
x equals y is False
x equals z is False

Why did that occur? Remember how I mentioned that each type decides how it implements the Equals method. In the case of most primitive types, they choose not to override the method. The language bases its results on the memory location of our values and not their numeric values.

Comparing String Types With Equals

Well, comparing numeric values with Equals did not get us the results we expected. What results do you expect comparing strings with the Equals method?

string x = "john";
string y = "john";
string z = "John";

Console.WriteLine($"x equals x is {x.Equals(x)}");
Console.WriteLine($"x equals y is {x.Equals(y)}");
Console.WriteLine($"x equals z is {x.Equals(z)}");

It’s exactly the results we achieved using the == operator.

x equals x is True
x equals y is True
x equals z is False

String is a class that has reimplemented the Equals method to behave like the == operator. In this case, they are equivalent. A nice bonus of using the Equals operator on strings, is it comes overloaded to take a StringComparison enum to make case insensitive comparisons.

x.Equals(z, StringComparison.OrdinalIgnoreCase)

Note: be careful that x is not null, or else your application will throw a null reference exception.

Conclusion

Comparing values is fundamental to all programming languages, and C# is no different. You have many options when deciding to compare two values together. In most cases, sticking with the == operator is best as the behavior is predictable. Additionally, being an object-oriented language, many classes can be broken down to the most basic of primitives and compared. Whether you are comparing two object’s identifiers or the temperature values of two thermostat instances, primitive comparisons are achievable.

The Equals method is useful for strings but I advise not to use it on other classes unless you know the expected behavior. I also do not recommend overriding the base implementation in your classes. If you want to make comparisons between more complex classes, I recommend writing a precise method outside of the instances.