C# LINQ & Collections Deep Dive

IEnumerable vs IQueryable, query syntax vs method syntax, deferred execution, common operators, and performance pitfalls.

IntermediateUpdated 2026-10-2937K views

Two Syntaxes, One Engine

using System.Linq;

var orders = new List<Order> { /* ... */ };

// 方法语法(最常见)
var recent = orders
    .Where(o => o.CreatedAt >= DateTime.Now.AddDays(-7))
    .OrderByDescending(o => o.Amount)
    .Take(10)
    .Select(o => o.Id)
    .ToArray();

// 查询语法(复杂 join 时更直观)
var q = from o in orders
        where o.CreatedAt >= DateTime.Now.AddDays(-7)
        orderby o.Amount descending
        select o.Id;

Common Operators

操作符作用复杂度/备注
Where过滤O(n) 流式
Select投影映射逐元素
SelectMany打平嵌套集合子列表拼接
OrderBy/ThenBy排序(稳定)O(n log n),需 ToList 才执行
GroupBy分组立即执行聚合
Distinct去重需哈希
First/FirstOrDefault取第一个勿对空集用 First
Any/All存在/全部判断Any 找到即停
ToDictionary转字典键重复抛异常
Sum/Min/Max/Average聚合立即执行
// 分组聚合:按用户统计订单总额
var stats = orders
    .GroupBy(o => o.UserId)
    .Select(g => new { UserId = g.Key, Total = g.Sum(o => o.Amount), Count = g.Count() });

// SelectMany:用户 -> 订单扁平化
var pairs = users.SelectMany(u => u.Orders, (u, o) => new { u.Name, o.Id });

// 字典索引(O(1) 查找)
var byId = orders.ToDictionary(o => o.Id);
if (byId.TryGetValue(42, out var target)) { /* ... */ }

// Left join
var joined = from u in users
             join o in orders on u.Id equals o.UserId into gj
             from o in gj.DefaultIfEmpty()
             select new { u.Name, OrderId = o?.Id };

Deferred Execution and Pitfalls

var query = orders.Where(o => o.Amount > 100);   // 尚未执行
orders.Add(new Order { Amount = 500 });         // 数据源改变
query.Count();      // 501 那条也算进去:query 绑定的是序列本身

// 多次枚举 = 多次计算:缓存一次结果
var list = query.ToList();
list.Count();  list.Count();         // 只算一次

// 无限序列必须 Take/Distinct 截断
// 副作用函数不要用在 Where/Select 中
// IQueryable 的表达式会翻译成 SQL:C# 本地函数翻译不了
int localFn(int x) => x * 2;
// db.Orders.Where(o => localFn(o.Amount) > 10); // ✗ EF 无法翻译
// db.Orders.Where(o => o.Amount * 2 > 10);      // ✓
Memory: IEnumerable holds nothing — materialize with ToList/ToArray when you reuse the result; keep it lazy when the consumer streams once.

LINQ in EF Core

// IQueryable:表达式树翻译为 SQL 在数据库执行
using var db = new ShopContext();

var page = await db.Orders
    .Where(o => o.Status == OrderStatus.Paid)
    .OrderByDescending(o => o.CreatedAt)
    .Skip(20).Take(10)                    // LIMIT 10 OFFSET 20
    .ToListAsync();

// Include = 预加载关联(防 N+1)
var users = await db.Users.Include(u => u.Orders).ToListAsync();

// 两种执行模型
IEnumerable<T>: 内存计算(先 AsEnumerable 拉回)
IQueryable<T>:  翻译成 SQL(翻译不了抛运行时异常)

Use IQueryable as far as possible (filtering, paging, projection on the server); materialize to IEnumerable only for in-memory post-processing. A Where that cannot translate forces client-side evaluation or an exception.

Friends & Partners