Last Updated on June 7, 2022


There are many ways to compare objects in C#. In this article, we will talk about the difference between the most common ones: the equality operator == and the Equals method (i.e. C# Equals vs ==).

Equality operator ==

The equality operator is the simplest way to compare two objects in C#. You can use this operator to compare built-in value types, as well as reference types.

Equality operator for built-in value types

When comparing two value types, the equality operator returns true only if the values are the same. For example, here is the output of the following program:

double d = 10;
int i = 10;
float f = 10;

DateTime d1 = new DateTime(2022, 2, 10);
DateTime d2 = new DateTime(2022, 2, 10);
DateTime d3 = new DateTime(2022, 2, 11);
Console.WriteLine($"d==i returns {d == i}");
Console.WriteLine($"d==f returns {d == f}");
Console.WriteLine($"i==f returns {i == f}");

Console.WriteLine($"d1==d2 returns {d1 == d2}");
Console.WriteLine($"d1==d3 returns {d1 == d3}");

Console output for built-in value types tests

Basically, comparing a double and an int will return true if they both have the same value. Also, since the DateTime type is a structure, comparing two DateTime instances that have the same properties will return true.

Equality operator for user-defined struct

There is no default implementation of the equality operator for user-defined structures. Therefore, it’s the responsibility of the developer to implement an overload of the equality operator if needed.

In the following example, you can see the compiler error displayed in Visual Studio when you try to compare 2 instances of a struct with the equality operator.

Here is the struct that we are using:

public readonly struct CarFeatures
{
    public bool HasBluetooth { get; init; }

    public bool HasRearViewCamera { get; init; }

    public bool HasTractionControl { get; init; }
}

And here is the screenshot of the compiler error (CS0019) in Visual Studio:

Compiler error when trying to compare 2 instances of a struct

Equality operator for reference types

For reference types, the default implementation of the equality operator returns true if the two objects are of the same type and contain the same reference. That means they point to the same address in the heap. Basically, using == for reference types will give the same result as using the Object.ReferenceEquals method.

For example, let’s define a class named Car.

public readonly struct CarFeatures
{
    public bool HasBluetooth { get; init; }

    public bool HasRearViewCamera { get; init; }

    public bool HasTractionControl { get; init; }
}


public class Car
{
    public string Make { get; init; }
    public string Model { get; init; }
    public int NumberOfSeats { get; init; }
    public int Mileage { get; init; }
    public CarFeatures Features { get; init; }
}

And the following code will compare two instances of the Car type.

CarFeatures myCarFeatures = new CarFeatures()
{
    HasBluetooth = true,
    HasRearViewCamera = true,
    HasTractionControl = true
};

Car myCar = new Car()
{
    Make = "Camry",
    Model = "Toyota",
    NumberOfSeats = 5,
    Mileage = 10000,
    Features = myCarFeatures
};

Car myCarSecondPointer = myCar;

Car hisCar = new Car()
{
    Make = "X5",
    Model = "BMW",
    NumberOfSeats = 5,
    Mileage = 1000,
    Features = myCarFeatures
};

Console.WriteLine($"myCar==myCarSecondPointer returns {myCar == myCarSecondPointer}");
Console.WriteLine($"Object.ReferenceEquals(myCar, myCarSecondPointer) returns {Object.ReferenceEquals(myCar, myCarSecondPointer)}");
Console.WriteLine($"myCar==hisCar  returns {myCar == hisCar }");
Console.WriteLine($"Object.ReferenceEquals(myCar, hisCar)  returns {Object.ReferenceEquals(myCar, hisCar)}");

That code will give the following output in the console:

Console output for reference types equality tests

Equality operator for the String type

As most C# developers already know, the String type is a reference type that behaves mostly like a value type. Therefore, when comparing two String, the equality operator will compare the values of those String instead of their references.

Here is a code sample:

String firstName = "Snake";
String lastName = "Snake";
String name = "Snake Snake";

Console.WriteLine($"firstName==lastName returns {firstName == lastName}");
Console.WriteLine($"firstName==name returns {firstName == name}");
Console.WriteLine($"name==lastName returns {name == lastName}");

And this is the console output of that code:

Console output for string equality tests

Equality operator for the Record type

The purpose of the record keyword is to define reference types that contain immutable data. In this case, the equality operator simply compares the values of all fields and properties of the records.




C# Equals method

The Object.Equals method is a virtual method of the Object class. It gives the ability to verify if two instances of the same class are equals.

C# Equals for built-in value types

When comparing two value types boxed into objects, the Equals method verifies if these 2 objects have the same type and then performs a value comparison of the 2 objects.

For example, here is the output of the same program that we used previously. This time, we replaced the equality operator with Object.Equals:

double d = 10;
int i = 10;
float f = 10;

DateTime d1 = new DateTime(2022, 2, 10);
DateTime d2 = new DateTime(2022, 2, 10);
DateTime d3 = new DateTime(2022, 2, 11);
Console.WriteLine($"Object.Equals(d, i) returns {Object.Equals(d, i)}");
Console.WriteLine($"Object.Equals(d, f) returns {Object.Equals(d, f)}");
Console.WriteLine($"Object.Equals(i, f) returns {Object.Equals(i, f)}");

Console.WriteLine($"Object.Equals(d1, d2) returns {Object.Equals(d1, d2)}");
Console.WriteLine($"Object.Equals(d1, d3) returns {Object.Equals(d1, d3)}");
Console output for value types equality tests with object.equals

Here, you can see that comparing a double and an int that have the same value returns true. In fact, the Equals method verifies the types of the 2 objects before comparing the values.

C# Equals for user-defined struct

In C#, all types can be boxed to the Object type. Therefore, you can use the Equals method to perform value type comparisons between two instances of a struct. In such a case, the code will compare the values of all public and private properties and fields of the 2 struct instances.

Here is an example that uses the same CarFeatures struct that we defined previously.

CarFeatures carFeatures1 = new CarFeatures()
{
    HasBluetooth = true,
    HasRearViewCamera = true,
    HasTractionControl = true
};

CarFeatures carFeatures2 = new CarFeatures()
{
    HasBluetooth = true,
    HasRearViewCamera = true,
    HasTractionControl = true
};

Console.WriteLine($"Object.Equals(carFeatures1, carFeatures2) returns {Object.Equals(carFeatures1, carFeatures2)}");

And the console output of the execution:

Console output for struct equality tests with object.equals

C# Equals for reference types

For reference types, the default implementation of the Equals method returns true if the two objects have the same reference. Basically, it will be the same result as using the Object.ReferenceEquals method.

In the following example, we will use the same Car and CarFeatures that were defined previously.

CarFeatures myCarFeatures = new CarFeatures()
{
    HasBluetooth = true,
    HasRearViewCamera = true,
    HasTractionControl = true
};

Car myCar = new Car()
{
    Make = "Camry",
    Model = "Toyota",
    NumberOfSeats = 5,
    Mileage = 10000,
    Features = myCarFeatures
};

Car myCarSecondPointer = myCar;

Car hisCar = new Car()
{
    Make = "X5",
    Model = "BMW",
    NumberOfSeats = 5,
    Mileage = 1000,
    Features = myCarFeatures
};

Console.WriteLine($"Object.Equals(myCar, myCarSecondPointer) returns {Object.Equals(myCar, myCarSecondPointer)}");
Console.WriteLine($"Object.ReferenceEquals(myCar, myCarSecondPointer) returns {Object.ReferenceEquals(myCar, myCarSecondPointer)}");
Console.WriteLine($"Object.Equals(myCar, hisCar)  returns {Object.Equals(myCar, hisCar) }");
Console.WriteLine($"Object.ReferenceEquals(myCar, hisCar)  returns {Object.ReferenceEquals(myCar, hisCar)}");

Here is the console output of that code sample:

Console output for reference type tests with object.equals

Overriding the Equals method

Since the Equals method is a virtual method, you can override it if necessary. If you do so, keep in mind that you need to override the GetHashCode method as well.

Here is an example of a class that overrides the Equals method:

public class Car: IEquatable<Car>
{
    public string Make { get; init; }
    public string Model { get; init; }
    public int NumberOfSeats { get; init; }
    public int Mileage { get; init; }
    public CarFeatures Features { get; init; }

    public bool Equals(Car otherCar)
    {
        if (otherCar is null)
            return false;

        //if the 2 are pointing to the same object,
        //no need to compare the values because it is the same object
        if (Object.ReferenceEquals(this, otherCar))
        {
            return true;
        }

        return Make == otherCar.Make && Model == otherCar.Model && NumberOfSeats == otherCar.NumberOfSeats 
            && Mileage == otherCar.Mileage && Object.Equals(Features, otherCar.Features);
    }
    public override bool Equals(object obj)
    {
        return Equals(obj as Car);
    }
    public override int GetHashCode()
    {
        return HashCode.Combine(Make, Model, NumberOfSeats, Mileage, Features);
    }

    public static bool operator ==(Car car1, Car car2)
    {
        if (car1 is null)
        {
            if (car2 is null)
            {
                return true;
            }

            return false;
        }

        return car1.Equals(car2);
    }

    public static bool operator !=(Car car1, Car car2) => !(car1 == car2);

}

In this example, we decided to compare the values of all properties when comparing two instances of the class. We also make sure that the two objects are instances of the same type. In addition, we did an overload of the equality operator at the same time. It is not mandatory to do so, but it is recommended to also implement this operator when you override the Equals method of a class.

Summary : Equals vs ==

To summarize, here are the differences between == and Object.Equals.

==Object.Equals
Built-in value typesCompare the valuesCompare the values and the types as well.
User-defined structuresThere is no default equality operator for user-defined structures. So you need to declare an overload of the == operator in order to be able to use it.Object.Equals can be used for structure through boxing. It will compare the values of fields and properties of the structures. It also verifies if the 2 struct are of the same type.
RecordsCompare the values of fields and properties of the records. It also verifies if the 2 records are of the same type.Compare the values of fields and properties of the records. It also verifies if the 2 records are of the same type.
String typeCompare the values of the two strings (same length and identical characters at the same position).Compare the values of the two strings (same length and identical characters at the same position).
Reference types (Classes)Compare the references of the 2 objects. The default implementation returns the same result as Object.ReferenceEquals.Compare the references of the 2 objects. The default implementation returns the same result as Object.ReferenceEquals.

I hope you enjoyed this article from our fundamentals series. You can find other articles with the fundamental tag here.