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).
Equalsshould anticipatenull, i.e.=> other is not null && this.KeyPart1 == other.KeyPart1 && this.KeyPart2 == other.KeyPart2;; you can also use a sneaky short-circuit in theobjectversion:public override bool Equals(object other) => other is MyObject typed && Equals(typed);IKey) to another method which takesIKeys (from a different implementation) and which normally works fine without anEqualityComparer.