Linq lambda expression IEqualityComparer for IEnumerable.Distinct and Except

One of the things that annoys me with the IEnumerable.Distinct method is that it has no overload allowing you to give a lambda expression to specify a particular property to perform the distinction, you have to give an IEqualityComparer.

I did a quick Google search and found this post. The guy here provides the following LambdaComparer class:

private 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);
     }
}

He is using this for the Except extension method, but it works equally well with the Distinct method. Here is an extension method using this class to provide the new overloaded Distinct method I wanted.

public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> enumerable, Func<TSource, TSource, bool> comparer)
{
    return enumerable.Distinct(new LambdaComparer<TSource>(comparer));
}

And here is how I can use it.

List<User> users = UserService.Create().GetUsers().Distinct((x, y) => x.Id == y.Id).ToList();

The above would return a distinct list of users, using the Id property of the User object to perform the distinction.

Posted on by Joe in C#, Linq

3 Responses to Linq lambda expression IEqualityComparer for IEnumerable.Distinct and Except

  1. Jason

    Assume that the User class contains:
    int Id,
    string Name,
    List Rights;

    and the GetUsers() method returns the records:
    1, “Alice”, {Read, Write},
    1, “Alice”, {Read, Write, Delete},
    2, “Bob”, {Read}.

    When we call distinct and use the Id to specify equality, then aren’t we saying that we do not care what values are in the other fields? In my example above the Rights for the second User with Id 1 will not be returned.

    If we do not care about the other attributes then couldn’t we use projection to return only, say, the Id and then distinct on that?

    Maybe the example you chose doesn’t fully convey the usefulness of the approach; I am having a hard time seeing how it would be useless.

    Not trying to start a fight, just a sincere question :)

  2. Joe

    Hi Jason

    I guess the example could have done with a bit more explanation.

    Say for example I have a couple of user objects that implement IUser; Administrator and Manager, both objects would share common properties for the user, then have a few extras specific to that type.

    I could have service methods called GetAdministrators and GetManagers which return List. A user could be an Admin and a Manager so therefore both methods may return the same user details.

    If I then have another service method that returns both Administrators and Managers combined for whatever reason, maybe I’m performing some authentication that allows Administrators or Managers, it would contain a Administrator object and a Manager object for the same user where only the user related properties match, so performing a standard .Distinct here wouldn’t work. I could write an comparer for these specific objects, but using this I can quickly filter out based on the User ID.

    Still not sure if that’s really a good example, but a project I worked on did something very similar, and using this solved my problem.

    Cheers
    Joe

  3. Pingback: Digital~Infamy | Linq LambdaComparisons

Add a Comment