这两个linq实现有什么区别?

时间:2011-01-12 23:11:52

标签: c# linq-to-objects where deferred-execution

我正在经历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   语句。

2 个答案:

答案 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语句时如何生成代码。基本上发生的是编译器生成一个不会被调用的迭代器(延迟执行),直到需要迭代中的一个项。初始方法包含检查参数的代码和迭代代码。编译器将所有这些都捆绑到迭代器中,记住,在需要第一个项之前不会调用它。这意味着在您尝试访问可枚举项中的一个项之前,验证不会发生。

通过将它分成两个方法,一个包含验证,一个包含迭代器块,它确保验证代码在构造迭代器时运行,而不是在执行时运行。这是因为捆绑到迭代器中的唯一代码是第二种方法中的代码;它是唯一执行延迟的代码。验证代码在您创建迭代器时执行。