我是"在玩#34;在LINQ附近测试一些东西,引起了我的注意。
假设我有这个"懒惰" GroupBy
扩展方法的实现:
public static IEnumerable<IGrouping<TKey, TSource>> GroupByA<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
//To avoid duplicate groups
List<TKey> grouping = new List<TKey>();
foreach (var item in source)
{
if (!grouping.Contains(keySelector(item)))
{
grouping.Add(keySelector(item));
Group<TKey, TSource> g = new Group<TKey, TSource>(
keySelector(item),
source.Where(x => keySelector(x).Equals(keySelector(item)))
);
Console.WriteLine("Returning group");
yield return g; //yield returning a complete group
}
}
}
注意:假设Group<TKey, TSource>
实施IGrouping<TKey, TSource
我想知道,如果执行此操作会发生什么?
var groups = students.GroupByA(x => x.Group).Take(2);
注意:students
为List<Student>
。
.Take(2)
会强制完成.GroupByA(x=>x.Group)
执行还是以某种方式一次消耗一个组,直到它计算为2
?无论哪种方式为什么吗
PS:我尝试使用自己的实现:
public static IEnumerable<T> TakeA<T>(this IEnumerable<T> source, int count)
像这样:
public static IEnumerable<T> TakeA<T>(this IEnumerable<T> source, int count)
{
int iter = 0;
foreach (var item in source)
{
if (iter == count)
yield break;
yield return item;
iter++;
}
}
但我很确定这种方式会导致GroupBy
在调用TakeA
之前完全执行。我不知道这是我实现它的方式还是以某种方式原始 Take
做了别的其他事情。
答案 0 :(得分:1)
C#编译器将您的代码转换为状态机。也就是说,它会在幕后创建一个新类,其中包含迭代学生列表所需的状态和行为。每次调用代码时,都会得到此类的实例。
将.Take(2)强制完成.GroupByA(x =&gt; x.Group)执行
查看完整的students.GroupByA(x => x.Group).Take(2)
表达式,.Net能够使用由GroupByA()
创建的Take()
函数创建的新类实例,并且您可以将其视为仅执行继续,直到您的代码第二次到达yield
行,但没有进一步。
但是,GROUP BY操作的性质是您必须循环遍历整个数据集以了解组的属性,这意味着即使您只看到第二个yield
表达式,source.Where()
调用仍然需要查看整个数据集,并至少进行O(n*m)
操作...每次识别新组时,都会再次浏览整个数据集。
应该可以使用Dictionary而不是List来编写O(n)
GROUP BY操作来查找新组并在字典值中累积聚合信息。您可能想看看是否可以管理它。当然,捕获的n
(小源列表大小)值很小,哈希计算和查找的成本可能高于序列迭代。