C# Sprite游戏Dictionary的ContainsKey返回false及Equals异常问题
问题根本原因
- Item的哈希码依赖可变属性:你当前Item的
GetHashCode方法用了holder(Sprite实例)的坐标、血量、护盾等可变状态,只要Sprite的这些属性发生变化,已经存在Dictionary中的Item实例的哈希码就会和插入时计算的哈希码不一致,导致ContainsKey找不到已存在的键,这是你遇到ContainsKey返回false的核心原因。 - Sprite的Equals方法字典比较错误:你直接用
inv.Equals(s.inv)是引用比较,只会判断两个字典是不是同一个实例,不会对比内容,所以即使两个Sprite背包内容完全一致,Equals也会返回false。 - 哈希码计算存在冲突:Item的
GetHashCode里holder.GetPoint().GetY() << 16和holder.GetPoint().GetZ() << 16位移位数重复,会大幅提升哈希冲突概率,同时你的哈希计算直接强转int会有溢出风险,未开启unchecked的情况下会抛出异常。 - DictionaryEquals逻辑不完整:只遍历了inv2的键校验inv是否存在,没有反过来校验inv的键是否都在inv2中,当inv包含inv2没有的键时会错误返回true。
修复方案
1. 修复Item类的GetHashCode和Equals逻辑
物品的唯一性应该只由自身属性决定,和持有者无关,修改为仅用名称计算哈希(如果有其他物品唯一属性也可以加进来):
public class Item { private string name; protected Sprite holder; public Item(string n, Sprite h) { name = n; holder = h; } public string GetItemName() { return name; } public override int GetHashCode() { // 仅用不可变的物品名称计算哈希,避免持有者属性变化导致哈希变更 return name.GetHashCode(); } public override bool Equals(Object obj) { if ((obj == null) || ! this.GetType().Equals(obj.GetType())) { return false; } else { Item i = (Item) obj; // 物品相等仅判断名称,持有者不属于物品本身的固有属性 return name == i.name; } } }
如果业务需要同名称物品区分不同实例,可以额外加唯一id字段参与哈希和相等判断
2. 修复Sprite类的Equals方法
将字典的引用比较替换为内容比较,调用你实现的DictionaryEquals方法:
public override bool Equals(Object obj) { if ((obj == null) || ! this.GetType().Equals(obj.GetType())) { return false; } else { Sprite s = (Sprite) obj; // 先判空避免空引用异常 if (inv == null && s.inv == null) return true; if (inv == null || s.inv == null) return false; return (p.Equals(s.p)) && // 替换为内容比较 DictionaryEquals(s.inv) && (Horiz == s.Horiz) && (Vert == s.Vert) && (Health == s.Health) && (Shield == s.Shield); } }
3. 补全DictionaryEquals逻辑
补充反向校验,同时添加空判断:
public bool DictionaryEquals(Dictionary<Item, int> inv2) { if (inv == null && inv2 == null) return true; if (inv == null || inv2 == null) return false; // 先判断长度是否一致,长度不同直接返回false if (inv.Count != inv2.Count) return false; foreach(KeyValuePair<Item, int> entry in inv2) { if(!inv.TryGetValue(entry.Key, out var count) || count != entry.Value) { return false; } } return true; }
这里用TryGetValue代替ContainsKey+索引器访问,避免二次查找,性能更好,也能避免KeyNotFoundException
4. (可选)优化Sprite的GetHashCode计算
如果Sprite不需要作为字典的键,其实可以不用重写GetHashCode,如果需要用的话,建议用更稳妥的哈希计算方式,避免溢出:
public override int GetHashCode() { unchecked { var hash = p.GetX().GetHashCode(); hash = (hash * 397) ^ p.GetY().GetHashCode(); hash = (hash * 397) ^ p.GetZ().GetHashCode(); hash = (hash * 397) ^ Horiz.GetHashCode(); hash = (hash * 397) ^ Vert.GetHashCode(); hash = (hash * 397) ^ Health.GetHashCode(); hash = (hash * 397) ^ Shield.GetHashCode(); return hash; } }
内容的提问来源于stack exchange,提问作者blub_9
相关产品推荐
相关产品推荐

