You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 21:57:02