GetHashCode 断码

GetHashCode breaks code

在我的代码中我有以下 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);
    }
}

只有一个地方使用了此代码,并且一旦创建了 ResourcePack 实例,成员就不会再更改。 ResourcePack 的唯一使用方式是字典的键索引:

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

当我不覆盖 GetHashCode 时,代码会按预期工作。 当我覆盖(取消注释 GetHashCode)时,代码无法正常工作,输出似乎是随机的。

谁能解释一下这种奇怪的行为?我自定义的 GetHashCode 是不是不够好?

更新 我在阅读了您的建议后更改了我的代码,但 GetHashCode 似乎仍然没有 return 唯一(足够?)值。

新代码:

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
}

如果我删除名为 "Hashing" 的区域,一切正常。

GetHashCode 必须 return 相同的值,同时该对象在同一词典中使用。有关详细信息,请参阅 documentation

Dictionary<TKey,TValue> 使用 HashCode 来为添加、获取和删除元素时传递的 Key 找到一个桶。

改变字典写入和读取之间的哈希码将使其选择错误的桶来查找元素,从而给您带来意想不到的结果。

如果它不是超级性能关键,为什么不利用一些现有的东西,例如元组的 GetHashCode

Tuple.Create(m_pirates,m_islands,m_enemy_drones,m_enemy_pirates).GetHashCode();

当您不覆盖 GetHashCode 时,字典由对象引用(同步块)索引。如果密钥丢失,则无法创建新的密钥实例。

另一方面,您对 GetHashCodeEquals 的实现使得仅通过使用相同的字段就可以 "recreate" 密钥。然而,与此同时,这意味着您的 ResourcePack 键在默认情况下不再是唯一的 - 如果它们具有相同的字段,它们 映射到字典.

您遇到的问题最可能的原因是:

  • 您改变哈希码字段。
  • 您有两个(或更多)ResourcePack 个对象,它们本应不同,但实际上具有相同的字段。默认实现使它们保持唯一性,而您的则不然。