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.