LINQ 核心概念与常用操作符详解
图表
代码
一、foreach
遍历的本质
底层机制:
foreach
循环本质是获取IEnumerable
的IEnumerator
对象循环调用
MoveNext()
方法并访问Current
属性当
MoveNext()
返回false
时结束循环
csharp
IEnumerator enumerator = collection.GetEnumerator();
try {
while (enumerator.MoveNext()) {
var item = enumerator.Current;
// 循环体
}
} finally {
(enumerator as IDisposable)?.Dispose();
}
二、LINQ核心机制
1. 扩展方法特性
LINQ操作符都是
IEnumerable<T>
的扩展方法定义在
System.Linq
命名空间采用延迟执行(Deferred Execution)策略
2. 延迟执行原理
csharp
var query = collection.Where(x => x > 5); // 仅创建查询,不执行
foreach (var item in query) // 实际执行查询
{
// 迭代时才会执行过滤操作
}
三、核心操作符详解
1. Where - 条件过滤
csharp
// 返回所有偶数
var evenNumbers = numbers.Where(n => n % 2 == 0);
执行过程:
创建包装器对象存储源序列和谓词
迭代时动态应用过滤条件
仅返回满足条件的元素
2. OfType - 类型过滤
csharp
IEnumerable mixedList = new ArrayList { 1, "two", 3.0, "four", 5 };
// 仅返回字符串元素
var strings = mixedList.OfType<string>();
3. Skip/Take - 分页操作
csharp
// 跳过前5个元素,取接下来的10个
var page = records.Skip(5).Take(10);
// 等效SQL: OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY
4. SkipLast/TakeLast - 首尾操作
csharp
// 排除最后3个元素
var allButLastThree = data.SkipLast(3);
// 仅取最后2个元素
var lastTwo = data.TakeLast(2);
5. TakeWhile - 条件截取
csharp
int[] numbers = { 1, 2, 3, 4, 5, 1, 2, 3 };
// 取元素直到遇到>3的值 → [1, 2, 3]
var result = numbers.TakeWhile(n => n <= 3);
6. Select - 投影转换
csharp
List<Person> people = ...;
// 提取姓名属性
var names = people.Select(p => p.Name);
// 类型转换
var lengths = names.Select(s => s.Length);
7. SelectMany - 扁平化处理
csharp
class School {
public List<Student> Students { get; set; }
}
List<School> schools = ...;
// 将所有学生合并到单个序列
var allStudents = schools.SelectMany(sch => sch.Students);
等效嵌套循环:
csharp
foreach (var school in schools)
foreach (var student in school.Students)
yield return student;
8. 计数方法:Count
vs TryGetNonEnumeratedCount
csharp
var numbers = new List<int> { 1, 2, 3 };
// 直接获取计数(高效)
if (numbers.TryGetNonEnumeratedCount(out int count)) {
Console.WriteLine($"Count: {count}"); // 输出: Count: 3
}
// 强制枚举(可能低效)
var query = numbers.Where(n => n > 1);
int result = query.Count(); // 遍历查询结果
9. 极值查询:Min
/Max
vs MinBy
/MaxBy
csharp
var products = new[] {
new { Name = "A", Price = 100 },
new { Name = "B", Price = 50 }
};
// 返回值
int minPrice = products.Min(p => p.Price); // 50
// 返回对象
var cheapest = products.MinBy(p => p.Price); // { Name="B", Price=50 }
注意:
MinBy
/MaxBy
在 .NET 6+ 引入,之前需用OrderBy(...).First()
替代。
10. 聚合操作:Aggregate
作用:将集合折叠为单一结果(类似递归/循环)。
csharp
// 计算乘积
int[] nums = { 1, 2, 3, 4 };
int product = nums.Aggregate((acc, next) => acc * next); // 1*2*3*4 = 24
// 拼接字符串
string[] words = { "a", "b", "c" };
string result = words.Aggregate("", (acc, w) => acc + w); // "abc"
// 带种子值
int sum = nums.Aggregate(10, (acc, n) => acc + n); // 10+1+2+3+4 = 20
11. 元素获取:First
vs Single
vs FirstOrDefault
csharp
var numbers = new[] { 1, 2, 2, 3 };
numbers.First(x => x == 2); // 2 (第一个2)
numbers.Single(x => x == 3); // 3 (唯一)
numbers.First(x => x == 4); // 异常!
numbers.FirstOrDefault(x => x == 4); // 0 (默认值)
最佳实践:
预期唯一结果 →
Single()
预期至少一个 →
First()
不确定是否存在 →
FirstOrDefault()
12. 集合操作
去重:Distinct
csharp
int[] nums = { 1, 2, 2, 3 };
var unique = nums.Distinct(); // { 1, 2, 3 }
交集(Intersect
)与并集(Union
)
csharp
var set1 = new[] { 1, 2, 3 };
var set2 = new[] { 2, 3, 4 };
var intersection = set1.Intersect(set2); // { 2, 3 }(共同元素)
var union = set1.Union(set2); // { 1, 2, 3, 4 }(合并后去重)
比较集合相等:SequenceEqual
csharp
int[] a = { 1, 2 };
int[] b = { 1, 2 };
bool areEqual = a.SequenceEqual(b); // true(顺序和值完全一致)
合并序列:Zip
csharp
var names = new[] { "Alice", "Bob" };
var scores = new[] { 90, 85 };
var results = names.Zip(scores, (name, score) => $"{name}: {score}");
// 输出: ["Alice: 90", "Bob: 85"]
13. Parallel LINQ (PLINQ) 核心方法
通过 .AsParallel()
启用并行查询:
csharp
var result = numbers.AsParallel()
.WithDegreeOfParallelism(4) // 限制并行度为4
.WithCancellation(token) // 支持取消
.Where(n => n % 2 == 0)
.AsOrdered() // 保持原始顺序
.ToArray();
关键总结:
计数优化:优先用
TryGetNonEnumeratedCount
避免大型集合遍历。极值查询:
Min
/Max
返回值,MinBy
/MaxBy
返回对象。聚合:
Aggregate
是万能折叠器,适合自定义累积逻辑。元素获取:
Single
要求严格唯一,First
更宽松。集合操作:
Distinct
去重Intersect
/Union
处理集合关系SequenceEqual
严格比较顺序和值Zip
合并两个序列
四、操作List的完整示例
csharp
List<Employee> employees = new List<Employee>
{
new Employee { ID=1, Name="John", Dept="HR", Skills=new List<string>{"Recruiting", "Interviewing"}},
new Employee { ID=2, Name="Mary", Dept="IT", Skills=new List<string>{"C#", "SQL"}},
new Employee { ID=3, Name="Tom", Dept="IT", Skills=new List<string>{"Python", "Azure"}},
new Employee { ID=4, Name="Anna", Dept="Finance", Skills=new List<string>{"Excel", "Reporting"}}
};
// 复杂查询
var result = employees
.Where(e => e.Dept == "IT") // 过滤部门
.Skip(0).Take(10) // 分页
.SelectMany(e => e.Skills // 展开技能
.Select(skill => new { e.Name, skill }))
.OrderBy(x => x.skill) // 按技能排序
.Select(x => $"{x.Name}: {x.skill}"); // 格式化输出
/* 输出:
Mary: C#
Tom: Azure
Mary: SQL
Tom: Python
*/
五、执行优化策略
智能迭代:
链式调用时仅遍历一次源集合
操作符组合优化(如
Where
+Select
合并执行)
短路优化:
TakeWhile
/First
等操作符提前终止迭代避免不必要的完整遍历
类型特化:
对
List
/Array
等集合使用优化实现避免额外开销(如索引访问优化)
六、最佳实践
延迟执行优势:
组合多个操作无额外开销
查询定义与实际执行分离
注意事项:
csharp
// 错误:多次执行导致重复计算 var query = data.Where(...); int count = query.Count(); // 迭代1次 foreach(var item in query) // 迭代2次 // 正确:物化结果 var results = query.ToList();
性能敏感场景:
考虑直接使用
for
循环替代LINQ大型集合避免复杂链式调用