

























Where、Select)不立即执行,只有在迭代(foreach、ToList、Count、First 等)时才会真正遍历数据源并执行逻辑。var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenQuery = numbers.Where(n => n % 2 == 0);
int count = evenQuery.Count(); // 遍历一次,筛选
int first = evenQuery.First(); // 又遍历一次,直到找到第一个偶数
潜在问题:
追问应对:
.ToList() 或 .ToArray() 将结果物化,后续操作基于内存副本。ToList() 会失去延迟执行的优势(如数据库分页过滤),需权衡。var evenList = evenQuery.ToList(); // 只执行一次
int count = evenList.Count; // 直接取属性,不重复遍历
int first = evenList[0];
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.Write(i)); // 捕获同一个变量 i
}
foreach (var a in actions) a(); // 输出 "333",而非 "012"
原因:Lambda 表达式中捕获的是变量本身,而不是创建时的值。for 循环中只有一个 i 变量,循环结束后 i 的值为 3。
追问变种:
foreach 呢?在 C# 5.0 之前,foreach 也存在同样问题;C# 5.0+ 中 foreach 循环变量在每次迭代中会被复制(类似新变量),因此不会出错。for (int i = 0; i < 3; i++)
{
int temp = i; // 每次迭代创建一个新变量
actions.Add(() => Console.Write(temp));
}
// 输出 "012"
在 LINQ 中的体现:
var items = new List<int> { 1, 2, 3 };
var query = items.Select(x => x * multiplier); // multiplier 变化会影响结果
IEnumerable<User> query;
using (var db = new MyDbContext())
{
query = db.Users.Where(u => u.Age > 18); // 未执行,只是定义查询
} // 此处释放数据库连接
var result = query.ToList(); // ❌ 枚举时连接已关闭 → ObjectDisposedException
原因:Where 返回的 IQueryable<T> 或 IEnumerable<T> 依赖于 DbContext 实例。using 块结束后,上下文被销毁,但查询尚未执行。等到 ToList() 枚举时才尝试访问已释放的数据库连接。
正确做法:
using 块内部立即物化结果:.ToList()。DbContext 存活直到枚举完成(例如注入到上层生命周期中)。类似场景:
File.ReadLines(path) 返回延迟执行序列,若在 using 外枚举会抛出异常。yield return)中依赖的资源。var expensiveQuery = GetDataFromApi().Where(...); // 延迟执行
Log($"Count = {expensiveQuery.Count()}"); // 执行1次
foreach (var item in expensiveQuery) // 执行2次(又请求API)
{
Process(item);
}
后果:在调试时,鼠标悬停展开查询变量可能会触发迭代(取决于调试器实现),导致意想不到的数据获取。
建议:
ToList()。System.Linq.Enumerable 中的扩展方法时,注意它们是否会触发迭代(Count、Any、First 都会)。IQueryable<User> dbQuery = db.Users.Where(u => u.Age > 18);
IEnumerable<User> enumerable = dbQuery; // 隐式转换
// 后续使用 Where 扩展方法:使用的是 Enumerable.Where 还是 Queryable.Where?
var result = enumerable.Where(u => u.Name.StartsWith("A")).ToList();
IQueryable 的 Where 会构建表达式树,最终在数据库端执行过滤。IEnumerable,后续的 LINQ 操作符(如 Where)将使用 Enumerable 扩展方法,数据会从数据库全部加载到内存,然后在内存中执行过滤,性能灾难。正确做法:保持 IQueryable 类型,直到所有查询条件都拼接完成,最后再 .ToList()。
如何避免重复枚举?
.ToList() 或 .ToArray() 缓存结果。foreach 处理,不保留查询变量。延迟执行的好处有哪些?
Select 只在实际需要时计算)。Fibonacci().Take(10))。哪些 LINQ 方法会立即执行?
Count、Sum、Average、Max、Min、AggregateFirst、Last、Single、ElementAtToList、ToArray、ToDictionary、ToLookupAny、All、Contains如何判断一个方法是延迟还是立即执行?
IEnumerable<T> 或 IOrderedEnumerable<T> 等接口/类型;int、bool、T、List<T> 等)。IQueryable 与 IEnumerable,避免无意中切换到内存模式。此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。