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

为何我的Dictionary<ValidationTarget, List<ValidationDirective>>抛出KeyNotFoundException?

枚举字典Keys时抛出KeyNotFoundException的原因及修复

问题场景

遍历自定义字典ValidationDictionary的Keys集合,直接用遍历得到的key取值时抛出KeyNotFoundException,代码如下:

foreach (var key in this.Keys)
{
    List<ValidationDirective> test = this[key];
}

ValidationDictionary继承自Dictionary<ValidationTarget, List<ValidationDirective>>,并使用自定义的ValidationTarget.EqualityComparer初始化。

核心问题分析

1. 自定义相等比较器的Equals逻辑完全错误

ValidationTarget.EqualityComparer.Equals方法通过比较两个对象的哈希码来判断相等,这是严重错误的:哈希码的设计目标是快速分组,不同对象完全可能产生相同的哈希码(哈希冲突),仅靠哈希码相等无法证明对象逻辑相等。这会导致字典在键匹配判断时出现误判。

2. ValidationTarget的Equals方法存在无限递归

ValidationTarget重写的Equals(object obj)方法中,当参数是ValidationTarget实例时,调用validationTargetObject.Equals(this),触发对方实例的Equals方法,进而形成无限递归。虽然这通常会导致栈溢出,但在你的场景中,哈希码不匹配可能导致查找过程在触发Equals之前就失败,从而抛出KeyNotFoundException。

3. 可变键对象的哈希码变化

ValidationTarget的GetHashCode方法依赖TargetId、TargetTag、ColumnName、RowIndex等字段,如果这些字段在对象被加入字典后被修改,对象的哈希码会发生变化。字典是根据对象加入时的哈希码分配哈希桶的,修改后查找时计算的哈希码与存储的哈希码不一致,导致字典无法在正确的桶中找到该键,即使key是从Keys集合中取出的。

修复方案

1. 修正自定义相等比较器的实现

确保Equals逻辑基于对象的实际字段,GetHashCode与Equals逻辑保持一致:

public class ValidationTarget
{
    public class EqualityComparer : IEqualityComparer<ValidationTarget>
    {
        public bool Equals(ValidationTarget x, ValidationTarget y)
        {
            if (ReferenceEquals(x, y)) return true;
            if (x is null || y is null) return false;
            
            return x.TargetId == y.TargetId
                && x.TargetTag == y.TargetTag
                && x.ColumnName == y.ColumnName
                && x.RowIndex == y.RowIndex;
        }

        public int GetHashCode(ValidationTarget obj)
        {
            if (obj is null) return 0;
            
            unchecked
            {
                const int hashBase = 10376669;
                const int hashMultiplier = 126036629;

                int hash = hashBase;
                hash = (hash * hashMultiplier) ^ obj.TargetId.GetHashCode();
                hash = (hash * hashMultiplier) ^ obj.TargetTag.GetHashCode();
                hash = (hash * hashMultiplier) ^ obj.ColumnName.GetHashCode();
                hash = (hash * hashMultiplier) ^ obj.RowIndex.GetHashCode();
                return hash;
            }
        }
    }
}

2. 修复ValidationTarget的Equals递归问题

实现正确的实例相等比较,同时实现IEquatable<ValidationTarget>接口提升性能:

public class ValidationTarget : IEquatable<ValidationTarget>
{
    // ... 原有字段和方法 ...

    public bool Equals(ValidationTarget other)
    {
        if (other is null) return false;
        if (ReferenceEquals(this, other)) return true;
        
        return TargetId == other.TargetId
            && TargetTag == other.TargetTag
            && ColumnName == other.ColumnName
            && RowIndex == other.RowIndex;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as ValidationTarget);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            const int hashBase = 10376669;
            const int hashMultiplier = 126036629;

            int hash = hashBase;
            hash = (hash * hashMultiplier) ^ this.TargetId.GetHashCode();
            hash = (hash * hashMultiplier) ^ this.TargetTag.GetHashCode();
            hash = (hash * hashMultiplier) ^ this.ColumnName.GetHashCode();
            hash = (hash * hashMultiplier) ^ this.RowIndex.GetHashCode();
            return hash;
        }
    }
}

3. 确保键对象不可变(推荐)

将ValidationTarget中用于计算哈希码和判断相等的字段设置为readonly,避免对象加入字典后被修改,从根源上杜绝哈希码变化导致的查找问题。

内容的提问来源于stack exchange,提问作者Russ S.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:24:52