In this article, we delve into the similarities and differences between the equality (==) operator and the Equals method in C#.

To download the source code for this article, you can visit our GitHub repository.

Now, let’s explore these concepts further!

Equality Operator And Equals Method in Different Scenarios

Let’s review some examples to understand how == operator and Equals method behaves in different cases. For a comprehensive go-through, we will also be exploring the usage of the ReferenceEquals method in the examples.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Built-In Value Types

Built-in value types, such as int, float, bool, and others, utilize the equality (==) operator to directly compare their values. The equality operator examines whether the values are equal or not.

Before proceeding with the code examples, let’s take a moment to inspect a method that we will use in our snippets for printing our results:

public static string PrintFormattedResult(bool? a, bool? b, bool? c)
{
    var result = $"Reference: {a}, Equality: {b}, Equals: {c}";
    Console.WriteLine(result);

    return result;
}

Taking that into account, let’s analyze a code snippet that compares the behaviors of the ReferenceEquals method, the equality (==) operator, and the Equals method:

int firstNumber = 5;
int secondNumber = 5;

return PrintFormattedResult(
    ReferenceEquals(firstNumber, secondNumber), // false
    firstNumber == secondNumber, // true
    firstNumber.Equals(secondNumber) // true
);

We must keep in mind that the ReferenceEquals method cannot be utilized with value types and will consistently return false when invoked with value types. However, for built-in value types, both the == operator and the Equals method behaves similarly by returning true when the values of the operands are equal. Therefore, when comparing built-in value types, we can see that the == operator and the Equals method exhibit similar behavior.

Objects

The equality (==) operator exhibits different behavior for reference types, such as objects, by default. It checks for reference equality, comparing the memory addresses of the objects to determine if they are the same instance. Even if two different objects have the same content, they will not be considered equal with the == operator unless they refer to the same instance. Similarly, the Equals method, by default, performs reference equality comparisons for reference types.

Let’s explore how two objects behave when we compare them using the == operator and the Equals method:

object firstObject = new[] { 1, 2, 3 };
object secondObject = new[] { 1, 2, 3 };

return PrintFormattedResult(
    ReferenceEquals(firstObject, secondObject), // false
    firstObject == secondObject, // false
    firstObject.Equals(secondObject) // false
);

The ReferenceEquals method returns false because these two objects have different memory addresses. The equality (==) operator also returns false for the same reason. Though the contents of both objects are the same, the Equals method returns false as the references of both objects are not identical.

Strings

Now, let’s study the behavior of the == operator and the Equals method when the comparison is made between two strings:

string firstWord = "pqr";
string secondWord = "pqr";

return PrintFormattedResult(
    ReferenceEquals(firstWord, secondWord), // true
    firstWord == secondWord, // true
    firstWord.Equals(secondWord) // true
);

String interning in .NET causes the ReferenceEquals method to return true. String interning is a process where the runtime maintains a pool of unique string instances. When creating a string, the runtime actively checks if an identical string already exists in the pool. If it does, the new string reference points to the existing string instance instead of creating a new one.

C# performs this optimization for conserving memory and improving performance. For string, the equality (==) operator, inequality (!=) operator and the Equals method perform a case-sensitive, ordinal comparison by default. Hence, both the == operator and the Equals method returns true when a comparison is made between firstWord and secondWord. Besides, we can pass a StringComparison argument with the Equals method for altering its sorting rules. To learn more about this, you may visit Default ordinal comparisons

Object and String

With that in mind, let’s take a closer look at how the == operator and the Equals method behave when we compare an object with a string:

object thirdObject = new string(new[] { 'x', 'y', 'z' });
string thirdWord = "xyz";

return PrintFormattedResult(
    ReferenceEquals(thirdObject, thirdWord), // false
    thirdObject == thirdWord, // false
    thirdObject.Equals(thirdWord) // true
);

The ReferenceEquals method returns false because thirdObject and thirdWord have different memory references. We can see that thirdObject is a string boxed as an object and thirdWord is simply a string. When we compare these two, the == operator defined on the object gets invoked, rather than the string. This leads to a reference comparison and a false value is returned. To achieve the behavior of == operator defined on the string class, we have to cast the thirdObject to a string:

Console.WriteLine((string) thirdObject == thirdWord); // true

We get a return value of true once we cast the thirdObject to string and compare it with thirdWord.

Finally, the thirdObject being a string calls the override of the Equals method defined in the string class, which performs the default case-sensitive ordinal comparison. This allows for an accurate comparison between the two string values.

Classes

For user-defined types (e.g., classes, structs, and records), the behavior of the == operator depends on how equality is defined for the type. By default, the == operator compares reference equality for classes. However, we can override the == operator or implement the Equals() method to define custom equality comparison logic for our types:

var firstEmployee = new Employee("Hermione Granger", 5000);
var secondEmployee = new Employee("Hermione Granger", 5000);

return PrintFormattedResult(
    ReferenceEquals(firstEmployee, secondEmployee), // false
    firstEmployee == secondEmployee, // false
    firstEmployee.Equals(secondEmployee) // false
);

The ReferenceEquals method returns false because firstEmployee and secondEmployee have different memory addresses. The equality (==) operator also performs reference equality for class types and hence returns false. Lastly, the Equals method defaults to performing reference equality, resulting in a false return. Again, it is possible to override the Equals method to enable a more meaningful comparison of equality.

Structs

That being said, let’s analyze the behavior of the == operator and the Equals method when the comparison is made between two struct types:

var firstCar = new Car("Audi R8", 3715);
var secondCar = new Car("Audi R8", 3715);

return PrintFormattedResult(
    ReferenceEquals(firstCar, secondCar), // false
    firstCar == secondCar, // true
    firstCar.Equals(secondCar) // true
);

In C#, structs are value types, which means they store their instances directly with their values instead of referring to memory locations. When using the ReferenceEquals method to compare two struct instances, it will always return false because the method verifies if the two references point to the same memory location, which is not applicable for structs.

Next, we encounter the == operator, which is not automatically defined for struct types. As a result, we have to override the == operator on the Car class in a way that replicates the behavior of the Equals method: 

public override bool Equals(object? obj)
{
    if (obj is not Car other) return false;

    return Model == other.Model && Weight == other.Weight;
}

public override int GetHashCode() => (Model, Weight).GetHashCode();

public static bool operator ==(Car carLeft, Car carRight) => carLeft.Equals(carRight);

public static bool operator !=(Car carLeft, Car carRight) => !(carLeft == carRight);

Finally, the Equals method compares the values of each field in the struct to determine equality. Since all the fields have identical values, the method returns true.

Records

With the class and struct types covered, let’s learn how the == operator and the Equals method behave when we compare two record types:

var firstLaptop = new Laptop("ASUS TUF A15", 1499);
var secondLaptop = new Laptop("ASUS TUF A15", 1499);

return PrintFormattedResult(
    ReferenceEquals(firstLaptop, secondLaptop), // false
    firstLaptop == secondLaptop, // true
    firstLaptop.Equals(secondLaptop) // true
);

The ReferenceEquals method returns false because firstLaptop and secondLaptop have different memory addresses. The equality (==) operator, by default, performs a memberwise comparison for record types. As all the members are identical, it returns true in this case. Lastly, the Equals method also has behavior similar to the equality (==) operator, and outputs will also be similar.

Nullable Types

When dealing with nullable types (e.g., int?, float?, etc.), we can use the equality (==) operator to compare nullable values. It returns true if both operands are null or have the same underlying value:

int? firstNullableNumber = null;
int? secondNullableNumber = null;

return PrintFormattedResult(
    ReferenceEquals(firstNullableNumber, secondNullableNumber), // true
    firstNullableNumber == secondNullableNumber, // true
    firstNullableNumber.Equals(secondNullableNumber) // true
);

The ReferenceEquals method returns true here, both arguments are null, indicating that they refer to the same null reference. Next, the equality (==) operator evaluates to true because null represents the absence of a value, and two null values are considered equal. Finally, when both nullable instances have null values, the Equals method considers them equal because they represent the absence of a value.

Conclusion

In this article, we have learned about the different behaviors of the equality operator and the Equals method in various scenarios. Moreover, this knowledge will enable us to make more informed decisions regarding when to utilize the equality operator and the Equals method in our code.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!