LINQ lambda expressions in universal generic equality comparer


Implementation

Below you can find implementation we use as universal equality comparer. Only lambda expressions as in LINQ queries are needed to measure items. Class LambdaComparer is generic, so you can use it on any type you want to.

LambdaComparer class

using System;
using System.Collections.Generic;
 
namespace Karmian.Framework.Helpers
{
    public class LambdaComparer<T> : IEqualityComparer<T>
    {
        public LambdaComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
        {
            _equals = equals;
            _getHashCode = getHashCode;
        }
 
        readonly Func<T, T, bool> _equals;
        public bool Equals(T x, T y)
        {
            return _equals(x, y);
        }
 
        readonly Func<T, int> _getHashCode;
        public int GetHashCode(T obj)
        {
            return _getHashCode(obj);
        }
    } 
}
Imports System.Collections.Generic
 
Namespace Karmian.Framework.Helpers
  Public Class LambdaComparer(Of T)
    Implements IEqualityComparer(Of T)
    Public Sub New(equals As Func(Of T, T, Boolean), getHashCode As Func(Of T, Integer))
      _equals = equals
      _getHashCode = getHashCode
    End Sub
 
    ReadOnly _equals As Func(Of T, T, Boolean)
    Public Overloads Function Equals(x As T, y As T) As Boolean Implements IEqualityComparer(Of T).Equals
      Return _equals(x, y)
    End Function
 
    ReadOnly _getHashCode As Func(Of T, Integer)
    Public Overloads Function GetHashCode(obj As T) As Integer Implements IEqualityComparer(Of T).GetHashCode
      Return _getHashCode(obj)
    End Function
  End Class
End Namespace

Usage example

City class used as comparison item

public class City
{
    public string Name { get; set; }
    public int Population { get; set; }
 
    public override string ToString()
    {
        return string.Format("\tName: {0}\t, Population: {1}", Name, Population);
    }
}
Public Class City
  Public Property Name() As String
    Get
      Return m_Name
    End Get
    Set
      m_Name = Value
    End Set
  End Property
  Private m_Name As String
  Public Property Population() As Integer
    Get
      Return m_Population
    End Get
    Set
      m_Population = Value
    End Set
  End Property
  Private m_Population As Integer
 
  Public Overrides Function ToString() As String
    Return String.Format(vbTab & "Name: {0}" & vbTab & ", Population: {1}", Name, Population)
  End Function
End Class

Application showing typical use

class Program
{
    private static void Main()
    {
        var cities = CreateCities();
        Console.WriteLine("Normal listing:");
        foreach (var city in cities)
            Console.WriteLine(city);
        Console.WriteLine("Press any key to continue...\n");
        Console.ReadKey(true);
 
        Console.WriteLine("Distinct by name:");
        foreach (var city in cities.Distinct(
            new LambdaComparer<City>(
                (c1, c2) => c1.Name.Equals(c2.Name),
                c => 1 /* force compare other than by instance */)))
            Console.WriteLine(city);
        Console.WriteLine("Press any key to continue...\n");
        Console.ReadKey(true);
 
        Console.WriteLine("More than one property is doubled:");
        foreach (var city in cities.Distinct(
            new LambdaComparer<City>(
                (c1, c2) => c1.Population.Equals(c2.Population) || c2.Name.Equals(c2.Name),
                c => 1 /* force compare other than by instance */)))
            Console.WriteLine(city);
        Console.WriteLine("Press any key to continue...\n");
        Console.ReadKey(true);
 
        Console.WriteLine("Contains city with population of 789? :");
        Console.WriteLine(cities.Contains(
            new City { Name = "City789", Population = 789 },
            new LambdaComparer<City>(
                (c1, c2) => c1.Population.Equals(c2.Population),
                c => c.GetHashCode() /* instance has nothing to do here */)));
        Console.WriteLine("Press any key to continue...\n");
        Console.ReadKey(true);
 
        Console.WriteLine("HashSet with equality set to City.Name property? :");
        var hashSet = new HashSet<City>(
            new LambdaComparer<City>(
                (c1, c2) => c1.Name.Equals(c2.Name),
                c => 1 /* force compare other than by instance */))
			        {
			            new City {Name = "aaa"},
			            new City {Name = "aba"},
			            new City {Name = "aaa"},
			            new City {Name = "baa"},
			            new City {Name = "aba"}
			        };
        foreach (var city in hashSet)
            Console.WriteLine(city);
        Console.WriteLine("Press any key to exit...\n");
        Console.ReadKey(true);
    }
 
    private static IEnumerable<City> CreateCities()
    {
        return new List<City>
			{
			    new City { Name = "City1", Population = 123 },
			    new City { Name = "City2", Population = 456 },
			    new City { Name = "City1", Population = 789 },
			    new City { Name = "City3", Population = 123 },
			    new City { Name = "City4", Population = 987 },
			    new City { Name = "City4", Population = 654 }
			};
    }
}
Class Program
  Private Shared Sub Main()
    Dim cities = CreateCities()
    Console.WriteLine("Normal listing:")
    For Each city As var In cities
      Console.WriteLine(city)
    Next
    Console.WriteLine("Press any key to continue..." & vbLf)
    Console.ReadKey(True)
 
    Console.WriteLine("Distinct by name:")
      ' force compare other than by instance 
    For Each city As var In cities.Distinct(New LambdaComparer(Of City)(Function(c1, c2) c1.Name.Equals(c2.Name), Function(c) 1))
      Console.WriteLine(city)
    Next
    Console.WriteLine("Press any key to continue..." & vbLf)
    Console.ReadKey(True)
 
    Console.WriteLine("More than one property is doubled:")
      ' force compare other than by instance 
    For Each city As var In cities.Distinct(New LambdaComparer(Of City)(Function(c1, c2) c1.Population.Equals(c2.Population) OrElse c2.Name.Equals(c2.Name), Function(c) 1))
      Console.WriteLine(city)
    Next
    Console.WriteLine("Press any key to continue..." & vbLf)
    Console.ReadKey(True)
 
    Console.WriteLine("Contains city with population of 789? :")
      ' instance has nothing to do here 
    Console.WriteLine(cities.Contains(New City() With { _
      Key .Name = "City789", _
      Key .Population = 789 _
    }, New LambdaComparer(Of City)(Function(c1, c2) c1.Population.Equals(c2.Population), Function(c) c.GetHashCode())))
    Console.WriteLine("Press any key to continue..." & vbLf)
    Console.ReadKey(True)
 
    Console.WriteLine("HashSet with equality set to City.Name property? :")
      ' force compare other than by instance 
    Dim hashSet = New HashSet(Of City)(New LambdaComparer(Of City)(Function(c1, c2) c1.Name.Equals(c2.Name), Function(c) 1)) From { _
      New City() With { _
        Key .Name = "aaa" _
      }, _
      New City() With { _
        Key .Name = "aba" _
      }, _
      New City() With { _
        Key .Name = "aaa" _
      }, _
      New City() With { _
        Key .Name = "baa" _
      }, _
      New City() With { _
        Key .Name = "aba" _
      } _
    }
    For Each city As var In hashSet
      Console.WriteLine(city)
    Next
    Console.WriteLine("Press any key to exit..." & vbLf)
    Console.ReadKey(True)
  End Sub
 
  Private Shared Function CreateCities() As IEnumerable(Of City)
    Return New List(Of City)() From { _
      New City() With { _
        Key .Name = "City1", _
        Key .Population = 123 _
      }, _
      New City() With { _
        Key .Name = "City2", _
        Key .Population = 456 _
      }, _
      New City() With { _
        Key .Name = "City1", _
        Key .Population = 789 _
      }, _
      New City() With { _
        Key .Name = "City3", _
        Key .Population = 123 _
      }, _
      New City() With { _
        Key .Name = "City4", _
        Key .Population = 987 _
      }, _
      New City() With { _
        Key .Name = "City4", _
        Key .Population = 654 _
      } _
    }
  End Function
End Class

Output

Normal listing:
        Name: City1     , Population: 123
        Name: City2     , Population: 456
        Name: City1     , Population: 789
        Name: City3     , Population: 123
        Name: City4     , Population: 987
        Name: City4     , Population: 654
Press any key to continue...

Distinct by name:
        Name: City1     , Population: 123
        Name: City2     , Population: 456
        Name: City3     , Population: 123
        Name: City4     , Population: 987
Press any key to continue...

More than one property is duplicated:
        Name: City1     , Population: 123
Press any key to continue...

Contains city with population of 789? :
True
Press any key to continue...

HashSet with equality set to City.Name property :
        Name: aaa       , Population: 0
        Name: aba       , Population: 0
        Name: baa       , Population: 0
Press any key to exit...
AttachmentSize
[binaries] KarmianEqualityComparer.zip41.67 KB
[sources] KarmianEqualityComparer.zip46.45 KB