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
时,字典由对象引用(同步块)索引。如果密钥丢失,则无法创建新的密钥实例。
另一方面,您对 GetHashCode
和 Equals
的实现使得仅通过使用相同的字段就可以 "recreate" 密钥。然而,与此同时,这意味着您的 ResourcePack
键在默认情况下不再是唯一的 - 如果它们具有相同的字段,它们 将 映射到字典.
您遇到的问题最可能的原因是:
- 您改变哈希码字段。
- 您有两个(或更多)
ResourcePack
个对象,它们本应不同,但实际上具有相同的字段。默认实现使它们保持唯一性,而您的则不然。
在我的代码中我有以下 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
时,字典由对象引用(同步块)索引。如果密钥丢失,则无法创建新的密钥实例。
另一方面,您对 GetHashCode
和 Equals
的实现使得仅通过使用相同的字段就可以 "recreate" 密钥。然而,与此同时,这意味着您的 ResourcePack
键在默认情况下不再是唯一的 - 如果它们具有相同的字段,它们 将 映射到字典.
您遇到的问题最可能的原因是:
- 您改变哈希码字段。
- 您有两个(或更多)
ResourcePack
个对象,它们本应不同,但实际上具有相同的字段。默认实现使它们保持唯一性,而您的则不然。