1

I have an interface which defines a composite key:

public interface IKey : IEquatable<IKey>
{
    public bool KeyPart1 { get; }
    public uint KeyPart2 { get; }
    int GetHashCode(); // never gets called
}

I have an object (with an ID) to which I want to add the composite key interface:

public class MyObject: IEquatable<MyObject>, IKey
{
    public MyObject(int i, (bool keyPart1, uint keyPart2) key) {
    {
        Id=i;
        KeyPart1 = key.keyPart1;
        KeyPart2 = key.keyPart2;
    }
    
    public int Id { get; }
    public bool KeyPart1 { get; }
    public uint KeyPart2 { get; }

    public bool Equals(MyObject other) => this.Id == other.Id;

    public override bool Equals(object other) => other is MyObject o && Equals(o);
    public override int GetHashCode() => Id.GetHashCode();

    bool IEquatable<IKey>.Equals(IKey other) => this.KeyPart1 == other.KeyPart1
                                                && this.KeyPart2 == other.KeyPart2;
    int IKey.GetHashCode() => (KeyPart1, KeyPart2).GetHashCode(); // never gets called
}

However, when have a list of these objects and try to group them using the interface, the grouping fails:

var one = new MyObject(1, (true, 1));
var two = new MyObject(2, (true, 1));
var three = new MyObject(1, (false, 0));
var items = new[] { one, two, three };

var byId = items.GroupBy(i => i);
// result: { [one, three] }, { [two] } -- as expected

var byKey = items.GroupBy<MyObject, IKey>(i => i as IKey);

// result: { [one, two, three] } // not grouped (by 'id' or 'key')
// expected: { [one, two] }, { [three] }

I'd expected that byId would have the items grouped by the Id property, and byKey would have the items grouped by the Key property.

However, byKey is not grouped at all. It appears that the override GetHashCode() method is always used rather than the explicitly implemented interface method.

Is it possible to implement something like this, where the type of the item being grouped determines the hash method to use (avoiding an EqualityComparer)?

I noticed this problem when passing the cast objects to another method expecting an IEnumerable<IKey>. I have a few different types implementing IKey and those with an existing GetHashCode() method did not work, while the others did.

Please note the objects have been simplified here and that I cannot easily change the interfaces (e.g. to use ValueTuple instead).

3
  • 2
    Why do you want to avoid EqualityComparer? I would argue that is the way to go whenever you want more than one way to compare objects. Commented Jul 21, 2021 at 7:49
  • 1
    Unrelated tip: Equals should anticipate null, i.e. => other is not null && this.KeyPart1 == other.KeyPart1 && this.KeyPart2 == other.KeyPart2;; you can also use a sneaky short-circuit in the object version: public override bool Equals(object other) => other is MyObject typed && Equals(typed); Commented Jul 21, 2021 at 7:50
  • @JonasH Only because it's 'unexpected'; I actually pass the objects (cast to IKey) to another method which takes IKeys (from a different implementation) and which normally works fine without an EqualityComparer. Commented Jul 21, 2021 at 8:08

1 Answer 1

4

The GetHashCode() used in equality is either:

  • the one defined via object.GetHashCode(), if no equality comparer is provided
  • IEqualityComparer<T>.GetHashCode(T), if an equality comparer is provided

Adding your own GetHashCode() method on your own interface does nothing, and it will never be used, as it is not part of an API that the framework/library code knows about.

So, I'd forget about IKey.GetHashCode(), and either (or both):

  • make MyObject.GetHashCode() provide the functionality you need, or
  • provide a custom equality comparer separately to the MyObject instance

There are overloads of GroupBy that accept an IEqualityComparer<TKey>, for the second option.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.