In this article I will discuss the two most common ways to compare the value type and reference types in C# in .NET. This can help you to program a bug free solution and not getting any surprises. You can refer here to know about the value types and reference types.
System.Object contains an virtual method known as Equals. It also contains the operator overloading for the equality operator “==”.
Equality For Reference Type
Equality Using System.Object Equals() Method
- System.Object contains a virtual Equals() method.The base class implementation always checks for reference equality. It can be overridden in the derived class as shown in the below code
public class Person { public string Name { get; set; } public override bool Equals(object obj) { if (obj == null) return false; if (ReferenceEquals(obj, this)) return true; if (obj.GetType() != this.GetType()) return false; Person per = obj as Person; return this.Name == per.Name; } public override int GetHashCode() { return Name.GetHashCode(); }
in the above code I am overriding the Equals method to check Person class equality only for Name field.
- ReferenceEquals() static method of the object class is used to only compare the reference of the two objects unlike Equals() methods, which can be overridden in the derived class to provide its own implementation of equality. The best example for this would be to compare two strings whose values are equal.
- The exception of the above rule is the String class. It overrides the Equals method to compare two strings using contents. And checks if two strings contain same characters in same sequence. This can be confirmed by below code.This comparison is case sensitive
string str1 = "string1"; string str2 = string.Copy(str1); Console.WriteLine(str1.Equals(str2)); //true
- Just like string, this reference exception is same for delegates as well as tuples.
- Equals() static method of the object checks for the nulls. It is better to use this method as it checks for the null and also it calls the overridden Equals method if the derived class provides its own implementation of equality.
Person per1 = new Person() { Name = "Vikram" }; Person per2 = new Person() { Name = "Vikram" }; object.Equals(per1, per2);
The above code will call the overridden method of the person class. But there is no need for us to check for the null reference in this case. We can safely remove the null reference checking from the method in above snippet
Equality using “==” operator
- Equality operator is not part of .NET framework. It is provided by C# language.
- “==” equality operator for reference types check for the reference. It checks if the two operands point to same memory location.
- “==” checks the content of the string as Microsoft has designed it be that way and it is quite logical, if when we are checking the string we should be checking for the content.
- The equality operator is case sensitive for strings
- We should not use the equality operator for the floating point numbers due to rounding errors. Please check the below code for example.
float f1 = 6.45f; float f2 = 0.55f; Console.WriteLine(f1 + f2 == 7.0f); //false
- “==” operator should be used with care as shown in the below code example.
string str1 = "string1"; string str2 = string.Copy((string)str1); Console.WriteLine(str1 == str2); //true object obj1 = "string1"; object obj2 = string.Copy((string)obj1); Console.WriteLine(obj1 == obj2); //false object obj1 = "string1"; object obj2 = string.Copy((string)obj1); Console.WriteLine(obj1.Equals(obj2)); //true
In the first example the string class provides an overload of the “==” operator which also compares the contents, it returns true.
In the second example we have replaced the string with object and equality operator for object compares the references. The code in second case is resolved at compile time to call the object’s “==” operator.
If we replace the equality operator with Equals method. The result will be true. It calls the overridden Equals method of string class at run time. This is the third case.
Equality For Value Types
- Object.Equals works differently for the Value types(e.g. structures). Value type are derived from System.Value type which in turn is derived from System.Object. But the overridden version of Equals of the System.Value type compares the values of all the properties of the structure. And if all properties value are equal per1.Equals(per2) returns true. Please see the below code as example.
public struct NewPerson { public string Name { get; set; } } NewPerson per1 = new NewPerson() { Name = "Vikram" }; NewPerson per2 = new NewPerson() { Name = "Vikram" }; Console.WriteLine(per1.Equals(per2)); // true
- However the above behavior of Equals method for the value types comes with a price. Since System.Value type does not know the fields of the Structure which we have created, that is why it uses reflection to identify the fields and does the comparison. It hits the performance. Therefore it is always advisable to define the overridden implementation of the Equal in case of value types. Hence the correct version of the above NewPerson structure would be as below
public struct NewPerson { public string Name { get; set; } public override bool Equals(object obj) { NewPerson? per = null; if (obj is NewPerson) { per = (NewPerson)obj; return this.Name == per.Value.Name; } else return false; } public override int GetHashCode() { return Name.GetHashCode(); } }
- Problems with Object.Equals() are if we are using the System.Objects() equals method for the comparison of the value type there will be a performance hit as value type will be boxed to the object. The whole purpose of creating a value type is gone. There is no out of box type safety as any instance can be cast to the object type which is the parameter of the Equals method.
- To cope with these drawbacks IEquatable
interface is introduced by microsoft. We do not need boxing and comparing type is strongly typed. All the primitive value type implement this interface. If we are implementing the IEquatable interface in that case we should be make sure that we should override Object.Equals and both of them should have the same implementation to maintain equality consistency as shown in below code. public struct NewPerson:IEquatable
{ public string Name { get; set; } public bool Equals(NewPerson other) { NewPerson per = (NewPerson)other; return this.Name == per.Name; } public override bool Equals(object obj) { NewPerson? per = null; if (obj is NewPerson) { per = (NewPerson)obj; return this.Name == per.Value.Name; } else return false; } public override int GetHashCode() { return Name.GetHashCode(); } } - The “==” implementation is not provided for the structs which we can confirm by comparing two structs.
How the Equality Comparer(“==”) compares the Values
Please check the below code.
int i = 1; int i1 = 2; i.Equals(i1); if(i == i1) { }
The IL code generated for the above two equality comparisons is as shown in the below figure.
As seen in the above code the Equals method calls the Eqauls method of the integer. While the “==” operator calls the ceq instruction.
This instruction compares the values directly from hardware using CPU’s register. It checks the values in both the registers are different in our case.
If we repeat the above process for the reference type i.e comparing two reference variables using Object.Equals() and “==”. For both the cases the result will be false as we know that both of these comparisons use reference equality.
But if we check the ildasm code for both the comparison we will see that “==” operator emits code to check the equality using ceq as in the previous case. But since the operands are the reference type the registers will contain the address location in the memory which is different for two different references.
Conclusion:
I hope I have covered enough for the equality for the value types and reference types in this article. This is the simple topic but can create some undesired results in the C# application if we are not aware about the internals of equality.
Reference
PluralSight : C# Equality and Comparison
Dmitry Bytchenko says
Instead of current Equals method in Person class
…
if (obj == null)
return false;
if (ReferenceEquals(obj, this))
return true;
if (obj.GetType() != this.GetType()) // first…
return false;
Person per = obj as Person; // and second type check
…
A more efficient code is possible
if (ReferenceEquals(obj, this))
return true;
Person other = obj as Person;
// if obj is null or of wrong type
if (null == other)
return false;
Another problem with Person class is in GetHashCode method
public override int GetHashCode()
{
return Name.GetHashCode();
}
What if Name is null? Ternary operator is a typical way out:
public override int GetHashCode()
{
return null == Name ? 0 : Name.GetHashCode();
}