IEnumerable 对象本身并不表示对象序列,它表示根据请求为您提供序列的第一个元素作为“当前元素”所需的算法,并为您提供当前元素之后的下一个元素。
当 linq 被发明时,人们决定 linq 使用延迟执行的概念,通常称为惰性求值。在使用延迟执行的 Enumerable 函数的 MSDN 描述中,您会发现以下短语:
该方法是通过使用延迟执行来实现的。立即返回值是一个存储执行操作所需的所有信息的对象。在通过直接调用其 GetEnumerator 方法或使用 foreach 枚举对象之前,不会执行此方法表示的查询。
如果您创建 IEnumerable,并更改 IEnumerable 对象作用的对象,则此更改可能会影响结果。如果函数作用的参数发生变化,它类似于返回不同值的函数:
int x = 4;
int y = 5;
int MyFunction()
{
return x + y;
}
int a = MyFunction();
y = 7;
int b = MyFunction();
现在 b 不等于 a。类似于您的 IEnumerable:
List<...> myList = CreateMySequence()
var IEnumerable<...> myOrder = myList.OrderBy(...);
myOrder 不包含结果,但就像一个可以为它计算结果的函数。如果您更改 myOrder 使用的参数之一,结果可能会更改:
myList.Add(someElement);
var myResult = myOrder.ToList();
myResult 已更改,因为您更改了函数。
发明延迟执行的原因是因为您通常不需要枚举序列的所有元素。在以下情况下,如果您要创建完整的序列,则会浪费处理时间:
- 我只想要第一个元素,
- 我想跳过 3 个元素,然后取两个元素,
- 我想要值为 x 的第一个元素
- 我想知道序列是否包含任何元素
当然,一旦您要求第一个元素,有些函数需要创建完整的序列:
- 如果您想要排序序列中的第一个,则必须对所有元素进行排序才能找到第一个。
- 如果您想要一组元素中的第一个元素,其中该组中的所有元素都具有某个属性 X (Enumerable.GroupBy) 的相同值
根据经验,明智的做法是尽可能长时间地将所有序列保持为 IEnumerable,直到您需要结果,或者直到用于创建序列的源发生更改。
后者在从数据库、文件、互联网获取数据时很重要:您必须在连接关闭之前创建序列。
以下行不通
using (var myDbContext = new MyDbContext)
{
return MyDbContext.Customers.Where(customer => customer.Age > 18);
}
离开 using 语句时,在 Disposed myDbContext 之前不会执行数据库查询。因此,只要您要求序列中的任何元素,您就会得到一个例外。