IEqualityComparer is a very important interface for comparer tasks in the LinQ world. The next extended methods have an overload with this parameter type: Contains, Distinct, Except, Intersect, GrouBy, GroupJoin, Join, SecuenceEqual, ToDictionary, ToLookUp and Union.
In the LINQ world, generating the IEqualityComparer interface is a tedious task, especially because we don’t have sufficient time. Our generic class comes in handy for making things easier for us.
- Classic IEqualityComparer Implementation (for fields)
- Generic IEqualityComparer Implementation (for fields)
- Classic IEqualityComparer Implementation (for expression)
- Generic IEqualityComparer Implementation (for Expression)
- Code GenericIEqualityComparer class
Classic IEqualityComparer Implementation (for fields)
This example generates different customers for a sequence.
Class Customer
- public class Customer {
- public int ID {
- get;
- set;
- }
- public string Name {
- get;
- set;
- }
- public decimal Sales {
- get;
- set;
- }
- public string City {
- get;
- set;
- }
- public static IEnumerable < Customer > GetData() {
- return new List < Customer > () {
- new Customer {
- ID = 1, Name = "Philips", Sales = 2000000 m, City = "Madrid"
- },
- new Customer {
- ID = 2, Name = "Pionner", Sales = 1000000 m, City = "Berlin"
- },
- new Customer {
- ID = 3, Name = "Renault", Sales = 2000000 m, City = "Paris"
- },
- new Customer {
- ID = 4, Name = "Sony Music", Sales = 500000 m, City = "London"
- },
- new Customer {
- ID = 5, Name = "Sony SCEE", Sales = 2000000 m, City = "Tokio"
- },
- new Customer {
- ID = 6, Name = "Pepsi", Sales = 9000000 m, City = "New York"
- },
- new Customer {
- ID = 6, Name = "LG", Sales = 2000000 m, City = "Rome"
- }
- };
- }
- }
Note
Look, the 2 last customers have the same ID.
If we call a Distinct LinQ Extension Method, the result would be a new sequence with 7 elements, and we would not find any distinct member.
This is because Distinct compares instances for default, and all are different.
- using System;
- using System.Linq;
- using DAL;
- using static System.Console;
- namespace ConsoleClient {
- class Program {
- static void Main(string[] args) {
- var customers = Customer.GetData();
- WriteLine($ "There are {customers.Count()} customers.");
- var customersDifferents = customers.Distinct();
- WriteLine($ "There are {customersDifferents.Count()} DIFFERENT customers.");
- Console.Read();
- }
- }
- }
Now, think of a field or fields that differentiate our class, ID for example. IEqualityComparer helps you to manage and improve the efficiency
- public class CustomerForIDEqualityComparer: IEqualityComparer < Customer > {
- public bool Equals(Customer x, Customer y) {
- bool result = x.ID == y.ID;
- return result;
- }
- public int GetHashCode(Customer obj) {
- return obj.ID.GetHashCode();
- }
- }
For more information of IEqualityComparer classic implementation: Spanish - English
The IEqualityComparer class name tell us your purpose, different customers for a field ID. Could be more IEqualityComparer classes for different objective and different fields (ForCity, ForSales, etc), as we needed.
We use our IEqualityComparer class
- static void Main(string[] args) {
- var customers = Customer.GetData();
- WriteLine($ "There are {customers.Count()} customers.");
- var customersDifferents = customers.Distinct(new CustomerForIDEqualityComparer()).ToList();
- WriteLine($ "There are {customersDifferents.Count()} DIFFERENT customers.");
- Console.Read();
- }
Good work, the result is fine because there are 2 customers with the same ID.
Generic IEqualityComparer Implementation (for fields) In this moment, we’ll downloaded the GenericEqualityComparer in nuget.
We have 2 possibilities
Through menu AddReference
Or by Package Manager Console
Install-Package MoralesLarios.GenericEqualityComparer
The namespace for the class is
- using MoralesLarios.Generics
We will make the previous example, but we’ll use GenericEqualityComparer class. Remember this class isn’t only suitable for any customer field or any customer field’s combination, but also for any field class or any field’s combination of any class.
GenericEqualityComparer class has an overload with a Func<T, object> parameter, this type is the same as Select method (System.LinQ).
Example - static void Main(string[] args) {
- var customers = Customer.GetData();
-
- Func < Customer, object > fieldComparator = customer => customer.ID;
-
- GenericEqualityComparer < Customer > genericCustomerIEqualityComparer = new GenericEqualityComparer < Customer > (fieldComparator);
- WriteLine($ "There are {customers.Count()} customers.");
- var customersDifferents = customers.Distinct(genericCustomerIEqualityComparer).ToList();
- WriteLine($ "There are {customersDifferents.Count()} DIFFERENT customers.");
- Console.Read();
- }
The first step is to create the FieldComparator (Func<T, object>). With this variable we tell Field(s) is/are our virtual PK.
The second step is to instantiate the GenericEqualityComparer class. Your TypeParameter is Customer and your constructor parameter is the FieldComparator.
Result The same result, but it’s much more readable.
The same code, but the generic version, is available online.
Classic IEqualityComparer Implementation (for expression)
We’ll write a classic implementation of IEqualityComparer class. In our example, we need to compare customers. Two customers are distinct if your first char Name is different.
This is our IEqualityComparer class for expression.
- public class Customer1stCharNameComparer: IEqualityComparer < Customer > {
- public bool Equals(Customer x, Customer y) {
- bool result = x.Name ? [0] == y.Name ? [0];
- return result;
- }
- public int GetHashCode(Customer obj) {
- return obj.Name == null ? 0 : obj.Name[0].GetHashCode();
- }
- }
- We do the same example, but with the IEqualityComparer
- for
- expression now: static void Main(string[] args) {
- var customers = Customer.GetData();
- WriteLine($ "There are {customers.Count()} customers.");
- var customersDifferents = customers.Distinct(new Customer1stCharNameComparer()).ToList();
- WriteLine($ "There are {customersDifferents.Count()} DIFFERENT customers.");
- Console.Read();
- }
Result Four different customers start with: P, R, S and L.
Generic IEqualityComparer Implementation (for expression)
For Expression is a broader scope, and doesn’t have limits.
GenericEqualityComparer has an overload with a Func<T, T, bool>, this parameter shows the comparison expression.
This is the same previous example, with GenericEqualityComparer.
- static void Main(string[] args) {
- var customers = Customer.GetData();
-
- Func < Customer, Customer, bool > expressionComparator = (customer1, customer2) => customer1.Name ? [0] == customer2.Name ? [0];
-
- GenericEqualityComparer < Customer > genericCustomerIEqualityComparer = new GenericEqualityComparer < Customer > (expressionComparator);
- WriteLine($ "There are {customers.Count()} customers.");
- var customersDifferents = customers.Distinct(genericCustomerIEqualityComparer).ToList();
- WriteLine($ "There are {customersDifferents.Count()} DIFFERENT customers.");
- Console.Read();
- }
The same result As in the previous case, the same result, but it’s much more readable.
The same code, but generic version, it online code.
Code GenericIEqualityComparer class
I’ll try to explain the code of GenericEqualityComparer.
This is the code
- using System;
- using System.Collections.Generic;
- namespace CodComun {
- public class GenericEqualityComparer < T > : IEqualityComparer < T > {
- private Func < T,
- object > _virtualFieldComparator;
- private Func < T,
- T,
- bool > _virtualFilterComparator;
- public GenericEqualityComparer(Func < T, object > virtualFieldComparator) {
- if (virtualFieldComparator == null) throw new ArgumentNullException(nameof(virtualFieldComparator), $ "{nameof(virtualFieldComparator)} doesn't be null");
- Reset();
- this._virtualFieldComparator = virtualFieldComparator;
- }
- public GenericEqualityComparer(Func < T, T, bool > virtualFilterComparator) {
- if (virtualFilterComparator == null) throw new ArgumentNullException(nameof(virtualFilterComparator), $ "{nameof(virtualFilterComparator)} doesn't be null");
- Reset();
- this._virtualFilterComparator = virtualFilterComparator;
- }
- private void Reset() {
- _virtualFieldComparator = null;
- _virtualFilterComparator = null;
- }
- public bool Equals(T x, T y) {
- bool result = false;
- if (_virtualFieldComparator != null) result = _virtualFieldComparator(x).Equals(_virtualFieldComparator(y));
- else result = _virtualFilterComparator(x, y);
- return result;
- }
- public int GetHashCode(T obj) {
- int result = 0;
- if (_virtualFieldComparator != null) result = _virtualFieldComparator(obj).GetHashCode();
- else result = _virtualFilterComparator(obj, obj).GetHashCode();
- return result;
- }
- }
- }
First, this class implements the IEqualityComparer<T>, as the classics implementations.
It has two private fields. This fields store (dependency injection mode) the value for your two constructors’ parameters. Each represent the two modes of class: For Field or For Expression, therefore one will be always null.
It has two constructors, to enable For Field mode or For Expression mode.
The private method Reset, resets all modes.
Finally, the two public methods, Equals and GetHashCode implement IEqualityComparer<T>, and use the encapsulate actions of their two private fields.
Hope this article will help you to understand the IEqualityComparer<T> interface. Don't hesitate to write questions or suggestions.