如何创建可索引桶的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的数据结构,满足两个核心要求:
- 每个分组(桶)支持通过索引直接访问元素,比如
lk["Fred"][0]获取对应分组的第一个元素; - 访问不存在的键时,不会抛出异常,而是返回空序列/空列表,比如
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
相关产品推荐
相关产品推荐

