Implementing IEqualityComparer<T> with .NET Core

Linq is very powerful and provides easy ways to manipulate and morph collections of objects. Some linq operations like Union, Except, Intersect and Distinct works on two collections, and need a way to compare items within collections to operate. If you are working with collections of basic types like int or string, NET Core framework will handle comparison all by itself. However, if you need to compare two complex types, like classes you have implemented over one or more of their properties, you have to tell the framework how to do it via an IEqualityComparer implementation.

Consider following class:


    public class Statistics
    {
        public DateTime DateUtc { get; set; }
        public decimal Price {get;set;}
        public int Quantity { get; set; }
    }

If you have two sets of statistics and want to unify them by comparing their Date fields you can do something like:


    {
        //some code...
        Statistics[] coll1 = GetStatisticsColl1();
        Statistics[] coll2 = GetStatisticsColl2();

        Statistics[] unifiedColl = coll1.Union(coll2, 
            new StatisticsDateComparer()).OrderBy(p => p.DateUtc).ToArray();
        //more code...
    }

In this code snippet StatisticsDateComparer is a class implementing IEqualityComparer<T>, telling NET Core framework how to compare two Statistics instances. To implement IEqualityComparer<T> interface, a class must implement two methods. Below GetHashCode implementation is almost the same code implemented by Resharper, you can change 397 with any prime number big enough to cause overflow in integer operations. Also you can use a completely different hash algorith if you like. Important factor is to avoid generating same hash code for different values as much as possible.


    using System.Diagnostics.CodeAnalysis;

    public class StatisticsDateComparer : IEqualityComparer<Statistics>
    {
        public bool Equals([AllowNull] Statistics x, [AllowNull] Statistics y)
        {
            if (ReferenceEquals(x, y)) return true;
            if (ReferenceEquals(x, null)) return false;
            if (ReferenceEquals(y, null)) return false;
            if (x.GetType() != y.GetType()) return false;

            //After above generic controls, tell how to compare two instances of Statistics.
            //If more fiels will be used for comparison add them to condition like so:
            // return x.DateUtc == y.DateUtc && x.FieldA == y.FieldA;
            return x.DateUtc == y.DateUtc;
        }

        public int GetHashCode([DisallowNull] Statistics obj)
        {
            unchecked
            {
                int result = (obj.DateUtc != null ? obj.DateUtc.GetHashCode() : 0);

                //if more fields will be used for comparison, add them to GetHashCode calculation like so:
                //^ is XOR operation
                //result = (result * 397) ^ (obj.FieldA != null ? obj.FieldA.GetHashCode() : 0);

                return result;
            }
        }
    }