C# LINQ —— 集合操作的瑞士军刀

LINQ(Language Integrated Query)是 C# 最具代表性的特性之一——它把"查询"变成语言级语法,让你用统一的方式筛选、变换、聚合任何集合(数组、List、Dictionary、数据库表、XML)。学会 LINQ,你处理数据的效率会提升一个量级

一、LINQ 是什么?

LINQ 在 C# 3(.NET 3.5,2007)引入,核心理念是"把数据库的查询能力搬到语言里"。它能做的事:

LINQ 不只是一个语法糖——它建立在 IEnumerable<T> + 扩展方法 + Lambda + 延迟执行之上,是 C# 设计水平的体现。

二、两种写法

LINQ 有两种等价的写法:

两者功能等价,编译后生成的 IL 一样。本系列示例优先用方法语法,因为更接近现代 C# 风格。

三、过滤 Where 与投影 Select

using System.Linq;

int[] nums = { 5, 2, 8, 1, 9, 3, 7, 4, 6 };

// Where:过滤
var evens = nums.Where(n => n % 2 == 0);
// [2, 8, 4, 6]
var big = nums.Where(n => n > 5);
// [8, 9, 7, 6]

// Select:投影(转换每个元素)
var doubled = nums.Select(n => n * 2);
// [10, 4, 16, 2, 18, 6, 14, 8, 12]
var strings = nums.Select(n => $"数字 {n}");
var indexed = nums.Select((n, i) => $"第 {i}: {n}");

// 链式调用:filter -> transform
var result = nums
    .Where(n => n > 3)
    .Select(n => n * 10);
// [50, 80, 90, 70, 40, 60]

Lambda 表达式 n => n % 2 == 0 是 LINQ 的灵魂——"输入 n,返回判断结果"。链式调用让代码声明式地描述"要什么",而不是"怎么做"。

四、排序 OrderBy / ThenBy

int[] nums = { 5, 2, 8, 1, 9 };

// OrderBy:升序
var asc = nums.OrderBy(n => n);          // [1, 2, 5, 8, 9]
// OrderByDescending:降序
var desc = nums.OrderByDescending(n => n); // [9, 8, 5, 2, 1]

// 多级排序:ThenBy
var students = new List<Student>
{
    new() { Name = "Tom", Grade = 3, Score = 85 },
    new() { Name = "Bob", Grade = 3, Score = 90 },
    new() { Name = "Amy", Grade = 2, Score = 88 },
};

var sorted = students
    .OrderBy(s => s.Grade)
    .ThenByDescending(s => s.Score);
// 按年级升序,同年级按分数降序

// Reverse:翻转
var rev = nums.Reverse();    // [9, 1, 8, 2, 5]

注意:

五、聚合与存在检查

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

// Count
int total = nums.Count();              // 5
int evens = nums.Count(n => n % 2 == 0); // 2

// Sum / Min / Max / Average
int sum = nums.Sum();      // 15
int max = nums.Max();      // 5
int min = nums.Min();      // 1
double avg = nums.Average();   // 3

// Aggregate:自定义聚合(类似 reduce/fold)
int product = nums.Aggregate((a, b) => a * b);   // 120 = 1*2*3*4*5
string concat = nums.Aggregate("", (s, n) => s + n + ",");   // "1,2,3,4,5,"

// Any / All
bool hasBig = nums.Any(n => n > 4);    // True
bool allPos = nums.All(n => n > 0);    // True

// First / FirstOrDefault / Single
int first = nums.First();              // 1
int firstBig = nums.First(n => n > 3); // 4
int notFound = nums.FirstOrDefault(n => n > 100);   // 0(默认值)
int single = new[] { 42 }.Single();    // 42(序列唯一元素)

几个易混点:

六、分组 GroupBy 与去重 Distinct

var orders = new List<Order>
{
    new() { Customer = "Tom", Amount = 100 },
    new() { Customer = "Tom", Amount = 200 },
    new() { Customer = "Amy", Amount = 50 },
    new() { Customer = "Amy", Amount = 80 },
    new() { Customer = "Bob", Amount = 300 },
};

// GroupBy:按客户分组
var groups = orders.GroupBy(o => o.Customer);
foreach (var g in groups)
{
    Console.WriteLine($"{g.Key}: {g.Sum(o => o.Amount)}");
    // Tom: 300 / Amy: 130 / Bob: 300
}

// 分组后投影
var stats = orders
    .GroupBy(o => o.Customer)
    .Select(g => new
    {
        Customer = g.Key,
        Total = g.Sum(o => o.Amount),
        Count = g.Count(),
        Average = g.Average(o => o.Amount)
    })
    .OrderByDescending(s => s.Total);

// Distinct:去重
var nums2 = new[] { 1, 2, 2, 3, 3, 3 };
var unique = nums2.Distinct();   // [1, 2, 3]

// Chunk(C# 8+):分块
var chunks = nums.Chunk(2);   // [[1,2], [3,4], [5]]

GroupBy 返回 IEnumerable<IGrouping<TKey, TElement>>,每个分组有 Key 和元素序列。配合 Select 投影出统计结果是经典模式。

七、查询语法(Query Syntax)

int[] nums = { 5, 2, 8, 1, 9, 3, 7 };

// 查询语法:类似 SQL(C# 特色)
var result = from n in nums
             where n > 3
             orderby n descending
             select n * 10;
// [90, 80, 70, 50]

// 多 from:类似 SelectMany(扁平化)
var matrix = new[] { new[] { 1, 2 }, new[] { 3, 4 } };
var flat = from row in matrix
           from n in row
           select n;
// [1, 2, 3, 4]

// join:连接两个集合
var students = new[] { (Id: 1, Name: "Tom"), (Id: 2, Name: "Amy") };
var scores = new[] { (StuId: 1, Score: 90), (StuId: 2, Score: 85) };

var joined = from s in students
             join sc in scores on s.Id equals sc.StuId
             select new { s.Name, sc.Score };
// {Tom, 90}, {Amy, 85}

// group ... by
var grouped = from o in orders
              group o by o.Customer into g
              select new { Customer = g.Key, Total = g.Sum(x => x.Amount) };

查询语法在多表 Join、SelectMany、Group 等复杂场景比方法语法清晰。日常 Where/Select/OrderBy 用方法语法更紧凑。

八、延迟执行(重要!)

LINQ 查询是延迟执行(lazy)的——定义时不跑,真正遍历/聚合时才跑:

// 延迟执行:LINQ 查询定义时不执行,遍历/聚合时才执行
var nums = new List<int> { 1, 2, 3, 4, 5 };

var evens = nums.Where(n => n % 2 == 0);
// 此时还没执行

nums.Add(6);   // 修改源数据
nums.Add(8);

foreach (var n in evens)
    Console.WriteLine(n);
// 2 4 6 8(看到了刚加的 6 和 8!)

// 强制立即执行:ToList / ToArray / ToDictionary / Count / Sum 等
var frozen = nums.Where(n => n % 2 == 0).ToList();
nums.Add(10);
// frozen 还是 [2, 4, 6, 8],不会变

// 性能陷阱:多次遍历同一个 LINQ 查询,重复执行
var query = nums.Where(ExpensiveFilter);
foreach (var n in query) { }
foreach (var n in query) { }   // ExpensiveFilter 跑了两遍!
// 缓存结果
var cached = query.ToList();

延迟执行的好处:

延迟执行的:

九、Join 与 SelectMany

var students = new[]
{
    new { Id = 1, Name = "Tom", ClassId = 1 },
    new { Id = 2, Name = "Amy", ClassId = 2 },
};

var classes = new[]
{
    new { Id = 1, Name = "一班" },
    new { Id = 2, Name = "二班" },
};

// Join:内连接
var joined = students.Join(
    classes,
    s => s.ClassId,
    c => c.Id,
    (s, c) => new { Student = s.Name, Class = c.Name }
);
// { Tom, 一班 }, { Amy, 二班 }

// SelectMany:扁平化(嵌套集合 -> 单层)
var teachers = new[]
{
    new { Name = "T1", Students = new[] { "Tom", "Bob" } },
    new { Name = "T2", Students = new[] { "Amy" } },
};

var all = teachers.SelectMany(t => t.Students);
// [Tom, Bob, Amy]

// GroupJoin:左连接(每个 left 元素 + 匹配的 right 集合)
var groupJoin = teachers.GroupJoin(
    students,
    t => t.Name,
    s => s.ClassId.ToString(),   // 假设有关系
    (t, ss) => new { Teacher = t.Name, Count = ss.Count() }
);

十、ToObject / ToDictionary / ToLookup

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

// ToList / ToArray
List<int> list = nums.ToList();
int[] arr = nums.ToArray();

// ToDictionary:转字典(key 必须唯一)
var dict = nums.ToDictionary(n => n, n => n * n);
// {1:1, 2:4, 3:9, 4:16, 5:25}

// ToLookup:一对多(类似 Dictionary<K, IEnumerable<V>>)
var students = new[]
{
    new { Name = "Tom", Grade = "A" },
    new { Name = "Bob", Grade = "A" },
    new { Name = "Amy", Grade = "B" },
};

var byGrade = students.ToLookup(s => s.Grade, s => s.Name);
foreach (var name in byGrade["A"])   // Tom, Bob
    Console.WriteLine(name);

// ToHashSet
var set = nums.ToHashSet();

十一、LINQ 处理对象集合示例

public record Order(string Customer, string Product, int Qty, decimal Price);

var orders = new List<Order>
{
    new("Tom", "鼠标", 2, 99.9m),
    new("Tom", "键盘", 1, 199m),
    new("Amy", "鼠标", 1, 99.9m),
    new("Bob", "显示器", 1, 999m),
    new("Amy", "鼠标", 3, 99.9m),
};

// 1. 每个客户的总消费
var topCustomers = orders
    .GroupBy(o => o.Customer)
    .Select(g => new { Customer = g.Key, Total = g.Sum(o => o.Qty * o.Price) })
    .OrderByDescending(x => x.Total)
    .Take(3);
// [{Tom, 398.8}, {Amy, 399.6}, {Bob, 999}]

// 2. 最畅销商品
var topProduct = orders
    .GroupBy(o => o.Product)
    .Select(g => new { Product = g.Key, TotalQty = g.Sum(o => o.Qty) })
    .OrderByDescending(x => x.TotalQty)
    .First();
// {鼠标, 6}

// 3. 平均客单价
var avgOrder = orders.Select(o => o.Qty * o.Price).Average();

// 4. 谁买了超过 1 件商品
var bulkBuyers = orders
    .Where(o => o.Qty > 1)
    .Select(o => o.Customer)
    .Distinct();
// [Tom, Amy]

这就是 LINQ 的威力——上面这些查询如果用传统 foreach,代码量会多 3 倍,可读性差 10 倍。

十二、LINQ to Entities(数据库)

同样的 LINQ 语法可以用来查数据库——EF Core / EF / LINQ to SQL 把 LINQ 表达式翻译成 SQL:

// EF Core 中
using var db = new AppDbContext();

// 这段 LINQ 会翻译成 SQL:SELECT * FROM Users WHERE Age > 18 ORDER BY Name
var adults = db.Users
    .Where(u => u.Age > 18)
    .OrderBy(u => u.Name)
    .ToList();

// JOIN 也行
var orders = db.Orders
    .Where(o => o.Amount > 100)
    .Join(db.Customers,
        o => o.CustomerId,
        c => c.Id,
        (o, c) => new { c.Name, o.Amount })
    .ToList();

同一套语法,既能查内存集合,又能查数据库,这是 LINQ 设计的巨大成功

十三、常见陷阱

小结

LINQ 是 C# 的核心杀手锏。学会它,你处理集合的代码会变得声明式、简洁、强大。记住几个核心:Where 过滤、Select 转换、OrderBy 排序、GroupBy 分组、Any/All/First 检查、ToList 物化。下一篇我们看异常处理——让程序更健壮。

← 上一篇 C# 集合

下一篇 C# 异常处理

✈️💬