Decade
Decade
Published on 2025-07-16 / 27 Visits
0
0

LinQ查询语句

LINQ 核心概念与常用操作符详解

图表

代码

一、foreach遍历的本质

  1. 底层机制

    • foreach循环本质是获取IEnumerableIEnumerator对象

    • 循环调用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);

执行过程

  1. 创建包装器对象存储源序列和谓词

  2. 迭代时动态应用过滤条件

  3. 仅返回满足条件的元素

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

方法

行为

使用场景

Count()

遍历整个集合计算元素数量。若集合实现了 ICollection<T>,直接返回 Count 属性。

需要精确计数且可接受遍历开销时。

TryGetNonEnumeratedCount()

尝试在不枚举集合的情况下获取计数。成功时返回 true 和计数;失败返回 false

避免遍历大型集合(如流式数据源)时优化性能。

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

方法

返回值

示例

Min()/Max()

具体值(如 int, string

products.Min(p => p.Price) → 返回最低价格值

MinBy()/MaxBy()

整个元素对象

products.MaxBy(p => p.Price) → 返回价格最高的产品对象

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

方法

行为

空集合

多匹配项

First()

返回第一个匹配元素

抛异常

返回第一个

Single()

要求严格唯一匹配

抛异常

抛异常

FirstOrDefault()

返回第一个或默认值(如 null/0

返回默认值

返回第一个

SingleOrDefault()

返回唯一元素或默认值

返回默认值

抛异常

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();

方法

作用

AsParallel()

启用并行执行

WithDegreeOfParallelism(n)

设置最大并发线程数

AsOrdered()/AsUnordered()

控制结果顺序

ForAll()

无缓冲并行执行(不保证顺序)


关键总结:

  1. 计数优化:优先用 TryGetNonEnumeratedCount 避免大型集合遍历。

  2. 极值查询Min/Max 返回值,MinBy/MaxBy 返回对象。

  3. 聚合Aggregate 是万能折叠器,适合自定义累积逻辑。

  4. 元素获取Single 要求严格唯一,First 更宽松。

  5. 集合操作

    • 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
*/

五、执行优化策略

  1. 智能迭代

    • 链式调用时仅遍历一次源集合

    • 操作符组合优化(如Where+Select合并执行)

  2. 短路优化

    • TakeWhile/First等操作符提前终止迭代

    • 避免不必要的完整遍历

  3. 类型特化

    • List/Array等集合使用优化实现

    • 避免额外开销(如索引访问优化)

六、最佳实践

  1. 延迟执行优势

    • 组合多个操作无额外开销

    • 查询定义与实际执行分离

  2. 注意事项

    csharp

    // 错误:多次执行导致重复计算
    var query = data.Where(...);
    int count = query.Count();  // 迭代1次
    foreach(var item in query)  // 迭代2次
    
    // 正确:物化结果
    var results = query.ToList();
  3. 性能敏感场景

    • 考虑直接使用for循环替代LINQ

    • 大型集合避免复杂链式调用


Comment