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).
The GetHashCode()
used in equality is either:
object.GetHashCode()
, if no equality comparer is providedIEqualityComparer<T>.GetHashCode(T)
, if an equality comparer is providedAdding 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):
MyObject.GetHashCode()
provide the functionality you need, orMyObject
instanceThere are overloads of GroupBy
that accept an IEqualityComparer<TKey>
, for the second option.