81

我一直难以阐明和之间的区别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. AnIGrouping<TKey, TVal>是单个组(即键控序列),类似于KeyValuePair<TKey, TVal>值实际上是元素序列(而不是单个元素)
  2. AnIEnumerable<IGrouping<TKey, TVal>>是这些序列的序列(类似于迭代IDictionary<TKey, TVal>
  3. AnILookup<TKey, TVal>更像是IDictionary<TKey, TVal>其中的值实际上是一系列元素
4

3 回答 3

78

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

并且ILookup<TKey, TValue>还可以扩展IEnumerable<IGrouping<TKey, TValue>>,因此您可以遍历所有键/集合对以及(或代替)仅查找特定键。

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

请记住,这ToLookup是一个“立即执行”操作(立即执行),而 aGroupBy是延迟的。碰巧的是,按照“拉 LINQ”的工作方式,当您开始IGrouping从 a 的结果中拉取 s 时GroupBy,它无论如何都必须读取所有数据(因为您不能在中途切换组),而在其他实现中它可能能够产生流式传输结果。(在 Push LINQ 中确实如此;我希望 LINQ to Events 是相同的。)

于 2009-08-26T21:21:55.837 回答
8

ILookup 和 IDictionary 之间还有另一个重要区别:前者强制不变性,因为这里没有更改数据的方法(除非消费者执行显式转换)。相比之下,IDictionary 具有允许更改数据的“添加”等方法。因此,从函数式编程和/或并行编程的角度来看,ILookup 更好。(我只希望还有一个 ILookup 版本,它只将一个值分配给一个键而不是一个组。)

(顺便说一句,似乎值得指出的是,IEnumerable 和 IList 之间的关系有点类似于 ILookup 和 IDictionary 之间的关系——前者是不可变的,后者不是。)

于 2013-06-05T16:00:57.320 回答
8

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

但是如果我将上面的代码重写如下:(注意代码与前面的代码相同,只是GroupBy被替换为ToLookUp

 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方法时,会生成结果并应用组,因此如果我personnels在迭代之前删除任何项目,这不会影响最终结果。

输出:

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

注意:GroupBy两者ToLookUp都返回不同的类型。

您可能会使用 ToDictionary 而不是 ToLookUp,但您需要注意这一点:(参考

ToLookup() 的用法与 ToDictionary() 的用法非常相似,都允许您指定键选择器、值选择器和比较器。主要区别在于 ToLookup() 允许(并期望)重复键,而 ToDictionary() 不允许

于 2018-08-08T09:15:12.760 回答