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

如何创建可索引桶的ToLookup?避免Dictionary抛异常

问题与需求

给定以下C#代码:

record R(string Name, int Id);

var rList = new List<R>()
{
    new R("Fred", 2),
    new R("Fred", 3),
    new R("Fred", 4),
    new R("Jim", 10),
};

需要创建一种类似Lookup的数据结构,满足两个核心要求:

  1. 每个分组(桶)支持通过索引直接访问元素,比如lk["Fred"][0]获取对应分组的第一个元素;
  2. 访问不存在的键时,不会抛出异常,而是返回空序列/空列表,比如lk["Harry"].Count()返回0。

同时明确不想使用Dictionary,因为它访问不存在的键会抛出异常。

解决方案

方案1:基于原生Lookup扩展索引能力

.NET原生的Lookup<TKey, TElement>本身就支持“访问不存在的键返回空序列”的特性,但它返回的分组是IEnumerable<TElement>,不支持直接索引。我们可以通过扩展方法来弥补这个不足:

扩展方法实现

public static class LookupExtensions
{
    public static TElement? GetElementAt<TKey, TElement>(this ILookup<TKey, TElement> lookup, TKey key, int index)
        where TKey : notnull
    {
        var group = lookup[key];
        return group.ElementAtOrDefault(index);
    }
}

使用示例

// 创建原生Lookup
var lk = rList.ToLookup(r => r.Name);

// 通过扩展方法获取指定索引的元素
var fred1 = lk.GetElementAt("Fred", 0); // 输出:R { Name = "Fred", Id = 2 }
var fred2 = lk.GetElementAt("Fred", 1); // 输出:R { Name = "Fred", Id = 3 }

// 访问不存在的键,返回空序列,Count()为0
var harryCount = lk["Harry"].Count(); // 输出:0
// 索引不存在的元素时返回null
var harryElement = lk.GetElementAt("Harry", 0); // 输出:null

方案2:自定义支持索引的Lookup结构

如果想要更原生的索引访问体验,可以自定义一个类,内部封装Dictionary<TKey, List<TElement>>,重写索引器来实现“访问不存在的键返回空列表”的特性:

自定义类实现

public class IndexableLookup<TKey, TElement> where TKey : notnull
{
    private readonly Dictionary<TKey, List<TElement>> _groupDict;

    public IndexableLookup(IEnumerable<IGrouping<TKey, TElement>> groups)
    {
        _groupDict = groups.ToDictionary(g => g.Key, g => g.ToList());
    }

    // 重写索引器,不存在键时返回空列表
    public List<TElement> this[TKey key]
    {
        get
        {
            _groupDict.TryGetValue(key, out var groupList);
            return groupList ?? new List<TElement>();
        }
    }
}

使用示例

// 基于分组创建自定义Lookup
var lk = new IndexableLookup<string, R>(rList.GroupBy(r => r.Name));

// 直接通过双层索引访问元素
var fred1 = lk["Fred"][0]; // 输出:R { Name = "Fred", Id = 2 }
var fred2 = lk["Fred"][1]; // 输出:R { Name = "Fred", Id = 3 }

// 访问不存在的键,返回空列表,Count()为0
var harryCount = lk["Harry"].Count(); // 输出:0

关键说明

  • 原生Lookup的核心优势就是“无键返回空序列”,完全避免了Dictionary的异常问题,只是缺少直接索引的能力,通过扩展方法可以快速弥补;
  • 自定义IndexableLookup则完全贴合需求,既保留了无键返回空的特性,又提供了原生的双层索引体验,适合需要频繁通过索引访问分组元素的场景;
  • 两种方案都不需要修改原始数据结构,仅对分组结果进行封装或扩展。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 23:35:21