Why is Distinct not working in this LINQ query?

In the query below, the Distinct call doesn't seem to be doing anything:

using (var db = new AccountsDbContext())
{
    var uniques = db.AccountMappings
        .Select(m => new CanvasAccount { UserName = m.CanvasUser, Password = m.CanvasPassword })
        .OrderBy(m => m.UserName)
        .ToList()
        .Distinct(new CanvasAccountComparer());
    accounts.AddRange(uniques);
}

This query returns three CanvasAccount objects, but two of those have exactly the same username and password. My comparer is as follows:

class CanvasAccountComparer : IEqualityComparer<CanvasAccount>
{
    public bool Equals(CanvasAccount x, CanvasAccount y)
    {
        return (x.UserName.ToLower() == y.UserName.ToLower()) && (x.Password == y.Password);
    }

    public int GetHashCode(CanvasAccount obj)
    {
        return obj.GetHashCode();
    }
}
Jon Skeet
people
quotationmark

Your comparer is broken - two objects which are equal don't necessarily return the same hash code. Equality and hash code generation have to be consistent with each other. You can fix this using something like:

public int GetHashCode(CanvasAccount obj)
{
    int hash = 23;
    hash = hash * 31 + obj.UserName.ToLower().GetHashCode();
    hash = hash * 31 + obj.Password.GetHashCode();
    return hash;
}

Note:

  • Using ToLower() isn't really a good way of performing case-insensitive comparisons. There are all kinds of oddities around culture here. Ideally, use StringComparer instead.
  • The code above assumes that Password and UserName will both be non-null. If either can be null, you'll need to check for that.
  • I hope this code doesn't mean that you're using plain-text passwords. Note that if the passwords are hashd and salted, two equal plain-text passwords are likely to have different hashed reprsentations.

people

See more on this question at Stackoverflow