简体   繁体   中英

How to implement multiple GetHashCode methods?

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 (eg to use ValueTuple instead).

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM