ILookup 是只读、立即执行、支持安全索引的一对多查找表,非 Dictionary;它允许重复 Key、索引器查无 Key 时返回空集合不抛异常,适用于多次按 Key 查集合的场景。

ILookup
为什么 ToLookup 返回的不是 Dictionary?
因为 ILookup 的设计目标不是键值映射,而是分组后快速取组——一个 Key 对应零个或多个元素,且不保证唯一性。它没有 Add、Remove、ContainsKey 等方法,也没有可变性。
-
Dictionary<tkey tvalue></tkey>要求每个 Key 唯一,Value 是单个对象;ILookup允许重复 Key(比如多个员工同属一个部门),Value 是IEnumerable<telement></telement> -
ILookup的this[TKey]索引器查不到 Key 时返回空集合,不会抛异常;而Dictionary会抛KeyNotFoundException - 你不能写
lookup.ContainsKey("X")—— 这个方法根本不存在;得用lookup["X"].Any()
ToLookup 的四个重载怎么选?
核心就看三件事:要不要改 Key 比较逻辑、要不要投影 Value、要不要自定义 Key 生成方式。最常用的是前两个:
-
source.ToLookup(x => x.Category):只按属性分组,保留原始对象 -
source.ToLookup(x => x.Category, x => x.Name):分组后只留姓名字符串,不存整个对象 -
source.ToLookup(x => x.Category, StringComparer.OrdinalIgnoreCase):忽略大小写分组(如 "A" 和 "a" 视为同一组) -
source.ToLookup(x => x.Category, x => x.Id, StringComparer.OrdinalIgnoreCase):同时指定比较器和投影
注意:ToLookup 没有类似 GroupBy 的 resultSelector 重载,所以不能直接构造新对象(比如 (k, g) => new { Key = k, Count = g.Count() })。需要先 ToLookup,再手动映射。
查不到 Key 时为什么不会崩?
因为 ILookup 的索引器设计就是“安全兜底”:无论 Key 是否存在,lookup["Missing"] 都返回一个空的 IEnumerable<telement></telement>,不是 null,也不抛异常。
-
lookup["Missing"].Count()返回0,不是NullReferenceException -
lookup["Missing"].Any()是高效判断方式(短路,不枚举全部) - 但别写
lookup["Missing"]?.First()——First()在空集合上会抛InvalidOperationException,要用FirstOrDefault()
和 GroupBy 混用时最容易踩什么坑?
最典型的是把 GroupBy 结果当 ILookup 用,比如写 groups["Electronics"],编译直接报错:CS0021 Cannot apply indexing with [] to an expression of type 'IEnumerable<igrouping telement>>'</igrouping>。
-
GroupBy返回的是延迟执行的IEnumerable<igrouping>></igrouping>,不支持索引访问 - 若需多次查 Key,必须先调用
ToLookup()转成ILookup,否则每次groups.FirstOrDefault(g => g.Key == "X")都要遍历整个序列 -
ToLookup是立即执行的,构建时就完成分组;GroupBy是延迟执行的,真正遍历时才分组——这点在 EF Core 查询中尤其关键:ToLookup会强制触发 SQL 查询并把结果拉到内存再分组
真正容易被忽略的是:ILookup 一旦构建完成,Key 就不可变,也不能增删项;如果数据源本身是动态变化的(比如实时日志流),得重新 ToLookup,而不是指望它自动更新。


















