我正在经历Jon Skeet's Reimplemnting Linq to Objects series。在implementation of where文章中,我发现了以下片段,但我不知道通过将原始方法分成两部分来获得的优势是什么。
原创方法:
// Naive validation - broken!
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (predicate == null)
{
throw new ArgumentNullException("predicate");
}
foreach (TSource item in source)
{
if (predicate(item))
{
yield return item;
}
}
}
重构方法:
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (predicate == null)
{
throw new ArgumentNullException("predicate");
}
return WhereImpl(source, predicate);
}
private static IEnumerable<TSource> WhereImpl<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
foreach (TSource item in source)
{
if (predicate(item))
{
yield return item;
}
}
}
乔恩说 - 它用于热切的验证,然后对其余部分进行解密。但是,我不明白。
有人可以更详细地解释一下,这两个功能之间的区别是什么?为什么验证会在一个而不是另一个中执行?
结论/解决方案:
由于我的缺乏,我感到很困惑 了解哪些功能 确定是迭代器 - 发电机。 我认为,它是基于 一种方法的签名 的的IEnumerable
<T>
即可。但是,基于 答案,现在我明白了,方法就是了 迭代器生成器,如果它使用 yield 语句。
答案 0 :(得分:5)
破坏的代码是一个单一的方法,实际上是一个迭代器生成器。这意味着它最初只返回状态机而不做任何事情。只有当调用代码调用MoveNext时(可能作为for-each循环的一部分),它才会执行从开始到第一次yield-return的所有操作。
使用正确的代码,Where
不是迭代器生成器。这意味着它会像平常一样立即执行所有操作。只有WhereImpl
。因此,验证会立即执行,但是WhereImpl
代码直到并包括第一个收益率返回都会延迟。
所以如果你有类似的东西:
IEnumerable<int> evens = list.Where(null); // Correct code gives error here.
foreach(int i in evens) // Broken code gives it here.
在您开始迭代之前,破坏的版本不会给您一个错误。
答案 1 :(得分:2)
我认为Jon在他的文章中解释得非常好,但是解释依赖于您了解编译器在存在yield
语句时如何生成代码。基本上发生的是编译器生成一个不会被调用的迭代器(延迟执行),直到需要迭代中的一个项。初始方法包含检查参数的代码和迭代代码。编译器将所有这些都捆绑到迭代器中,记住,在需要第一个项之前不会调用它。这意味着在您尝试访问可枚举项中的一个项之前,验证不会发生。
通过将它分成两个方法,一个包含验证,一个包含迭代器块,它确保验证代码在构造迭代器时运行,而不是在执行时运行。这是因为捆绑到迭代器中的唯一代码是第二种方法中的代码;它是唯一执行延迟的代码。验证代码在您创建迭代器时执行。