Question

I'm trying to do a Linq GroupBy on some objects using an explicit key type. I'm not passing an IEqualityComparer to the GroupBy, so according to the docs:

The default equality comparer Default is used to compare keys.

It explains the EqualityComparer<T>.Default property like this:

The Default property checks whether type T implements the System.IEquatable<T> generic interface and if so returns an EqualityComparer<T> that uses that implementation.

In the code below, I'm grouping an array of Fred objects. They have a key type called FredKey, which implements IEquatable<FredKey>.

That should be enough to make the grouping work, but the grouping is not working. In the last line below I should have 2 groups, but I don't, I just have 3 groups containing the 3 input items.

Why is the grouping not working?

class Fred
{
    public string A;
    public string B;
    public FredKey Key
    {
        get { return new FredKey() { A = this.A }; }
    }
}

class FredKey : IEquatable<FredKey>
{
    public string A;
    public bool Equals(FredKey other)
    {
        return A == other.A;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var f = new Fred[]
        {
            new Fred {A = "hello", B = "frog"},
            new Fred {A = "jim", B = "jog"},
            new Fred {A = "hello", B = "bog"},
        };

        var groups = f.GroupBy(x => x.Key);
        Debug.Assert(groups.Count() == 2);  // <--- fails
    }
}
Was it helpful?

Solution

From MSDN

If you implement IEquatable, you should also override the base class implementations of Object::Equals(Object) and GetHashCode() so that their behavior is consistent with that of the IEquatable::Equals method. If you do override Object::Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. This ensures that all invocations of the Equals() method return consistent results.

add this to FredKey and it should work

public override int GetHashCode()
    {
        return A.GetHashCode();
    }

OTHER TIPS

Here is a complete example with a Fiddle. Note: the example differs slightly from the question's example.

The following implementation of IEquatable can act as the TKey in GroupBy. Note that it includes both GetHashCode and Equals.

public class CustomKey : IEquatable<CustomKey>
{
    public string A { get; set; }
    public string B { get; set; }

    public bool Equals(CustomKey other)
    {
        return other.A == A && other.B == B;
    }

    public override int GetHashCode()
    {
        return string.Format("{0}{1}", A, B).GetHashCode();
    }
}

public class Custom
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
}

public static void Main()
{
    var c = new Custom[]
       {
           new Custom {A = "hello", B = "frog" },
           new Custom {A = "jim", B = "jog" },
           new Custom {A = "hello", B = "frog" },
       };

    var groups = c.GroupBy(x => new CustomKey { A = x.A, B = x.B } );
       Console.WriteLine(groups.Count() == 2);  
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top