多个Where连接将执行几次迭代?

在编写LINQ时,为了保持代码清晰,我们通常会将多个Where进行连接,如

var numbers = Enumerable.Range(1, 10);
var evenNumbersLessThanFive = numbers
    .Where(i => i % 2 == 0)
    .Where(i => i < 5);

但多个Where是否意味着多次迭代呢?直觉告诉我们显然不是。因为Where只是生成一个可迭代的对象,在对该对象进行foreach之前,是不会真正执行迭代的。但编译器是如何处理这种Where连接的呢?

查看源代码后发现,Enumerable.Where扩展方法返回的是WhereXxxIterator<TSource>这样的类型,它们均继承自Enumerable.Iterator<TSource>类。我们以WhereEnumerableIterator<TSource>类为例,它包含一个Where方法。我们知道,当调用类的一个实例方法时,编译器就会忽略与其签名相同的扩展方法。因此在多个Where连接的代码中,从第二个Where开始,调用的就是WhereEnumerableIterator<TSource>.Where(Func<TSource, bool> predicate)方法,该方法实现如下

public override IEnumerable<TSource> Where(Func<TSource, bool> predicate)
{
    return new Enumerable.WhereEnumerableIterator<TSource>(
        this.source,
        Enumerable.CombinePredicates<TSource>(this.predicate, predicate));
} 

可以看到,CombinePredicates这个方法,是将两个Func<TSource, bool>委托合并。它是一个工厂方法,生成仍然是一个Func<TSource, bool>。

private static Func<TSource, bool> CombinePredicates<TSource>(
    Func<TSource, bool> predicate1, Func<TSource, bool> predicate2)
{
    <>c__DisplayClassf<TSource> classf = new <>c__DisplayClassf<TSource>();
    classf.predicate1 = predicate1;
    classf.predicate2 = predicate2;
    return new Func<TSource, bool>(classf.<CombinePredicates>b__e);
}

其中,<>c__DisplayClassf<TSource>是一个包含两个Func<TSource, bool>属性的密封类,它的<CombinePredicates>b__e方法负责合并两个委托:

public bool <CombinePredicates>b__e(TSource x)
{
    if (this.predicate1(x))
    {
        return this.predicate2(x);
    }
    return false;
}

因此可以得出结论,多个Where连接,与将多个条件写到一个Where中,几乎是等同的,它们都只执行一次迭代。

var evenNumbersLessThanFive2 =
    numbers.Where(i => i % 2 == 0 && i < 5);
posted @ 2011-03-03 10:09  麒麟.NET  阅读(1262)  评论(5编辑  收藏  举报