补充:
关于LINQ语句何时执行,特别是Where语句,似乎存在一些困惑。我创建了一个小程序来展示源数据实际被访问的时间。结果在本答案末尾。
补充结束
你必须意识到大多数LINQ函数的惰性。
惰性LINQ函数只会在你开始枚举时改变IEnumerable.GetEnumerator()
将返回的Enumerator
。因此,只要调用惰性LINQ函数,查询就不会被执行。
只有当你开始枚举时,查询才会被执行。枚举是在调用foreach
或非惰性LINQ函数(如ToList()
、Any()
、FirstOrDefault()
、Max()
等)时开始的。
在每个LINQ函数的注释部分都描述了该函数是否惰性。你还可以通过检查返回值来确定函数是否惰性。如果它返回一个IEnumerable<...>(或IQueryable),那么LINQ尚未被枚举。
这种惰性的好处是,只要使用惰性函数,改变LINQ表达式就不会耗费时间。只有当你使用非惰性函数时,你必须意识到它的影响。
例如,如果获取序列的第一个元素需要很长时间来计算,因为涉及排序、分组、数据库查询等,请确保不要多次枚举同一序列(不要为同一序列使用非惰性函数)
不要在家里这样做:
假设你有以下查询
var query = toDoLists
.Where(todo => todo.Person == me)
.GroupBy(todo => todo.Priority)
.Select(todoGroup => new
{
Priority = todoGroup.Key,
Hours = todoGroup.Select(todo => todo.ExpectedWorkTime).Sum(),
}
.OrderByDescending(work => work.Priority)
.ThenBy(work => work.WorkCount);
这个查询只包含懒加载的LINQ函数。在所有这些语句执行之后,todoLists
还没有被访问。
但是,一旦获取了结果序列的第一个元素,所有元素都必须被访问(可能多次)以按优先级对它们进行分组、计算涉及的总工作时间并按降序优先级排序。
Any()和First()也是如此:
if (query.Any())
{
var highestOnTodoList = query.First();
Process(highestOnTodoList);
}
else
{
GoFishing();
}
在这种情况下,最好使用正确的函数:
var highestOnToDoList = query.FirstOrDefault()
if (highestOnTioDoList != null)
etc.
回到你的问题
Enumerable.Select
语句仅为您创建了一个IEnumerable
对象,您忘记枚举它了。
此外,您多次构造了CustomerRepo。这是有意为之吗?
ICustomerRepo repo = new CustomerRepo();
IEnumerable<Task<CustomerRepo>> query = ids.Select(id => repo.getCustomer(i));
foreach (var task in query)
{
id = await task;
Console.WriteLine(id);
}
补充:LINQ语句何时执行?
我创建了一个小程序来测试LINQ语句何时执行,特别是在执行Where时。
一个返回IEnumerable的函数:
IEnumerable<int> GetNumbers()
{
for (int i=0; i<10; ++i)
{
yield return i;
}
}
使用老式枚举器的程序。
public static void Main()
{
IEnumerable<int> number = GetNumbers();
IEnumerable<int> smallNumbers = numbers.Where(number => number < 3);
IEnumerator<int> smallEnumerator = smallNumbers.GetEnumerator();
bool smallNumberAvailable = smallEnumerator.MoveNext();
while (smallNumberAvailable)
{
int smallNumber = smallEnumerator.Current;
Console.WriteLine(smallNumber);
smallNumberAvailable = smallEnumerator.MoveNext();
}
}
在调试过程中,我注意到当第一次调用MoveNext()时,
GetNumbers将会被执行,直到第一个yield return语句。 每次调用MoveNext()之后,yield return之后的语句将被执行,直到下一个yield return被执行。通过改变代码,使用foreach、Any()、FirstOrDefault()、ToDictionary等访问枚举器的方法,可以显示出这些函数调用是实际访问源的时间点。
if (smallNumbers.Any())
{
int x = smallNumbers.First();
Console.WriteLine(x);
}
调试显示,源代码 从头开始枚举两次。 因此,这样做确实不明智,特别是如果您需要执行许多计算以计算第一个元素(GroupBy、OrderBy、数据库访问等)。
foreach
循环,并将操作放在循环体内。Select 应该是选择某些东西,而不是引起效果。 - Eric LippertSelect
可以返回函数的结果,而该函数可能需要从外部服务选择进一步的数据,这将是异步的... - jenson-button-event