Intro
By now (almost 20 years since .NET was released) this topic has been discussed a looooooot, but still, sometimes, I feel like it's forgotten, so there's probably no harm in talking about it again.
Java makes it a bit simpler in this regard, because it doesn't allow for operator overload (e.g. ==
), so developers know the rule really well: ==
compares references, equals
actually compares the objects.
Because .NET allows for operator overload, .NET developers end up using ==
and Equals
interchangeably, but it isn't as straightforward.
A quick example
Let's create a simple class that overrides the Equals
method, as well as overloads the ==
operator.
public class A
{
private readonly int _someValue;
public A(int someValue)
{
_someValue = someValue;
}
public override bool Equals(object other) => other is A a && _someValue == a._someValue;
public static bool operator == (A left, A right) => object.Equals(left, right);
public static bool operator != (A left, A right) => !object.Equals(left, right);
}
Now let's play around with the available comparisons.
class Program
{
static void Main(string[] args)
{
A a1 = new A(1);
A a2 = new A(1);
Console.WriteLine($"a1.Equals(a2): {a1.Equals(a2)}");
Console.WriteLine($"object.Equals(a1, a2): {object.Equals(a1, a2)}");
Console.WriteLine($"a1 == a2: {a1 == a2}");
}
}
This program console output is the following:
a1.Equals(a2): True
object.Equals(a1, a2): True
a1 == a2: True
So far so good! The objects are the equal, so every comparison returns true
.
The static object.Equals
ends up also calling the overridden Equals
method, the advantage of using it is to avoid getting null reference exception (or having to check for null
s manually).
Now let's make an apparently harmless change:
class Program
{
static void Main(string[] args)
{
object a1 = new A(1); // changed the variable type from A to object
object a2 = new A(1); // ditto
Console.WriteLine($"a1.Equals(a2): {a1.Equals(a2)}");
Console.WriteLine($"object.Equals(a1, a2): {object.Equals(a1, a2)}");
Console.WriteLine($"a1 == a2: {a1 == a2}");
}
}
Now, does anything change in the output?
a1.Equals(a2): True
object.Equals(a1, a2): True
a1 == a2: False
So now ==
returns false
, not good!
Why is that?
The Equals
method is a "normal" method defined in object
and overridden in our A
class, so it follows the usual polymorphism rules: what matters is the type of the object, not the type of the variable that references it.
The ==
operator is simply defined to compare A
objects, as you can see by the types of parameters used in its definition above. This means it won't be called on non A
variables. In this case, it ends up using the default object comparison, which means reference comparison. As the references are not the same, we get false
.
Another twisted example
Now let's look at a slightly more twisted example, using a built-in type, the string
.
class Program
{
static void Main(string[] args)
{
object s1 = "s";
object s2 = "s";
Console.WriteLine($"s1 == s2: {s1 == s2}");
}
}
This outputs:
s1 == s2: True
So, what's different here?
For optimization, .NET (and others) uses something called string interning to keep a single copy of the each distinct value, so even though we are declaring it two times, it ends up keeping a single copy.
Due to string interning, the ==
works as desired in this situation, because the reference is actually the same.
We can however break this with a slight tweak:
class Program
{
static void Main(string[] args)
{
object s1 = "s";
object s2 = new string(new []{'s'});
Console.WriteLine($"s1 == s2: {s1 == s2}");
Console.WriteLine($"s1.Equals(s2): {s1.Equals(s2)}");
}
}
And now we get:
s1 == s2: False
s1.Equals(s2): True
So, even though most of the time, with strings, things will just work, there are situations where it won't, so better be careful.
Outro
Wrapping up, the goal was not to say not to use the ==
operator, but keep in mind the pitfalls associated with it.
You're comparing built-in types or types you control and have the correct reference type? Sure, go ahead. Every other case, better go with Equals
.
Thanks for stopping by, cyaz!
Top comments (0)