ILookup < TKey,TVal > vs. IGrouping < TKey,TVal >

我一直很难清楚地表达 ILookup<TKey, TVal>IGrouping<TKey, TVal>之间的区别,我很好奇,如果我现在正确地理解它。LINQ 通过生成 IGrouping项目的序列,同时也给了我一个 ToLookup扩展方法,使问题复杂化。所以在我仔细观察之前,我觉得它们是一样的。

var q1 =
from n in N
group n by n.MyKey into g
select g;
// q1 is IEnumerable<IGrouping<TKey, TVal>>

相当于:

var q2 = N.GroupBy(n => n.MyKey, n => n);
// q2 is IEnumerable<IGrouping<TKey, TVal>>

看起来很像:

var q3 = N.ToLookup(n => n.MyKey, n => n);
// q3 is ILookup<TKey, TVal>

下面的比喻对吗?

  1. IGrouping<TKey, TVal>是一个单独的组(即键控序列) ,类似于 KeyValuePair<TKey, TVal>,其中的值实际上是一个元素序列(而不是单个元素)
  2. IEnumerable<IGrouping<TKey, TVal>>是这些序列的序列(类似于在 IDictionary<TKey, TVal>上迭代时得到的序列
  3. ILookup<TKey, TVal>更像是 IDictionary<TKey, TVal>,其中的值实际上是一系列元素
12244 次浏览

是的,所有这些都是正确的。

ILookup<TKey, TValue>也扩展了 IEnumerable<IGrouping<TKey, TValue>>,这样您就可以遍历所有的键/集合对,以及(或者代替)查找特定的键。

我基本上认为 ILookup<TKey,TValue>就像 IDictionary<TKey, IEnumerable<TValue>>

请记住,ToLookup是一个“现在就做”操作(即时执行) ,而 GroupBy是延迟的。碰巧,按照“ pull LINQ”的工作方式,当你开始从 GroupBy的结果中提取 IGrouping时,它必须读取所有的数据(因为你不能在中途切换组) ,而在其他实现中,它可能会产生一个流结果。(在 PushLINQ 中是这样的; 我希望 LINQtoEvents 也是这样。)

ILookup 和 IDictionary 之间还有一个重要的区别: 前者强制执行不变性,因为这里没有更改数据的方法(除非使用者执行显式强制转换)。相比之下,IDictionary 有类似“ Add”的方法,允许更改数据。因此,从函数式编程和/或并行编程的角度来看,ILookup 更好。

(另外,似乎值得指出的是,IEnumable 和 IList 之间的关系有点类似于 ILookup 和 IDictionary 之间的关系——前者是不可变的,后者则不是。)

GroupByToLookUp具有几乎相同的功能

GroupBy: GroupBy 运算符返回基于 每个组由 IGrouping 表示 对象。

ToLookup: ToLookup 与 GroupBy 相同; 唯一的区别是 是 GroupBy 的执行被延迟,而 ToLookup 的执行被延迟 马上。

假设我们有一个表示 Person模型的类:

class Personnel
{
public int Id { get; set; }
public string FullName { get; set; }
public int Level { get; set; }
}

然后我们定义一个 personnels列表如下:

 var personnels = new List<Personnel>
{
new Personnel { Id = 1, FullName = "P1", Level = 1 },
new Personnel { Id = 2, FullName = "P2", Level = 2 },
new Personnel { Id = 3, FullName = "P3", Level = 1 },
new Personnel { Id = 4, FullName = "P4", Level = 1 },
new Personnel { Id = 5, FullName = "P5", Level =2 },
new Personnel { Id = 6, FullName = "P6", Level = 2 },
new Personnel { Id = 7, FullName = "P7", Level = 2 }
};

现在我需要把 personnels按级别分组。我有两个办法。使用 GroupByToLookUp。如果我使用 GroupBy,如前所述,它将使用延迟执行,这意味着,当您在集合中迭代时,下一个项可能会被计算,也可能不会被计算,直到它被调用。

 var groups = personnels.GroupBy(p => p.Level);
personnels.RemoveAll(p => p.Level == 1);
foreach (var product in groups)
{
Console.WriteLine(product.Key);
foreach (var item in product)
Console.WriteLine(item.Id + " >>> " + item.FullName + " >>> " + item.Level);
}

在上面的代码中,我首先对 personnels进行分组,但是在迭代它之前,我删除了一些 personnels。由于 GroupBy使用延迟执行,因此最终结果将不包括已删除的项,因为分组将在此处的 foreach点中进行计算。

产出:

2
2 >>> P2 >>> 2
5 >>> P5 >>> 2
6 >>> P6 >>> 2
7 >>> P7 >>> 2

但是,如果我重写以上代码如下: (注意,代码与以前的代码相同,除了 GroupByToLookUp替换)

 var groups = personnels.ToLookup(p => p.Level);
personnels.RemoveAll(p => p.Level == 1);
foreach (var product in groups)
{
Console.WriteLine(product.Key);
foreach (var item in product)
Console.WriteLine(item.Id + " >>> " + item.FullName + " >>> " + item.Level);
}

由于 ToLookUp使用立即执行,这意味着当我调用 ToLookUp方法时,结果生成并应用 group,因此如果在迭代之前从 personnels中删除任何项,都不会影响最终结果。

产出:

1
1 >>> P1 >>> 1
3 >>> P3 >>> 1
4 >>> P4 >>> 1
2
2 >>> P2 >>> 2
5 >>> P5 >>> 2
6 >>> P6 >>> 2
7 >>> P7 >>> 2

注意: GroupByToLookUp也都返回不同的类型。

您可以使用 ToDictionary 而不是 ToLookUp,但是您需要注意这一点: (参考文献)

ToLookup ()的用法非常类似于 ToDictionary () , 都允许您指定键选择器、值选择器和 主要区别在于 ToLookup ()允许(和 期望)重复的键,而 ToDictionary ()没有