When working with LINQ, often one has to pass in an IEqualityComparer of the class(es) being used. Implementing
IEqualityComparer on classes can sometimes be unwanted to just make the computations work, therefore a generic
implementation would be nice to invoke when performing the LINQ expression without either adding IEqualityComparer
or worse - having to rewrite it. Sometimes also multiple implementations are desired..
The following class
LambdaComparer is a generic implementation of IEqualityComparer of T.
using System;
using System.Collections.Generic;
namespace Hemit.OpPlan.Client.Infrastructure.Utility
{
/// <summary>
/// LambdaComparer - avoids the need for writing custom IEqualityComparers
///
/// Usage:
///
/// List<MyObject> x = myCollection.Except(otherCollection, new LambdaComparer<MyObject>((x, y) => x.Id == y.Id)).ToList();
///
/// or
///
/// IEqualityComparer comparer = new LambdaComparer<MyObject>((x, y) => x.Id == y.Id);
/// List<MyObject> x = myCollection.Except(otherCollection, comparer).ToList();
///
/// </summary>
/// <typeparam name="T">The type to compare</typeparam>
public class LambdaComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _lambdaComparer;
private readonly Func<T, int> _lambdaHash;
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer, o => 0)
{
}
public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash)
{
if (lambdaComparer == null)
{
throw new ArgumentNullException("lambdaComparer");
}
if (lambdaHash == null)
{
throw new ArgumentNullException("lambdaHash");
}
_lambdaComparer = lambdaComparer;
_lambdaHash = lambdaHash;
}
public bool Equals(T x, T y)
{
return _lambdaComparer(x, y);
}
public int GetHashCode(T obj)
{
return _lambdaHash(obj);
}
}
}
The following unit tests uses this implementation of a generic IEqualityComparer of T:
using System;
using System.Collections.Generic;
using System.Linq;
using Hemit.OpPlan.Client.Infrastructure.Utility;
using NUnit.Framework;
namespace Hemit.OpPlan.Client.Infrastructure.Test.Utility
{
[TestFixture]
public class LambdaComparerTest
{
[Test]
public void LambdaComparerPerformsExpected()
{
var countriesFirst = new List<Tuple<int, string>>{
new Tuple<int, string>(1, "Spain"),
new Tuple<int, string>(3, "Brazil"),
new Tuple<int, string>(5, "Argentina"),
new Tuple<int, string>(6, "Switzerland"),
new Tuple<int, string>(7, "Uruguay"),
new Tuple<int, string>(8, "Colombia")
};
var countriesSecond = new List<Tuple<int, string>>{
new Tuple<int, string>(1, "Spain"),
new Tuple<int, string>(4, "Portugal"),
new Tuple<int, string>(7, "Uruguay"),
new Tuple<int, string>(10, "England"),
new Tuple<int, string>(11, "Belgium"),
new Tuple<int, string>(12, "Greece")
};
var expected = new List<Tuple<int, string>>
{
new Tuple<int, string>(3, "Brazil"),
new Tuple<int, string>(5, "Argentina"),
new Tuple<int, string>(6, "Switzerland"),
new Tuple<int, string>(8, "Colombia")
};
var countriesOnlyInFirst = countriesFirst.Except(countriesSecond, new LambdaComparer<Tuple<int, string>>((x, y) => x.Item1 == y.Item1));
CollectionAssert.AreEqual(countriesOnlyInFirst, expected);
}
}
}
In the unit test above, two lists containing the topmost FIFA ranking country teams in soccer in the world are being used in a LINQ
Except expression, where the generic LambdaComparer class is being used. The class being passed in is Tuple of int and string:
Tuple<int,string> - Note that an ordinary class could also be used here. The property Item1 of the Tuple is the "key" that is being
used to compare two different objects - if it is the same, the objects are being considered to be the same. This results into a list of
items from the first country list that are not being present in the second list. Finally, a list of the expected result is being built up and
the NUnit CollectionAssert.AreEqual method is used for checking consistent result. The test passes.
Much of .NET requires implementing interfaces such as IEqualityComparer, IComparer and more. Using a generic implementation class that expects
Func expressions (the lambda expressions being passed, is a pattern that can give much flexibility.