C# LINQ与函数式编程
LINQ 查询语法与方法语法、延迟执行、标准查询运算符、PLINQ、表达式树、局部函数、模式匹配进阶
1. LINQ 查询语法与方法语法
1.1 两种语法对比
var products = new List<Product>
{
new("笔记本", 5999m, "电子"),
new("手机", 3999m, "电子"),
new("T恤", 199m, "服装"),
new("耳机", 899m, "电子"),
new("运动鞋", 599m, "服装"),
};
// 查询语法(Query Syntax)
var query1 = from p in products
where p.Price > 500
orderby p.Price descending
select new { p.Name, p.Price };
// 方法语法(Method Syntax / Fluent API)
var query2 = products
.Where(p => p.Price > 500)
.OrderByDescending(p => p.Price)
select new { p.Name, p.Price };
// 查询语法在 join 和 group 时更清晰
var query3 = from p in products
group p by p.Category into g
select new
{
Category = g.Key,
Count = g.Count(),
AvgPrice = g.Average(p => p.Price)
};
1.2 SelectMany - 扁平化
var orders = new List<Order>
{
new(1, [new Item("A", 2), new Item("B", 1)]),
new(2, [new Item("C", 3), new Item("D", 2)]),
};
// 查询语法
var allItems = from o in orders
from i in o.Items
select i;
// 方法语法
var allItems2 = orders.SelectMany(o => o.Items);
// 带结果选择器
var results = orders.SelectMany(
o => o.Items,
(order, item) => new { order.Id, item.Name, item.Quantity }
);
2. 延迟执行
2.1 延迟执行 vs 立即执行
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 延迟执行 - 定义查询但不执行
var query = numbers.Where(n => n > 2); // 此时未执行
numbers.Add(6); // 修改源数据
// 执行查询(每次枚举都重新计算)
foreach (var n in query)
{
Console.WriteLine(n); // 3, 4, 5, 6(包含新增的6)
}
// 立即执行方法
int count = query.Count(); // 立即执行
List<int> list = query.ToList(); // 立即执行并缓存
int[] array = query.ToArray(); // 立即执行并缓存
Dictionary<string, int> dict = query.ToDictionary(
n => n.ToString(), n => n); // 立即执行
2.2 延迟执行陷阱
// 陷阱:闭包捕获循环变量
var actions = new List<Func<int>>();
for (int i = 0; i < 5; i++)
{
actions.Add(() => i); // 所有 lambda 共享同一个 i
}
// actions 全部返回 5
// 解决:局部变量捕获
for (int i = 0; i < 5; i++)
{
int local = i;
actions.Add(() => local); // 每个 lambda 捕获各自的 local
}
// 陷阱:多次枚举
var filtered = products.Where(p => p.Price > 100);
// 每次遍历都重新查询!
var count = filtered.Count(); // 查询1次
foreach (var p in filtered) { } // 又查询1次
// 解决:缓存结果
var cached = products.Where(p => p.Price > 100).ToList();
3. 标准查询运算符
3.1 筛选与投影
// Where - 条件筛选
var active = users.Where(u => u.IsActive);
// OfType - 类型筛选
var strings = mixed.OfType<string>();
// Select - 投影
var names = users.Select(u => u.Name);
// SelectMany - 扁平化投影
var allTags = articles.SelectMany(a => a.Tags).Distinct();
// Zip - 合并两个序列
var combined = names.Zip(ages, (name, age) => $"{name}: {age}");
// Chunk (.NET 6+) - 分块
var chunks = largeList.Chunk(100); // 每块100个
3.2 排序与分组
// OrderBy / OrderByDescending
var sorted = products.OrderBy(p => p.Price);
// ThenBy / ThenByDescending
var multi = products.OrderBy(p => p.Category).ThenBy(p => p.Price);
// Reverse
var reversed = list.Reverse();
// GroupBy
var grouped = products.GroupBy(p => p.Category);
var grouped2 = products.GroupBy(
p => p.Category,
p => p.Name, // 元素选择器
(key, names) => new // 结果选择器
{
Category = key,
Names = names.ToList()
}
);
// ToLookup - 立即执行的分组
var lookup = products.ToLookup(p => p.Category);
var electronics = lookup["电子"]; // O(1) 查找
3.3 聚合与量化
// 聚合
int sum = numbers.Sum();
double avg = numbers.Average();
int max = numbers.Max();
int min = numbers.Min();
// 自定义聚合
string joined = words.Aggregate((a, b) => $"{a}, {b}");
// 带种子值的聚合
int total = orders.Aggregate(0, (sum, o) => sum + o.Amount);
// 量化
bool any = products.Any(p => p.Price > 1000); // 是否存在
bool all = products.All(p => p.Price > 0); // 是否全部满足
bool contains = numbers.Contains(42);
// .NET 9 新方法
var counts = words.CountBy(w => w.Length);
// 结果: [{ Key: 3, Value: 5 }, { Key: 5, Value: 3 }]
var aggregated = orders.AggregateBy(
o => o.CustomerId, 0m, (sum, o) => sum + o.Amount);
3.4 集合操作
// Distinct - 去重
var unique = numbers.Distinct();
var uniqueBy = products.DistinctBy(p => p.Category); // .NET 6+
// Union - 并集
var union = set1.Union(set2);
// Intersect - 交集
var intersect = set1.Intersect(set2);
// Except - 差集
var except = set1.Except(set2);
// Skip / Take - 分页
var page = items.Skip((pageNum - 1) * pageSize).Take(pageSize);
// SkipWhile / TakeWhile - 条件跳过/取值
var result = numbers.SkipWhile(n => n < 5).TakeWhile(n => n < 10);
// DefaultIfEmpty - 空集合默认值
var nonEmpty = emptyList.DefaultIfEmpty(new Product("默认", 0, ""));
3.5 Join 操作
// 内连接
var innerJoin = from p in products
join c in categories on p.CategoryId equals c.Id
select new { p.Name, CategoryName = c.Name };
// 方法语法
var innerJoin2 = products.Join(
categories,
p => p.CategoryId,
c => c.Id,
(p, c) => new { p.Name, CategoryName = c.Name }
);
// 分组连接
var groupJoin = from c in categories
join p in products on c.Id equals p.CategoryId into productsGroup
select new { c.Name, Products = productsGroup };
// 左外连接
var leftJoin = from c in categories
join p in products on c.Id equals p.CategoryId into productsGroup
from p in productsGroup.DefaultIfEmpty()
select new { c.Name, ProductName = p?.Name ?? "无" };
4. PLINQ
// AsParallel - 并行 LINQ
var result = numbers.AsParallel()
.Where(n => IsPrime(n))
.Select(n => n * n)
.OrderBy(n => n)
.ToList();
// 控制并行度
var result2 = numbers.AsParallel()
.WithDegreeOfParallelism(4)
.WithCancellation(ct)
.Select(HeavyTransform);
// ForAll - 并行遍历(无序)
numbers.AsParallel()
.Where(n => n > 100)
.ForAll(n => Process(n)); // 不保证顺序
// 保留顺序
var ordered = numbers.AsParallel()
.AsOrdered()
.Select(Transform);
// 注意:PLINQ 不总是更快
// 1. 集合小时开销大于收益
// 2. 有副作用时不安全
// 3. I/O 操作不适合 PLINQ
5. 表达式树
5.1 基本概念
// Lambda 表达式 vs 表达式树
Func<int, int, int> add = (a, b) => a + b; // 委托
Expression<Func<int, int, int>> addExpr = (a, b) => a + b; // 表达式树
// 编译执行
var compiled = addExpr.Compile();
int result = compiled(3, 4); // 7
// 检查表达式结构
Console.WriteLine(addExpr); // (a, b) => (a + b)
Console.WriteLine(addExpr.Body); // (a + b)
Console.WriteLine(addExpr.NodeType); // Lambda
5.2 构建表达式树
// 手动构建: (int x) => x > 10
ParameterExpression param = Expression.Parameter(typeof(int), "x");
ConstantExpression constant = Expression.Constant(10);
BinaryExpression body = Expression.GreaterThan(param, constant);
Expression<Func<int, bool>> expr =
Expression.Lambda<Func<int, bool>>(body, param);
var func = expr.Compile();
Console.WriteLine(func(15)); // True
// 动态组合表达式
Expression<Func<Product, bool>> IsExpensive = p => p.Price > 1000;
Expression<Func<Product, bool>> IsElectronic = p => p.Category == "电子";
// AND 组合
var combined = Expression.Lambda<Func<Product, bool>>(
Expression.AndAlso(IsExpensive.Body, IsElectronic.Body),
IsExpensive.Parameters[0]);
5.3 EF Core 中的表达式树
// EF Core 利用表达式树将 LINQ 翻译为 SQL
// 方法语法中的 Lambda 被编译为表达式树
var products = await _context.Products
.Where(p => p.Price > 100) // 表达式树 → SQL WHERE
.OrderBy(p => p.Name) // → ORDER BY
.Select(p => new { p.Name, p.Price }) // → SELECT
.ToListAsync();
// 表达式树中不能包含 C# 方法调用
var bad = _context.Products
.Where(p => IsValid(p.Name)); // 无法翻译为 SQL!
// 先在内存中处理或使用可翻译的表达式
var good = _context.Products
.Where(p => p.Name.StartsWith("A") && p.Price > 100);
6. 局部函数
public IEnumerable<int> GetFactors(int number)
{
if (number < 1)
throw new ArgumentException("必须为正整数", nameof(number));
// 局部函数 - 可以访问外部变量
IEnumerable<int> GetFactorsInternal()
{
for (int i = 1; i <= number; i++)
{
if (number % i == 0)
yield return i;
}
}
return GetFactorsInternal();
}
// 静态局部函数(不捕获外部变量,性能更好)
public int Calculate(int x, int y)
{
return Multiply(x, y) + Add(x, y);
static int Multiply(int a, int b) => a * b;
static int Add(int a, int b) => a + b;
}
// 局部函数用于迭代器中的参数验证
public static IEnumerable<T> Take<T>(IEnumerable<T> source, int count)
{
if (source is null) throw new ArgumentNullException(nameof(source));
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
return Iterator();
IEnumerable<T> Iterator()
{
var i = 0;
foreach (var item in source)
{
if (i++ >= count) yield break;
yield return item;
}
}
}
7. 模式匹配进阶
7.1 完整的模式匹配体系
// 声明模式
if (obj is string s) { }
if (obj is int[] arr) { }
if (obj is List<int> list) { }
// 类型模式(C# 9+,不需要变量名)
if (obj is string) { }
switch (obj)
{
case int: return "整数";
case string: return "字符串";
}
// 属性模式
if (person is { Age: >= 18, Name: "张三" }) { }
// 嵌套属性模式
if (order is { Customer.VipLevel: >= 3, Amount: > 1000m }) { }
// 模式组合
if (n is > 0 and < 100 and not 50) { }
if (c is 'a' or 'e' or 'i' or 'o' or 'u') { }
// 列表模式(C# 11)
if (arr is [1, 2, 3]) { } // 精确匹配
if (arr is [1, .., 3]) { } // 首尾匹配
if (arr is [var first, .., var last]) { } // 提取首尾
// 对模式匹配使用 when 守卫
var category = shape switch
{
Circle { Radius: > 10 } c => "大圆",
Circle c => "小圆",
Rectangle { Width: var w, Height: var h } when w == h => "正方形",
Rectangle r => "矩形",
_ => "未知"
};
7.2 函数式风格
// 不可变数据管道
var result = products
.Where(p => p.Price > 100)
.GroupBy(p => p.Category)
.Select(g => new
{
Category = g.Key,
Total = g.Sum(p => p.Price),
Items = g.Select(p => p.Name).ToList()
})
.OrderByDescending(x => x.Total)
.ToList();
// 使用 record 保证不可变性
public record ProcessedData(
string Category,
decimal Total,
IReadOnlyList<string> Items
);
// 管道式处理
var pipeline = products
.Filter(p => p.Price > 100)
.GroupBy(p => p.Category)
.Map(g => new ProcessedData(
g.Key,
g.Sum(p => p.Price),
g.Select(p => p.Name).ToList()
))
.SortByDescending(x => x.Total);