Object Equality Schematics
Equals(Object) method override
- override Equals(Object) method let you to check if two different object instances are the same object from their content point of view, using a.Equals(b).
Equality and inequality operators overload
- when override Equals(Object) method is a good practice to override the equality == and inequality != operators to avoid mistake if never mind to use the Equals method.
IEquatable<T> interface implementation
- when override Equals(Object) method is a good practice to implements the IEquatable<T> interface and call the Equals(T) method from the Equals(Object) to increment efficiency avoiding box-unboxing from Object to T type.
Collections HashSet and Dictionary
- overriding of the Equals method in objects allow to achieve follow results for Equals objects :
- HashSet avoid double insertion.
- Dictionary find object key already present using Equals objects not inserted in the dictionary.
GetHashCode
- when override Equals method for use of objects in collections :
- must implement GetHashCode or as default it will declare two object different regardless they are equal using Equals method.
- when two objects have the same hashcode then a second detailed check will ensure they equals using Equals method :
- two different object can have the same hash code ( this of course decrease performance cause more detailed equals check will be done eg. key collisions ).
- two equals object must have the same hash code or the equals method check will skipped and they are stated as different.
Sample unit test code
Without Equals(Object)
using System.Diagnostics;
namespace TestHashCode
{
class Program
{
static void Main(string[] args)
{
var a = new Test() { value = 10 };
var b = new Test() { value = 10 };
Debug.Assert(!a.Equals(b));
}
}
public class Test
{
public int value { get; set; }
}
}
With Equals(Object) and without equality inequality operators
using System.Diagnostics;
namespace TestHashCode
{
class Program
{
static void Main(string[] args)
{
var a = new Test() { value = 10 };
var b = new Test() { value = 10 };
Debug.Assert(!ReferenceEquals(a, b)); // different instances
Debug.Assert(a != b); // operator not overloaded
Debug.Assert(a.Equals(b)); // same equals object
}
}
public class Test
{
public int value { get; set; }
public override bool Equals(object obj)
{
return value == ((Test)obj).value;
}
}
}
With Equals(Object) and with equality inequality operators
using System.Diagnostics;
namespace TestHashCode
{
class Program
{
static void Main(string[] args)
{
var a = new Test() { value = 10 };
var b = new Test() { value = 10 };
Debug.Assert(!ReferenceEquals(a, b)); // different instances
Debug.Assert(a == b); // operator overloaded
Debug.Assert(a.Equals(b)); // same equals object
}
}
public class Test
{
public int value { get; set; }
public override bool Equals(object obj)
{
return value == ((Test)obj).value;
}
public static bool operator ==(Test x, Test y)
{
return x.Equals(y);
}
public static bool operator !=(Test x, Test y)
{
return !x.Equals(y);
}
}
}
With Equals(T)
Test results (in Release mode) :
- with Equals(Object) : ~5 sec
- with Equals(T) : ~1 sec
using System;
using System.Diagnostics;
namespace TestHashCode
{
class Program
{
static void Main(string[] args)
{
var a = new Test() { value = 10 };
var b = new Test() { value = 10 };
var c = 0;
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1500000000; ++i)
{
if (a == b) ++c;
}
Console.WriteLine(sw.Elapsed);
}
}
public class Test : IEquatable<Test>
{
public int value { get; set; }
public bool Equals(Test other)
{
return value == other.value;
}
public override bool Equals(object obj)
{
return Equals((Test)obj); // recall Equals(T)
}
public static bool operator ==(Test x, Test y)
{
return x.Equals(y);
}
public static bool operator !=(Test x, Test y)
{
return !x.Equals(y);
}
}
}
Without GetHashCode
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace TestHashCode
{
class Program
{
static void Main(string[] args)
{
var a = new Test() { value = 10 };
var b = new Test() { value = 10 };
var hs = new HashSet<Test>();
hs.Add(a);
// element b will added cause GetHashCode returns different values
// for different objects as default
hs.Add(b);
Debug.Assert(hs.Count == 2);
var dict = new Dictionary<Test, int>();
dict.Add(a, a.value);
Debug.Assert(!dict.ContainsKey(b));
}
}
public class Test : IEquatable<Test>
{
public int value { get; set; }
public bool Equals(Test other)
{
return value == other.value;
}
public override bool Equals(object obj)
{
return Equals((Test)obj); // recall Equals(T)
}
public static bool operator ==(Test x, Test y)
{
return x.Equals(y);
}
public static bool operator !=(Test x, Test y)
{
return !x.Equals(y);
}
}
}
With GetHashCode
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace TestHashCode
{
class Program
{
static void Main(string[] args)
{
var a = new Test() { value = 10 };
var b = new Test() { value = 10 };
var hs = new HashSet<Test>();
hs.Add(a);
// element b will not added despite its different instance
hs.Add(b);
Debug.Assert(hs.Count == 1);
var dict = new Dictionary<Test, int>();
dict.Add(a, a.value);
Debug.Assert(dict.ContainsKey(b));
}
}
public class Test : IEquatable<Test>
{
public int value { get; set; }
public bool Equals(Test other)
{
return value == other.value;
}
public override bool Equals(object obj)
{
return Equals((Test)obj); // recall Equals(T)
}
public static bool operator ==(Test x, Test y)
{
return x.Equals(y);
}
public static bool operator !=(Test x, Test y)
{
return !x.Equals(y);
}
public override int GetHashCode()
{
return value;
}
}
}
Reference material
- GetHashCode ( https://msdn.microsoft.com/it-it/library/system.object.gethashcode(v=vs.110).aspx ) shows methods to compose hash codes for structured objects.
C# Objects Equality by Lorenzo Delana is licensed under a Creative Commons Attribution 4.0 International License.
No comments:
Post a Comment