在 C# 开发中,数据处理占据了大量工作。过去我们需要手写循环、条件判断来筛选和转换集合数据,代码冗长且容易出错。LINQ(Language Integrated Query,语言集成查询)的出现彻底改变了这一局面,它让我们能够用类似 SQL 的声明式语法,对集合、数组、数据库、XML 等多种数据源进行统一查询。掌握 LINQ 不仅能让代码更简洁,还能显著提升开发效率。本文将从基础语法讲到进阶技巧,配合丰富的代码示例,带你全面掌握 C# 中的 LINQ 数据查询。

一、LINQ 的两种查询语法:查询语法与方法语法
LINQ 提供两种写法:查询语法(Query Syntax)和方法语法(Method Syntax)。查询语法更接近 SQL,可读性好;方法语法基于扩展方法和 Lambda 表达式,功能更全面(部分操作符如 Count、First 只有方法形式)。两种语法可以混用,编译后生成的代码完全相同。
int[] numbers = { 5, 2, 8, 1, 9, 3 };
// 查询语法
var query1 = from n in numbers
where n > 3
select n;
// 方法语法(效果等价)
var query2 = numbers.Where(n => n > 3);
foreach (var n in query1)
{
Console.WriteLine(n); // 输出 5 8 9
}初学者建议先从查询语法入手理解思路,实际项目中方法语法使用更普遍,因为可以链式调用,且不受操作符覆盖范围的限制。两者没有性能差异,选择哪一种主要看团队习惯和代码可读性。
二、筛选、投影与排序:Where、Select、OrderBy
这是 LINQ 最常用的三个操作。Where 用于按条件筛选元素,Select 用于把元素投影成新形态(类似 SQL 的 SELECT 列),OrderBy / OrderByDescending 用于排序,还可以用 ThenBy 实现多级排序。
var students = new[]
{
new { Name = "张三", Age = 20, Score = 85 },
new { Name = "李四", Age = 22, Score = 92 },
new { Name = "王五", Age = 20, Score = 78 }
};
var result = students
.Where(s => s.Score >= 80) // 筛选成绩合格的学生
.OrderByDescending(s => s.Score) // 按成绩降序
.ThenBy(s => s.Age) // 成绩相同时按年龄升序
.Select(s => new { s.Name, s.Score }); // 只取需要的字段
foreach (var s in result)
{
Console.WriteLine($"{s.Name}: {s.Score}");
}需要注意 Select 投影成匿名类型后,返回的结果类型与原集合不同,这在只关心部分字段时可以减少内存占用。此外,Where 可以串联多次,多个条件会依次过滤,等价于逻辑与的关系。
三、分组与聚合:GroupBy、Count、Sum、Average
分组统计是数据处理的高频需求。GroupBy 按指定的键将元素分组,每组是一个实现了 IGrouping 接口的对象,包含键值和元素序列;配合 Count、Sum、Average、Max、Min 等聚合操作符,可以轻松完成报表类统计。
var orders = new[]
{
new { Product = "手机", Category = "数码", Amount = 4999 },
new { Product = "耳机", Category = "数码", Amount = 399 },
new { Product = "外套", Category = "服装", Amount = 299 },
new { Product = "裤子", Category = "服装", Amount = 199 }
};
var statistics = orders
.GroupBy(o => o.Category)
.Select(g => new
{
Category = g.Key, // 分组的键
Count = g.Count(), // 每组订单数
Total = g.Sum(o => o.Amount) // 每组总金额
});
foreach (var s in statistics)
{
Console.WriteLine($"{s.Category}: {s.Count} 单,合计 {s.Total} 元");
}如果想按多个字段分组,可以用匿名类型作为键:GroupBy(o => new { o.Category, o.Product })。聚合操作符都有带种子值的重载,例如 Aggregate 可以实现自定义累积逻辑,适合求字符串拼接等复杂场景。
四、连接查询与集合操作:Join、Distinct、Union
当需要关联两个数据源时,可以使用 Join,它类似 SQL 的内连接;GroupJoin 则可以实现一对多的分组连接。集合运算方面,Distinct 去重、Union 并集、Intersect 交集、Except 差集也非常实用。
var departments = new[]
{
new { Id = 1, Name = "技术部" },
new { Id = 2, Name = "市场部" }
};
var employees = new[]
{
new { Name = "张三", DeptId = 1 },
new { Name = "李四", DeptId = 2 },
new { Name = "王五", DeptId = 1 }
};
// 内连接:员工与部门关联
var joined = employees.Join(departments,
e => e.DeptId,
d => d.Id,
(e, d) => new { e.Name, DeptName = d.Name });
int[] a = { 1, 2, 3, 3 };
int[] b = { 3, 4, 5 };
var distinct = a.Distinct(); // 1 2 3
var union = a.Union(b); // 1 2 3 4 5(自动去重)
var intersect = a.Intersect(b); // 3
var except = a.Except(b); // 1 2使用 Join 时要特别注意两个键的选择器类型必须一致,否则编译不报错但运行时关联不上。Union 与 Concat 的区别在于前者会去重,后者只是简单拼接。
五、理解延迟执行:LINQ 最容易踩的坑
LINQ 查询默认是延迟执行的(Deferred Execution),即定义查询时并不执行,只有在遍历结果(如 foreach)或调用 ToList、Count 等方法时才真正执行。这是 LINQ 的核心设计,好处是可以组合多个查询而不产生中间集合,但也容易引发问题。
var list = new List<int> { 1, 2, 3 };
var query = list.Where(n => n > 1);
list.Add(4);
foreach (var n in query)
{
Console.WriteLine(n); // 输出 2 3 4,包含后添加的元素
}
// 如果希望立即固定结果,使用 ToList
var fixedList = list.Where(n => n > 1).ToList();延迟执行的另一个陷阱是多次遍历同一个查询会导致重复计算,比如先 Count() 再 foreach,数据源会被枚举两次。如果数据源是数据库,还可能发起两次 SQL 查询。解决办法是在需要复用结果时调用 ToList() 或 ToArray() 物化结果。另外要注意,遍历查询时如果修改了集合,会抛出 InvalidOperationException 异常。
六、实用技巧与性能建议
掌握以下技巧可以让你把 LINQ 用得更加得心应手:
- 善用
Any替代Count() > 0:Any()找到第一个匹配元素就返回,不需要遍历整个集合,性能更好。 - 空值防护:对可能为 null 的集合使用
?? Enumerable.Empty<T>(),避免空引用异常。 First与FirstOrDefault的选择:确定必须有结果时用First(无结果抛异常),不确定时用FirstOrDefault(返回默认值)。ToDictionary快速建索引:需要频繁按键查找时,先把列表转成字典,查找复杂度从 O(n) 降为 O(1)。- 注意 IEnumerable 与 IQueryable 的区别:操作数据库时(如 EF Core),确保查询保持
IQueryable类型,筛选条件才能被翻译成 SQL 在数据库端执行;过早调用AsEnumerable或ToList会把全部数据加载到内存再过滤。
总的来说,LINQ 是 C# 开发者必须熟练掌握的技能。建议从 Where、Select、OrderBy 这几个高频操作练起,逐步过渡到分组、连接和延迟执行等进阶概念。写 LINQ 时始终记住它是声明式的,描述的是“要什么”而不是“怎么做”,思维方式的转变会让你的代码质量提升一个台阶。