0

In my code I have the following class:

public class ResourcePack
{
    private int m_pirates;
    private int m_islands;
    private int m_enemy_drones;
    private int m_enemy_pirates;

    public ResourcePack()
    {
        this.m_pirates = 0;
        this.m_islands = 0;
        this.m_enemy_drones = 0;
        this.m_enemy_pirates = 0;
    }

    public ResourcePack(ResourcePack other)
    {
        this.m_pirates = other.m_pirates;
        this.m_islands = other.m_islands;
        this.m_enemy_drones = other.m_enemy_drones;
        this.m_enemy_pirates = other.m_enemy_pirates;
    }

    public bool CanConcatinate(ResourcePack other)
    {
        if ((this.m_islands & other.m_islands) != 0)
        {
            return false;
        }

        if ((this.m_enemy_drones & other.m_enemy_drones) != 0)
        {
            return false;
        }

        if ((this.m_enemy_pirates & other.m_enemy_pirates) != 0)
        {
            return false;
        }

        return true;
    }

    internal void AddIsland(int island)
    {
        m_islands |= (1 << island);
    }

    internal void AddPirate(int pirate)
    {
        m_pirates |= (1 << pirate);
    }

    internal void AddEnemyDrone(int drone)
    {
        m_enemy_drones |= (1 << drone);
    }

    internal void AddEnemyPirates(int pirate)
    {
        m_enemy_pirates |= (1 << pirate);
    }

    public ResourcePack SemiConcatinate(ResourcePack other)
    {
        var ret = new ResourcePack();

        ret.m_pirates       = this.m_pirates | other.m_pirates;
        ret.m_islands       = this.m_islands | other.m_islands;
        ret.m_enemy_drones  = this.m_enemy_drones | other.m_enemy_drones;
        ret.m_enemy_pirates = this.m_enemy_pirates | other.m_enemy_pirates;

        return ret;
    }

    public override bool Equals(object value)
    {
        ResourcePack other = value as ResourcePack;

        return !object.ReferenceEquals(null, other)
            && int.Equals(m_pirates, other.m_pirates)
            && int.Equals(m_islands, other.m_islands)
            && int.Equals(m_enemy_drones, other.m_enemy_drones)
            && int.Equals(m_enemy_pirates, other.m_enemy_pirates);
    }

    /*
    public override int GetHashCode()
    {
        unchecked
        {
            int hash = (int)2166136261;
            hash = (hash * 16777619) ^ m_pirates.GetHashCode();
            hash = (hash * 16777619) ^ m_islands.GetHashCode();
            hash = (hash * 16777619) ^ m_enemy_drones.GetHashCode();
            hash = (hash * 16777619) ^ m_enemy_pirates.GetHashCode();
            return hash;
        }
    }*/

    public static bool operator ==(ResourcePack a, ResourcePack b)
    {
        if (object.ReferenceEquals(a, b))
            return true;

        if (object.ReferenceEquals(null, a))
            return false;

        return a.Equals(b);
    }

    public static bool operator !=(ResourcePack a, ResourcePack b)
    {
        return !(a == b);
    }
}

There's only one place where this code is being used and once an instance of ResourcePack was created the members doesn't change anymore. The only way ResourcePack is used as is a key index for a dictionary:

Dictionary<ResourcePack, T> current = new Dictionary<ResourcePack, T>(missions[0].Length);
// current[t1] = t2; where t1 is an instance of ResourcePack

When I dont override GetHashCode, the code works as it supposed to. When I do override (Uncomment GetHashCode), the code doesn't work the same and the output seems random.

Can someone please explain me this weired behavior? Is my custom GetHashCode not good enough?

Update I've changed my code after reading your suggestions, but it still seems that GetHashCode doesn't return unique (enough?) values.

New code:

public sealed class ResourcePack
{
    private readonly int m_pirates;
    private readonly int m_islands;
    private readonly int m_enemy_drones;
    private readonly int m_enemy_pirates;

    public ResourcePack(int pirates, int islands, int enemy_drones, int enemy_pirates)
    {
        this.m_pirates = pirates;
        this.m_islands = islands;
        this.m_enemy_drones = enemy_drones;
        this.m_enemy_pirates = enemy_pirates;
    }

    public ResourcePack(ResourcePack other)
    {
        this.m_pirates = other.m_pirates;
        this.m_islands = other.m_islands;
        this.m_enemy_drones = other.m_enemy_drones;
        this.m_enemy_pirates = other.m_enemy_pirates;
    }

    public bool CanConcatinate(ResourcePack other)
    {
        if ((this.m_islands & other.m_islands) != 0)
        {
            return false;
        }

        if ((this.m_enemy_drones & other.m_enemy_drones) != 0)
        {
            return false;
        }

        if ((this.m_enemy_pirates & other.m_enemy_pirates) != 0)
        {
            return false;
        }

        return true;
    }

    public ResourcePack SemiConcatinate(ResourcePack other)
    {
        return new ResourcePack(this.m_pirates | other.m_pirates,
            this.m_islands | other.m_islands,
            this.m_enemy_drones | other.m_enemy_drones,
            this.m_enemy_pirates | other.m_enemy_pirates);
    }

    #region Hashing

    public override bool Equals(object value)
    {
        ResourcePack other = value as ResourcePack;

        return !object.ReferenceEquals(null, other)
            && int.Equals(m_pirates, other.m_pirates)
            && int.Equals(m_islands, other.m_islands)
            && int.Equals(m_enemy_drones, other.m_enemy_drones)
            && int.Equals(m_enemy_pirates, other.m_enemy_pirates);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = (int)23;
            hash = (hash * 17) ^ m_pirates.GetHashCode();
            hash = (hash * 17) ^ m_islands.GetHashCode();
            hash = (hash * 17) ^ m_enemy_drones.GetHashCode();
            hash = (hash * 17) ^ m_enemy_pirates.GetHashCode();
            return hash;
        }
    }

    public static bool operator ==(ResourcePack a, ResourcePack b)
    {
        if (object.ReferenceEquals(a, b))
            return true;

        if (object.ReferenceEquals(null, a))
            return false;

        return a.Equals(b);
    }

    public static bool operator !=(ResourcePack a, ResourcePack b)
    {
        return !(a == b);
    }

    #endregion
}

If I remove the region called "Hashing" everything works fine.

  • where did you find a similar algorithm for the hash? why not a much simpler XOR of the field hashes? – Mario Vernari Jan 29 '17 at 09:22
  • @MarioVernari I've tried it already, didn't work, the code still behaved differently. It seems that the only thing that doesn't break the code is the internal GetHashCode. – user3601822 Jan 29 '17 at 09:24
  • 4
    `GetHashCode` should only be used on readonly members. Hashcode is used to find the correct bucket in the dictionary. If the method returns different results, the dictionary will pick different buckets. – CSharpie Jan 29 '17 at 09:25
  • CSharpie is right. I didn't realize your class isn't immutable. You should design in that way. – Mario Vernari Jan 29 '17 at 09:29
  • Also make it "sealed". – Mario Vernari Jan 29 '17 at 09:29
  • 4
    Read this **very carefully** and do **everything** it says. https://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/ – Eric Lippert Jan 29 '17 at 10:18

2 Answers2

4

GetHashCode must return the same value while the object is used within the same Dictionary. See the documentation for more details

Dictionary<TKey,TValue> uses the HashCode in order to find a bucket for the Key passed when adding, getting and removing elements.

Altering the Hashcode between Writing and Reading to the Dictionary will make it pick the wrong bucket to look for the element and thus giving you unexpected results.

If its not super performance critical, why not make use of some existing things, e.g. Tuple's GetHashCode

Tuple.Create(m_pirates,m_islands,m_enemy_drones,m_enemy_pirates).GetHashCode();
CSharpie
  • 9,195
  • 4
  • 44
  • 71
  • First of all thank you for your fast reply. I used your suggestions but it still doesn't seem to work. The only "custom" GetHashCode that works is: return new[] { m_pirates, m_islands, m_enemy_drones, m_enemy_pirates }.GetHashCode() – user3601822 Jan 29 '17 at 09:41
  • @user3601822 try what Jon Skeet wrote here then http://stackoverflow.com/a/1646913/1789202 – CSharpie Jan 29 '17 at 09:46
  • It is super performance critical, that's the reason I'm trying to implement a custom GetHashCode. As I said, the code works fine without the custom GetHashCode but it could benefit from a custom one. I've tried Skeet's implementation, it seems like it works better but it still not unique as the native one. Does anyone know how the native implementation works? – user3601822 Jan 29 '17 at 09:53
  • @user3601822 HashCode doesnt guarantee and also wont allways be unique. You Should provide an example where you bellieve the Hashcode is at fault. – CSharpie Jan 29 '17 at 09:57
  • @CSharpie - you need to ensure that the `GetHashCode` function always returns the same value for the lifetime of the object (at least while it is used within the dictionary) – Enigmativity Jan 29 '17 at 09:59
  • @Enigmativity you are right. Didnt really know how to say it. – CSharpie Jan 29 '17 at 10:02
0

When you don't override GetHashCode, the dictionary is indexed by the object reference (the sync block). There is no way to create a new instance of your key if it is lost.

On the other hand, your implementation of both GetHashCode and Equals makes it possible to "recreate" the key just by using the same fields. At the same time, however, it means that your ResourcePack keys are no longer unique by default - if they have the same fields, they will map to the same item in the dictionary.

The most likely causes for the issues you're seeing are:

  • You mutate the hashcode fields.
  • You have two (or more) ResourcePack objects that are supposed to be different, but actually have the same fields. The default implementation keeps them unique, yours doesn't.
Luaan
  • 62,244
  • 7
  • 97
  • 116