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

构建多维查找表时,嵌套字典的更优替代方案是什么?

嘿,太懂你这种被嵌套字典的ContainsKey层层判断搞得代码又长又啰嗦的感觉了!之前我做多维查找表的时候也这么写过,每次加数据或者查数据都得反复检查每一层键是否存在,烦得不行。结合C#的特性,给你几个更优雅的实现方案,你可以根据自己的场景选:

方案1:用TryGetValue简化嵌套判断

其实不用每次都写ContainsKey再取值,TryGetValue可以把“判断键是否存在”和“获取对应值”一步搞定,能省不少重复代码。比如你的AddItem和GetItems可以改成这样:

public class MyLookupTable<T1, T2, T3>
{
    private readonly IDictionary<T1, IDictionary<T2, ISet<T3>>> _mapOfMapOfSequences 
        = new Dictionary<T1, IDictionary<T2, ISet<T3>>>();

    public void AddItem(T1 key1, T2 key2, T3 myItem)
    {
        // 检查第一层字典,不存在就创建
        if (!_mapOfMapOfSequences.TryGetValue(key1, out var innerDict))
        {
            innerDict = new Dictionary<T2, ISet<T3>>();
            _mapOfMapOfSequences[key1] = innerDict;
        }

        // 检查第二层字典,不存在就创建集合
        if (!innerDict.TryGetValue(key2, out var itemSet))
        {
            itemSet = new HashSet<T3>();
            innerDict[key2] = itemSet;
        }

        itemSet.Add(myItem);
    }

    public IEnumerable<T3> GetItems(T1 key1, T2 key2)
    {
        // 一层一层找,找不到就返回空序列
        if (_mapOfMapOfSequences.TryGetValue(key1, out var innerDict) &&
            innerDict.TryGetValue(key2, out var itemSet))
        {
            return itemSet;
        }
        return Enumerable.Empty<T3>();
    }
}

这样代码比原来的ContainsKey写法清爽多了,还避免了重复查找键的开销。

方案2:封装自定义多维查找表类

如果这种多维查找的场景你经常用到,不如把所有嵌套逻辑都封装成一个独立的类,对外只暴露简单的API,以后用起来就不用再写重复的判断了。比如:

public class TwoKeyLookup<TKey1, TKey2, TValue>
{
    private readonly Dictionary<TKey1, Dictionary<TKey2, HashSet<TValue>>> _innerLookup 
        = new Dictionary<TKey1, Dictionary<TKey2, HashSet<TValue>>>();

    // 添加元素
    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
        if (!_innerLookup.TryGetValue(key1, out var innerDict))
        {
            innerDict = new Dictionary<TKey2, HashSet<TValue>>();
            _innerLookup[key1] = innerDict;
        }

        if (!innerDict.TryGetValue(key2, out var valueSet))
        {
            valueSet = new HashSet<TValue>();
            innerDict[key2] = valueSet;
        }

        valueSet.Add(value);
    }

    // 获取元素
    public IEnumerable<TValue> Get(TKey1 key1, TKey2 key2)
    {
        return _innerLookup.TryGetValue(key1, out var innerDict) &&
               innerDict.TryGetValue(key2, out var valueSet)
            ? valueSet
            : Enumerable.Empty<TValue>();
    }

    // 还可以扩展其他方法,比如检查元素是否存在
    public bool ContainsValue(TKey1 key1, TKey2 key2, TValue value)
    {
        return _innerLookup.TryGetValue(key1, out var innerDict) &&
               innerDict.TryGetValue(key2, out var valueSet) &&
               valueSet.Contains(value);
    }
}

用的时候直接实例化这个类,调用Add和Get就行,完全不用关心内部的嵌套结构,代码会干净很多。而且以后要是需要扩展成三维键,修改这个类也很方便。

方案3:用元组作为字典的键(扁平结构更简洁)

如果你的两个键T1和T2可以组合成一个元组,那直接用Dictionary<(T1, T2), ISet<T3>>代替嵌套字典是最省事的。把二维键变成一维,操作起来就跟普通字典一样简单:

public class MyLookupTable<T1, T2, T3>
{
    private readonly Dictionary<(T1, T2), ISet<T3>> _lookup 
        = new Dictionary<(T1, T2), ISet<T3>>();

    public void AddItem(T1 key1, T2 key2, T3 myItem)
    {
        var compositeKey = (key1, key2);
        if (!_lookup.TryGetValue(compositeKey, out var itemSet))
        {
            itemSet = new HashSet<T3>();
            _lookup[compositeKey] = itemSet;
        }
        itemSet.Add(myItem);
    }

    public IEnumerable<T3> GetItems(T1 key1, T2 key2)
    {
        return _lookup.TryGetValue((key1, key2), out var itemSet)
            ? itemSet
            : Enumerable.Empty<T3>();
    }
}

这个方案的优势是结构扁平,代码最少。不过要注意元组的相等性:如果T1和T2是值类型或者已经正确重写了Equals和GetHashCode,那没问题;如果是自定义引用类型,要确保它们的相等判断符合你的业务预期。

方案4:用.NET内置的Lookup(只读场景适用)

如果你的查找表是只读的,或者只需要一次性构建完成后查询,那可以试试System.Linq.Lookup。虽然它不直接支持多键,但可以把多个键组合成元组或者匿名类型作为键,用ToLookup方法构建:

// 假设你有一个数据源,比如 IEnumerable<(T1 Key1, T2 Key2, T3 Value)> dataItems
var readOnlyLookup = dataItems.ToLookup(item => (item.Key1, item.Key2), item => item.Value);

// 查询的时候直接用复合键
var results = readOnlyLookup[(targetKey1, targetKey2)]; // 返回IEnumerable<T3>,无匹配时返回空序列

不过Lookup是只读的,不能动态添加或删除元素,所以如果你的表需要频繁更新,这个方案就不适合了。


我个人比较推荐方案2或者方案3:如果以后可能扩展更多维度的键,方案2的自定义类扩展性更好;如果只是固定二维键,方案3的元组键字典最简洁。

内容的提问来源于stack exchange,提问作者Vesuvian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:52:22