它是什么

LINQ(Language Integrated Query)= C# 提供的集合查询语法。对任何实现 IEnumerable<T> 的东西都能用:

var nums = new[] { 1, 2, 3, 4, 5 };

var evens = nums.Where(n => n % 2 == 0).Select(n => n * n);
foreach (var n in evens) Console.WriteLine(n);
// 4, 16

Where 过滤、Select 映射。其余几十个方法。

链式 vs 查询语法

// 链式(方法)
var result = nums
    .Where(n => n > 2)
    .OrderBy(n => n)
    .Select(n => n * 10)
    .ToList();

// 查询语法(SQL-like)
var result2 = (from n in nums
               where n > 2
               orderby n
               select n * 10).ToList();

两者完全等价——查询语法编译时翻译成方法链。新代码用链式,可读 + 全功能(不是所有方法都有查询语法对应)。

最常用的方法

// 过滤
nums.Where(n => n > 2)

// 映射
nums.Select(n => n * 2)
users.Select(u => u.Name)

// 排序
nums.OrderBy(n => n)
nums.OrderByDescending(n => n)
users.OrderBy(u => u.Age).ThenBy(u => u.Name)

// 取部分
nums.Take(3)              // 前 3
nums.Skip(2)              // 跳前 2
nums.Take(2..5)           // 索引 2..5(Take(Range) 自 .NET 6 起)

// 单个值
nums.First()              // 第一个;空抛错
nums.FirstOrDefault()     // 第一个;空返 default
nums.Single()             // 必须正好一个
nums.SingleOrDefault()    // 0 或 1 个
nums.Last() / .LastOrDefault()

// 聚合
nums.Count()
nums.Sum()
nums.Average()
nums.Max() / .Min()
nums.Any()                // 至少一个
nums.Any(n => n > 100)    // 至少一个 > 100
nums.All(n => n > 0)      // 全部 > 0

// 集合操作
nums.Distinct()
nums.Union(other)
nums.Intersect(other)
nums.Except(other)
nums.Contains(3)

// 分组
users.GroupBy(u => u.Department)
     .Select(g => new { Dep = g.Key, Count = g.Count() })

// 连接(类似 SQL JOIN)
users.Join(orders, u => u.Id, o => o.UserId, (u, o) => new { u.Name, o.Total })

物化(执行)

LINQ 是延迟执行——Where / Select 只是描述,不真的算:

var q = nums.Where(n => { Console.WriteLine("check " + n); return n > 2; });
// 这里不打印
foreach (var x in q) Console.WriteLine(x);
// 现在才 check 1, check 2, check 3, 3, check 4, 4, ...

强制立即执行

.ToList()         // → List<T>
.ToArray()        // → T[]
.ToDictionary(...)// → Dictionary
.ToHashSet()      // → HashSet
.ToLookup(...)    // → ILookup(一对多)

.Count()          // 也是立即(要遍历整个序列)
.First()          // 立即

:链上反复 .Count() 反复遍历——记得 ToList 一次再用。

GroupBy 例

var orders = new[] {
    new { User = "Alice", Amount = 100 },
    new { User = "Bob", Amount = 50 },
    new { User = "Alice", Amount = 200 },
};

var byUser = orders
    .GroupBy(o => o.User)
    .Select(g => new {
        User = g.Key,
        Total = g.Sum(o => o.Amount),
        Count = g.Count(),
    });

foreach (var g in byUser)
    Console.WriteLine($"{g.User}: {g.Count} 单, 共 {g.Total}");
// Alice: 2 单, 共 300
// Bob: 1 单, 共 50

内连接

var users = new[] {
    new { Id = 1, Name = "Alice" },
    new { Id = 2, Name = "Bob" },
};
var orders = new[] {
    new { UserId = 1, Total = 100 },
    new { UserId = 1, Total = 200 },
};

var joined = users.Join(
    orders,
    u => u.Id,            // user 的 key
    o => o.UserId,         // order 的 key
    (u, o) => new { u.Name, o.Total }
);

左连接 = GroupJoin + SelectMany

var leftJoin = from u in users
               join o in orders on u.Id equals o.UserId into ords
               from o in ords.DefaultIfEmpty()
               select new { u.Name, Total = o?.Total ?? 0 };

链式写法比较啰嗦——左连接是查询语法略胜的少数场景之一。

SelectMany:扁平化

var teachers = new[] {
    new { Name = "A", Students = new[] { "Alice", "Bob" } },
    new { Name = "B", Students = new[] { "Charlie" } },
};

var allStudents = teachers.SelectMany(t => t.Students);
// "Alice", "Bob", "Charlie"

类似 JS 的 flatMap

自定义比较器

nums.Distinct(EqualityComparer<int>.Default);
users.OrderBy(u => u.Name, StringComparer.OrdinalIgnoreCase);
users.GroupBy(u => u.Email, StringComparer.OrdinalIgnoreCase);

很多 LINQ 方法接受可选的 IEqualityComparer / IComparer 参数。

LINQ to SQL(EF Core)

// 同样的 LINQ 语法,但翻译成 SQL
var users = db.Users
    .Where(u => u.Age >= 18)
    .OrderBy(u => u.Name)
    .Take(10)
    .ToList();   // 这里才真正执行 SQL

EF Core 把 LINQ 表达式树翻译成数据库方言——同一份代码可以查 SQL Server / PostgreSQL / SQLite。

性能

  • LINQ 方法通常比手写循环慢 2-5 倍——不是数量级,是常数倍
  • 热循环里 1M+ 元素 → 写 for / foreach 可能更快
  • 一般业务代码用 LINQ:可读性 >> 微小性能差
// 同样意思
var sum1 = nums.Where(n => n > 0).Sum();   // LINQ
int sum2 = 0;
foreach (var n in nums) if (n > 0) sum2 += n;

实用方法(.NET 6+ 新增)

nums.Chunk(3)                 // 分块:[1,2,3], [4,5,6], ...
nums.MinBy(x => x.Property)   // 最小(带选择器)
nums.MaxBy(x => x.Property)
nums.DistinctBy(x => x.Name)
nums.UnionBy(other, x => x.Id)
nums.Zip(other, (a, b) => ...)

MinBy / MaxBy 特别实用——之前要 OrderBy().First(),现在一行。

→ 下一篇 async / await