====== C# LINQ (Language Integrated Query) 详解 ======
**LINQ** (语言集成查询) 是 C# 中的一组功能,它允许你使用统一的语法来查询不同的数据源(如内存集合、数据库、XML 等)。
它的核心优势在于:
* **一致性**:无论数据源是 List 还是 SQL 数据库,查询语法基本一致。
* **强类型**:编译时检查错误,而不是运行时才发现 SQL 拼写错误。
* **可读性**:代码更简洁,意图更明确。
===== 1. 准备数据 (Data Source) =====
为了演示,我们先定义一个简单的 `Student` 类和一些测试数据。
using System;
using System.Collections.Generic;
using System.Linq;
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string ClassName { get; set; }
}
// 模拟数据源
var students = new List
{
new Student { Id = 1, Name = "Alice", Age = 22, ClassName = "A" },
new Student { Id = 2, Name = "Bob", Age = 19, ClassName = "B" },
new Student { Id = 3, Name = "Charlie", Age = 24, ClassName = "A" },
new Student { Id = 4, Name = "David", Age = 20, ClassName = "B" },
new Student { Id = 5, Name = "Eve", Age = 22, ClassName = "A" }
};
===== 2. 两种语法风格 (Syntax Styles) =====
LINQ 有两种写法,它们在编译后是等价的。
==== 2.1 查询语法 (Query Syntax) ====
看起来像 SQL,适合复杂的连接(Join)和分组操作。
// 查找所有年龄大于 20 岁的学生
var query = from s in students
where s.Age > 20
select s;
==== 2.2 方法语法 (Method Syntax) ====
使用 Lambda 表达式,是现代 C# 开发中最常用的方式,支持所有 LINQ 操作符。
// 查找所有年龄大于 20 岁的学生
var query = students.Where(s => s.Age > 20);
===== 3. 常用操作符 (Common Operators) =====
以下示例主要使用**方法语法**。
==== 3.1 筛选 (Filtering) - Where ====
// 找出班级是 "A" 的学生
var classAStudents = students.Where(s => s.ClassName == "A");
==== 3.2 投影 (Projection) - Select ====
用于转换数据,例如只取某一列,或者构造新的对象。
// 只获取所有学生的姓名 (返回 List)
var names = students.Select(s => s.Name);
// 构造匿名对象 (只包含 ID 和 姓名)
var simpleList = students.Select(s => new { s.Id, s.Name });
==== 3.3 排序 (Sorting) - OrderBy / ThenBy ====
// 按年龄升序排序
var sortedByAge = students.OrderBy(s => s.Age);
// 按年龄降序,如果年龄相同则按名字升序
var sortedComplex = students.OrderByDescending(s => s.Age)
.ThenBy(s => s.Name);
==== 3.4 分页 (Partitioning) - Skip / Take ====
常用于 Web 开发中的分页功能。
// 跳过前 2 个,取接下来的 2 个 (获取第 3、4 名学生)
var page2 = students.Skip(2).Take(2);
==== 3.5 聚合 (Aggregation) ====
用于计算统计数据。
int count = students.Count(); // 总数
int maxAge = students.Max(s => s.Age); // 最大年龄
double avgAge = students.Average(s => s.Age); // 平均年龄
int sumAge = students.Sum(s => s.Age); // 年龄总和
==== 3.6 元素获取 (Element Operators) ====
获取单个元素,注意处理空值情况。
// 获取第一个元素,如果没有则抛出异常
var first = students.First(s => s.Age > 20);
// 获取第一个元素,如果没有则返回 null (推荐)
var firstSafe = students.FirstOrDefault(s => s.Age > 100);
// 检查是否包含满足条件的元素 (返回 bool)
bool hasUnderage = students.Any(s => s.Age < 18);
// 检查是否所有元素都满足条件 (返回 bool)
bool allAdults = students.All(s => s.Age >= 18);
===== 4. 高级操作 (Advanced) =====
==== 4.1 分组 (GroupBy) ====
将数据按某个键归类。
// 按班级分组
var grouped = students.GroupBy(s => s.ClassName);
foreach (var group in grouped)
{
Console.WriteLine($"班级: {group.Key}"); // Key 是 ClassName
foreach (var student in group)
{
Console.WriteLine($" - {student.Name}");
}
}
==== 4.2 集合操作 ====
var list1 = new List { 1, 2, 3 };
var list2 = new List { 3, 4, 5 };
var union = list1.Union(list2); // 并集: 1, 2, 3, 4, 5
var intersect = list1.Intersect(list2); // 交集: 3
var except = list1.Except(list2); // 差集 (list1 - list2): 1, 2
var distinct = list1.Concat(list1).Distinct(); // 去重
===== 5. 延迟执行 vs 立即执行 (Important!) =====
这是 LINQ 中最重要的概念之一。
* **延迟执行 (Deferred Execution)**:
* 大多数 LINQ 操作符(如 `Where`, `Select`, `OrderBy`)**不会立即查询数据**。
* 它们只是构建了一个查询计划。
* 只有当你遍历结果(`foreach`)时,查询才会真正执行。
* **立即执行 (Immediate Execution)**:
* 某些方法会强制立即执行查询并把结果存入内存。
* 常见方法:`ToList()`, `ToArray()`, `Count()`, `First()`.
// 1. 定义查询 (此时还没运行)
var query = students.Where(s => s.Age < 20);
// 2. 修改数据源 (注意:我们在定义查询之后修改了数据)
students.Add(new Student { Name = "NewKid", Age = 15 });
// 3. 执行查询 (foreach 或 ToList)
// 结果会包含 "NewKid",因为查询是在这一行才真正执行的!
var result = query.ToList();
**最佳实践**:
- 如果你需要多次遍历同一个查询结果,请先调用 `.ToList()` 缓存结果,否则每次遍历都会重新执行一遍查询逻辑(如果连的是数据库,这会造成性能浪费)。
===== 6. 总结 =====
| 操作类型 | 常用方法 |
| **筛选** | ''Where'', ''OfType'' |
| **投影** | ''Select'', ''SelectMany'' |
| **排序** | ''OrderBy'', ''OrderByDescending'', ''ThenBy'' |
| **聚合** | ''Count'', ''Sum'', ''Min'', ''Max'', ''Average'' |
| **量词** | ''Any'', ''All'', ''Contains'' |
| **分区** | ''Skip'', ''Take'' |
| **转换** | ''ToList'', ''ToArray'', ''ToDictionary'' |