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 不会对Where
和someCollection
运算符进行任何其他调用?
谢谢
修改
1)
你在d)中的假设是不正确的 - 结果只是一个 IEnumerable由Where()扩展返回 方法。只有在遍历枚举时(即使用foreach) 或者ToList())将为“真实”创建序列。在那时候 - 如果你设定一个断点,你甚至可以看到这一点 - 所有的Linq 依次执行扩展方法 - Where()扩展方法 将询问输入IEnumerable的第一个项目,这将导致 依次为Select()运算符将从中获取第一个项目 底层集合并吐出一个FooType项目。
a)因此,在将结果对象分配给Where
变量(Select
)时,首先会在赋值语句中调用results
和var results=...
。然后,在枚举Where
时,还会为每个项目调用Select
/ results
(来自someCollection
对象内)?
b)假设results
实例的类型为 C - 何时定义/创建了 C 类?它是由Where
方法定义的,还是由编译器定义的类 C ,因此Where
只返回C
的实例?
2)
仅当您遍历枚举时(即使用foreach或 ToList())将为“真实”创建序列。那时 - 你 甚至可以看到这个,如果你设置一个断点 - 所有Linq扩展 方法依次执行 - Where()扩展方法会询问 输入IEnumerable为其第一个项目,这将导致 Select()运算符依次从底层获取第一个项目 收集并吐出一个FooType项目
a)您说在results
对象Select
和Where
内为集合中的每个项目 I 调用。假设我未实现IEnumerable<>
,那么Select
和Where
如果I
只能在IEnumerable<>
上进行操作类型?
答案 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
);
现在很明显,Where
对Select
的结果进行了操作。然后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}}也会知道它已经耗尽了物品的供应量,并且会停止让你回头。