名称:csharp-linq 用户可调用:false 描述:在使用LINQ(语言集成查询)时,包括查询和方法语法、延迟执行、表达式树和性能优化。 允许工具:
- 读取
- 写入
- 编辑
- 搜索
- 全局
- Bash
C# LINQ
LINQ(语言集成查询)提供了跨不同数据源(包括集合、数据库、XML等)的一致查询体验。它结合了类似SQL查询的强大功能与C#类型安全和智能感知支持,实现了表达性强且可维护的数据操作代码。
查询语法
查询语法提供类似SQL的语法用于查询数据源,在编译时转换为方法调用。
using System;
using System.Collections.Generic;
using System.Linq;
public class QuerySyntaxExamples
{
public record Person(string Name, int Age, string City);
// 基本查询
public IEnumerable<Person> BasicQuery(List<Person> people)
{
var query = from p in people
where p.Age >= 18
select p;
return query;
}
// 带多个条件的查询
public IEnumerable<Person> MultipleConditions(List<Person> people)
{
var query = from p in people
where p.Age >= 18 && p.City == "Seattle"
orderby p.Name
select p;
return query;
}
// 使用select进行投影
public IEnumerable<string> ProjectNames(List<Person> people)
{
var query = from p in people
where p.Age >= 21
select p.Name;
return query;
}
// 匿名类型
public IEnumerable<object> AnonymousProjection(List<Person> people)
{
var query = from p in people
select new
{
p.Name,
p.Age,
IsAdult = p.Age >= 18
};
return query;
}
// 分组
public IEnumerable<IGrouping<string, Person>> GroupByCity(
List<Person> people)
{
var query = from p in people
group p by p.City;
return query;
}
// 带投影的分组
public IEnumerable<object> GroupWithProjection(List<Person> people)
{
var query = from p in people
group p by p.City into cityGroup
select new
{
City = cityGroup.Key,
Count = cityGroup.Count(),
AverageAge = cityGroup.Average(p => p.Age)
};
return query;
}
// 连接
public record Order(int Id, string PersonName, decimal Amount);
public IEnumerable<object> JoinExample(
List<Person> people,
List<Order> orders)
{
var query = from p in people
join o in orders on p.Name equals o.PersonName
select new
{
p.Name,
p.Age,
OrderAmount = o.Amount
};
return query;
}
// 左连接
public IEnumerable<object> LeftJoin(
List<Person> people,
List<Order> orders)
{
var query = from p in people
join o in orders on p.Name equals o.PersonName
into personOrders
from po in personOrders.DefaultIfEmpty()
select new
{
p.Name,
OrderAmount = po?.Amount ?? 0
};
return query;
}
}
方法语法
方法语法使用扩展方法进行查询,提供更多灵活性和访问所有LINQ操作符。
using System;
using System.Collections.Generic;
using System.Linq;
public class MethodSyntaxExamples
{
public record Product(string Name, decimal Price, string Category);
// 过滤
public IEnumerable<Product> FilterProducts(List<Product> products)
{
return products
.Where(p => p.Price > 100)
.Where(p => p.Category == "Electronics");
}
// 排序
public IEnumerable<Product> OrderProducts(List<Product> products)
{
return products
.OrderBy(p => p.Category)
.ThenByDescending(p => p.Price);
}
// 投影
public IEnumerable<string> ProjectNames(List<Product> products)
{
return products
.Select(p => p.Name.ToUpper());
}
// SelectMany(扁平化)
public IEnumerable<int> FlattenLists()
{
var lists = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5 },
new List<int> { 6, 7, 8, 9 }
};
return lists.SelectMany(list => list);
}
// 分组
public IEnumerable<IGrouping<string, Product>> GroupByCategory(
List<Product> products)
{
return products.GroupBy(p => p.Category);
}
// 聚合
public void AggregationExamples(List<Product> products)
{
decimal total = products.Sum(p => p.Price);
decimal average = products.Average(p => p.Price);
decimal max = products.Max(p => p.Price);
decimal min = products.Min(p => p.Price);
int count = products.Count();
int expensiveCount = products.Count(p => p.Price > 500);
}
// Any和All
public void ExistenceChecks(List<Product> products)
{
bool hasExpensive = products.Any(p => p.Price > 1000);
bool allAffordable = products.All(p => p.Price < 100);
bool hasElectronics = products.Any(p =>
p.Category == "Electronics");
}
// Take和Skip
public IEnumerable<Product> Pagination(
List<Product> products,
int page,
int pageSize)
{
return products
.OrderBy(p => p.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize);
}
// Distinct
public IEnumerable<string> UniqueCategories(List<Product> products)
{
return products
.Select(p => p.Category)
.Distinct();
}
// 集合操作
public void SetOperations(
List<Product> products1,
List<Product> products2)
{
var union = products1.Union(products2);
var intersect = products1.Intersect(products2);
var except = products1.Except(products2);
}
}
延迟执行
LINQ查询使用延迟执行,意味着查询在枚举时执行,而不是在定义时。
using System;
using System.Collections.Generic;
using System.Linq;
public class DeferredExecutionExamples
{
// 延迟执行演示
public void DeferredExecutionDemo()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 查询已定义但未执行
var query = numbers.Where(n => n > 2);
Console.WriteLine("修改前:");
foreach (var n in query) // 在此处执行
{
Console.WriteLine(n); // 3, 4, 5
}
// 修改源数据
numbers.Add(6);
numbers.Add(7);
Console.WriteLine("修改后:");
foreach (var n in query) // 使用新数据再次执行
{
Console.WriteLine(n); // 3, 4, 5, 6, 7
}
}
// 使用ToList立即执行
public void ImmediateExecution()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 立即执行并缓存结果
var list = numbers.Where(n => n > 2).ToList();
numbers.Add(6);
numbers.Add(7);
// list仍然只包含3, 4, 5
foreach (var n in list)
{
Console.WriteLine(n);
}
}
// 强制立即执行的操作符
public void ImmediateExecutionOperators()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 这些立即执行
var array = numbers.Where(n => n > 2).ToArray();
var dict = numbers.ToDictionary(n => n, n => n * 2);
var hashSet = numbers.ToHashSet();
var lookup = numbers.ToLookup(n => n % 2);
// 聚合操作立即执行
int count = numbers.Count(n => n > 2);
int sum = numbers.Sum();
int max = numbers.Max();
bool any = numbers.Any(n => n > 10);
}
// 多次枚举问题
public void MultipleEnumeration()
{
var numbers = GetNumbers(); // IEnumerable
// 差:枚举两次
int count = numbers.Count();
int sum = numbers.Sum();
// 好:枚举一次,缓存
var list = numbers.ToList();
count = list.Count;
sum = list.Sum();
}
private IEnumerable<int> GetNumbers()
{
Console.WriteLine("生成数字...");
for (int i = 1; i <= 5; i++)
{
yield return i;
}
}
}
复杂查询
组合多个LINQ操作进行复杂数据转换。
using System;
using System.Collections.Generic;
using System.Linq;
public class ComplexQueries
{
public record Student(string Name, int Grade, string Subject,
int Score);
public record Course(string Subject, string Teacher, int Credits);
// 复杂过滤和投影
public IEnumerable<object> StudentReport(
List<Student> students,
List<Course> courses)
{
return students
.Where(s => s.Grade >= 10)
.GroupBy(s => new { s.Name, s.Grade })
.Select(g => new
{
g.Key.Name,
g.Key.Grade,
Subjects = g.Select(s => s.Subject).Distinct(),
AverageScore = g.Average(s => s.Score),
TotalCredits = g.Join(
courses,
s => s.Subject,
c => c.Subject,
(s, c) => c.Credits
).Sum()
})
.OrderByDescending(s => s.AverageScore);
}
// 嵌套查询
public IEnumerable<object> TopStudentsBySubject(
List<Student> students)
{
return students
.GroupBy(s => s.Subject)
.Select(g => new
{
Subject = g.Key,
TopStudent = g
.OrderByDescending(s => s.Score)
.Select(s => new { s.Name, s.Score })
.FirstOrDefault(),
ClassAverage = g.Average(s => s.Score)
});
}
// 窗口函数
public IEnumerable<object> RunningTotal(List<Student> students)
{
return students
.OrderBy(s => s.Name)
.ThenBy(s => s.Subject)
.Select((s, index) => new
{
s.Name,
s.Subject,
s.Score,
RunningTotal = students
.Take(index + 1)
.Sum(x => x.Score)
});
}
// 层次数据
public record Category(string Name, string Parent);
public IEnumerable<object> BuildHierarchy(List<Category> categories)
{
return categories
.Where(c => c.Parent == null)
.Select(parent => new
{
parent.Name,
Children = categories
.Where(c => c.Parent == parent.Name)
.Select(child => new
{
child.Name,
Grandchildren = categories
.Where(gc => gc.Parent == child.Name)
})
});
}
}
性能优化
理解LINQ性能特征和优化技术。
using System;
using System.Collections.Generic;
using System.Linq;
public class PerformanceOptimization
{
// 使用Count属性代替Count()方法
public void CountOptimization(List<int> numbers)
{
// 慢:迭代整个集合
int count1 = numbers.Where(n => n > 0).Count();
// 快:如果可用,使用Count属性
int count2 = numbers.Count;
// 使用Any()代替Count()进行存在检查
bool hasItems = numbers.Any(); // 快
bool hasItems2 = numbers.Count() > 0; // 慢
}
// 避免多次枚举
public void AvoidMultipleEnumerations()
{
var query = GetExpensiveQuery();
// 差:多次枚举
int count = query.Count();
int sum = query.Sum();
var first = query.First();
// 好:枚举一次
var list = query.ToList();
count = list.Count;
sum = list.Sum();
first = list.First();
}
// 在Select之前使用Where
public IEnumerable<string> FilterBeforeProject(List<int> numbers)
{
// 好:先过滤(减少投影项)
return numbers
.Where(n => n > 100)
.Select(n => n.ToString());
// 差:先投影所有,再过滤
// return numbers
// .Select(n => n.ToString())
// .Where(s => int.Parse(s) > 100);
}
// 使用FirstOrDefault代替Where().First()
public int? FindFirst(List<int> numbers)
{
// 好:在第一个匹配处停止
return numbers.FirstOrDefault(n => n > 100);
// 差:过滤所有,然后取第一个
// return numbers.Where(n => n > 100).FirstOrDefault();
}
// 避免不必要的排序
public IEnumerable<int> TakeWithoutSort(List<int> numbers)
{
// 如果只需要前N个,考虑部分排序
return numbers
.OrderByDescending(n => n)
.Take(10);
// 对于大型集合更好:使用PriorityQueue或类似
}
// 对CPU密集型操作使用AsParallel
public IEnumerable<int> ParallelQuery(List<int> numbers)
{
return numbers
.AsParallel()
.Where(n => ExpensiveOperation(n))
.Select(n => n * 2);
}
private IEnumerable<int> GetExpensiveQuery()
{
return Enumerable.Range(1, 1000)
.Where(n => n % 2 == 0);
}
private bool ExpensiveOperation(int n)
{
System.Threading.Thread.Sleep(1);
return n > 50;
}
}
LINQ to Objects vs LINQ to SQL
理解内存查询和数据库查询之间的差异。
using System;
using System.Collections.Generic;
using System.Linq;
public class LinqProviders
{
public record Customer(int Id, string Name, string City);
// LINQ to Objects(内存中)
public void LinqToObjects(List<Customer> customers)
{
// 在内存中执行
var query = customers
.Where(c => c.City == "Seattle")
.OrderBy(c => c.Name)
.Select(c => new { c.Name, c.City });
// 可以使用任何C#方法
var withMethods = customers
.Where(c => IsValidCity(c.City))
.ToList();
}
// LINQ to SQL(数据库)
public void LinqToSQL()
{
// 在实际应用中,这将是DbContext
// var query = dbContext.Customers
// .Where(c => c.City == "Seattle") // 翻译为SQL
// .OrderBy(c => c.Name)
// .Select(c => new { c.Name, c.City });
// 不能在SQL查询中使用任意C#方法
// 这会抛出运行时错误:
// .Where(c => IsValidCity(c.City))
// 使用AsEnumerable()切换到LINQ to Objects
// var mixed = dbContext.Customers
// .Where(c => c.City == "Seattle") // SQL
// .AsEnumerable()
// .Where(c => IsValidCity(c.City)); // 内存中
}
private bool IsValidCity(string city)
{
return !string.IsNullOrEmpty(city) && city.Length > 2;
}
}
表达式树
理解用于高级LINQ场景的表达式树。
using System;
using System.Linq.Expressions;
public class ExpressionTreeExamples
{
// 构建表达式树
public void BuildExpressionTree()
{
// 手动表达式树
ParameterExpression param = Expression.Parameter(typeof(int), "x");
BinaryExpression body = Expression.Add(
param,
Expression.Constant(5)
);
Expression<Func<int, int>> expr =
Expression.Lambda<Func<int, int>>(body, param);
// 编译并执行
Func<int, int> func = expr.Compile();
int result = func(10); // 15
}
// 从lambda到表达式
public void LambdaToExpression()
{
// 从lambda表达式树
Expression<Func<int, bool>> expr = x => x > 5;
// 编译为委托
Func<int, bool> func = expr.Compile();
bool result = func(10); // true
}
// 分析表达式
public void AnalyzeExpression()
{
Expression<Func<int, bool>> expr = x => x > 5;
// 获取部分
var lambda = (LambdaExpression)expr;
var body = (BinaryExpression)lambda.Body;
var left = (ParameterExpression)body.Left;
var right = (ConstantExpression)body.Right;
Console.WriteLine($"参数:{left.Name}");
Console.WriteLine($"操作符:{body.NodeType}");
Console.WriteLine($"常量:{right.Value}");
}
// 动态查询构建
public Expression<Func<T, bool>> BuildPredicate<T>(
string propertyName,
object value)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
MemberExpression property = Expression.Property(param,
propertyName);
ConstantExpression constant = Expression.Constant(value);
BinaryExpression equal = Expression.Equal(property, constant);
return Expression.Lambda<Func<T, bool>>(equal, param);
}
}
最佳实践
- 对具有多个操作的复杂查询使用方法语法
- 对看起来更像SQL的查询使用查询语法
- 当需要多次枚举时,调用
ToList()或ToArray() - 使用
Any()代替Count() > 0进行存在检查 - 在使用
Select()投影之前先用Where()过滤 - 查找单个项目时,使用
FirstOrDefault()代替Where().First() - 当不需要转换数据时,避免使用
Select() - 仅对大型数据集的CPU密集型操作使用
AsParallel() - 注意延迟执行以及查询实际执行的时间
- 考虑频繁使用查询的表达式树编译成本
常见陷阱
- 多次枚举
IEnumerable导致性能问题 - 在不理解SQL翻译的情况下对数据库查询使用LINQ
- 过早调用
ToList(),失去延迟执行优势 - 在集合上使用
Count()方法而不是Count属性 - 不释放数据库查询的
IEnumerable,导致连接泄漏 - 在foreach更清晰的地方使用LINQ处理简单循环
- 过度使用
AsParallel()导致开销而非加速 - 在lambda表达式中捕获变量导致意外闭包
- 在需要扁平化时使用
Select()而不是SelectMany() - 不理解复杂查询表达式中的操作符优先级
何时使用LINQ
在以下情况下使用LINQ:
- 统一查询集合、数据库、XML或其他数据源
- 表达性强且可维护的数据转换和过滤操作
- 类型安全查询,具有编译时检查和智能感知
- 用于数据操作的功能式编程模式
- 复杂的分组、连接和聚合操作
- 声明式代码,清晰表达意图
- 与Entity Framework或其他ORM集成
- 从可重用组件组合查询
- 使用PLINQ对大型数据集进行并行处理
- 跨不同数据源的一致API