Key comparisons for Linq GroupBy using Default EqualityComparer
Asked Answered
L

2

19

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
    }
}
Letterperfect answered 6/11, 2009 at 8:55 Comment(0)
E
20

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();
    }
Enthrall answered 6/11, 2009 at 11:58 Comment(2)
That was it. Thanks. I found a good explanation here: codeproject.com/KB/dotnet/IEquatable.aspxLetterperfect
Does this mean that you should have two Equals methods in FredKey: one for Equals(Object) and one for Equals(FredKey)?Tudela
3
7

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);  
}
3d answered 20/11, 2015 at 18:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.