Linq-to-objects操作员返回的对象 - 引擎盖下发生了什么?

时间:2011-09-26 17:18:39

标签: linq linq-to-objects

LINQ使用延迟执行模型,这意味着在调用Linq运算符时不返回结果序列,而是这些运算符返回一个对象,然后只有在枚举此对象时才会生成序列的元素。

var results = someCollection.Select(item => item.Foo).Where(foo => foo < 3);

当我们枚举结果对象时,它将只遍历someCollection一次,对于迭代期间请求的每个项目,代码(位于results对象内)执行映射操作并最终执行过滤

但我无法理解幕后发生的事情:

a)Where方法是实际创建results对象的方法吗?

b)如果Where确实创建了results个对象,那么我假设Where还需要从Select运算符中提取一些逻辑(例如return Item.Foo })以便它可以将该逻辑放入results对象中?

c)如果我的假设是正确的,Where如何从Select中提取逻辑?

d)无论如何,results对象包含必要的逻辑 L 来评估someCollection中的每个项目。在评估Select中的每个项目时,我假设此逻辑 L 不会对WheresomeCollection运算符进行任何其他调用?

谢谢


修改

1)

  

你在d)中的假设是不正确的 - 结果只是一个   IEnumerable由Where()扩展返回   方法。只有在遍历枚举时(即使用foreach)   或者ToList())将为“真实”创建序列。在那时候 -   如果你设定一个断点,你甚至可以看到这一点 - 所有的Linq   依次执行扩展方法 - Where()扩展方法   将询问输入IEnumerable的第一个项目,这将导致   依次为Select()运算符将从中获取第一个项目   底层集合并吐出一个FooType项目。

a)因此,在将结果对象分配给Where变量(Select)时,首先会在赋值语句中调用resultsvar results=...。然后,在枚举Where时,还会为每个项目调用Select / results(来自someCollection对象内)?

b)假设results实例的类型为 C - 何时定义/创建了 C 类?它是由Where方法定义的,还是由编译器定义的类 C ,因此Where只返回C的实例?

2)

  

仅当您遍历枚举时(即使用foreach或   ToList())将为“真实”创建序列。那时 - 你   甚至可以看到这个,如果你设置一个断点 - 所有Linq扩展   方法依次执行 - Where()扩展方法会询问   输入IEnumerable为其第一个项目,这将导致   Select()运算符依次从底层获取第一个项目   收集并吐出一个FooType项目

a)您说在results对象SelectWhere内为集合中的每个项目 I 调用。假设未实现IEnumerable<>,那么SelectWhere如果I只能在IEnumerable<>上进行操作类型?

2 个答案:

答案 0 :(得分:3)

关键是所有这些Linq扩展方法都是链接的。每个都在上一个扩展方法的输出上工作,对于Linq to Objects(Linq to SQL另一方面做了一些优化),至少每个扩展方法除了作为其输入的直接枚举之外不需要知道任何其他内容。

这些扩展方法中的每一个都将特定类型的IEnumerable作为输入,并再次生成IEnumerable(使用Select()时可能不同类型的结果)。由于这种限制和可链接性,您可以以不同的方式组合Linq扩展方法,这使Linq变得如此灵活和强大。

因此,对于您的示例,Select()IEnumerable<YourCollectionType>进行操作,并产生IEnumerable<FooType>的结果。 Where()IEnumerable<FooType>进行操作并过滤此序列,然后再次生成IEnumerable<FooType>

您在d)中的假设不正确 - results只是IEnumerable<FooType>Where()扩展方法返回的foreach。只有当您遍历枚举(即使用ToList()Where())时,才会为“真实”创建序列。此时 - 如果设置了断点,您甚至可以看到这一点 - 所有Linq扩展方法都依次执行 - IEnumerable扩展方法将询问输入Select()的第一个项目,这将是因为FooType运算符将从底层集合中获取第一个项目并吐出{{1}}项目。

答案 1 :(得分:2)

这样想,因为这是编译时发生的事情:

var results = someCollection.Select(item => item.Foo).Where(foo => foo < 3);

被翻译成

var results = Enumerable.Where(
                  Enumerable.Select(
                      someCollection, item => item.Foo
                  ),
                  foo => foo < 3
              );

现在很明显,WhereSelect的结果进行了操作。然后Where将从其来源(在这种情况下,Enumerable.Select的结果)中拉出并一次生成一个来自源的与谓词匹配的项(在本例中为foo < 3

实现将如下所示:

public static IEnumerable<T> Where<T>(
    IEnumerable<T> source,
    Func<T, bool> predicate
) {
    foreach(var item in source) {
        if(predicate(item)) {
            yield return item;
        }
    }
}

public static IEnumerable<U> Select<T, U>(
    IEnumerable<T> source,
    Func<T, U> project
) {

    foreach(var item in source) {
        yield project(item);
    }
}

所以当你想要从results中提取项目时,Where将从Select拉出,直到找到与谓词匹配的项目为止。它可能需要拉很多物品,直到它找到一个可以回馈给你的物品。同时,每当它从Select拉出时,Select会从someCollection中提取另一个项目,并回退投影(item.Foo)。当您尝试从Where中提取另一个项目时,Where会从Select中提取所需的下一个项目,直到找到一个回复​​给您的项目。如果Select在任何时候someCollection耗尽Where,{{1}}也会知道它已经耗尽了物品的供应量,并且会停止让你回头。