C# LINQ详解
LINQ(Language Integrated Query)是C#中一项革命性的技术,它将查询功能直接集成到C#语言中,使开发者能够以声明式的方式查询各种数据源。本文将全面深入地介绍C#中LINQ的各种特性、用法和最佳实践。
一、LINQ概述
1.1 LINQ是什么?
LINQ(Language Integrated Query)是.NET Framework 3.5引入的一项技术,它允许开发者使用类似SQL的语法来查询各种数据源,包括:
- 内存中的集合(如数组、列表)
- 数据库(SQL Server、Oracle等)
- XML文档
- Web服务
- 其他实现了IEnumerable或IQueryable接口的数据源
1.2 LINQ的核心组件
LINQ主要由以下几个部分组成:
- LINQ to Objects:查询内存中的集合
- LINQ to SQL:查询SQL Server数据库(已逐渐被Entity Framework取代)
- LINQ to Entities:通过Entity Framework查询各种数据库
- LINQ to XML:查询和操作XML文档
- LINQ to DataSet:查询DataSet对象
- 并行LINQ(PLINQ):提供并行查询功能
二、LINQ基础语法
2.1 查询表达式语法(Query Syntax)
查询表达式语法类似于SQL,使用关键字如from
、where
、select
等:
var query = from p in products
where p.Price > 100
orderby p.Name
select p;
2.2 方法语法(Method Syntax)
方法语法使用扩展方法,如Where()
、Select()
等:
var query = products.Where(p => p.Price > 100)
.OrderBy(p => p.Name)
.Select(p => p);
2.3 混合使用
查询表达式和方法语法可以混合使用:
var query = from p in products
where p.Price > 100
orderby p.Name
select p.Name.ToUpper(); // 这里使用了方法语法
三、LINQ操作符详解
3.1 筛选操作符
操作符 | 描述 | 示例 |
---|---|---|
Where | 筛选满足条件的元素 | products.Where(p => p.Price > 100) |
OfType | 筛选指定类型的元素 | collection.OfType<string>() |
3.2 投影操作符
操作符 | 描述 | 示例 |
---|---|---|
Select | 投影新序列 | products.Select(p => p.Name) |
SelectMany | 将嵌套集合展平 | customers.SelectMany(c => c.Orders) |
3.3 分区操作符
操作符 | 描述 | 示例 |
---|---|---|
Take | 返回前N个元素 | products.Take(5) |
TakeWhile | 返回满足条件的元素,直到条件不满足 | products.TakeWhile(p => p.Price < 100) |
Skip | 跳过前N个元素 | products.Skip(5) |
SkipWhile | 跳过满足条件的元素,直到条件不满足 | products.SkipWhile(p => p.Price < 100) |
3.4 排序操作符
操作符 | 描述 | 示例 |
---|---|---|
OrderBy | 升序排序 | products.OrderBy(p => p.Price) |
OrderByDescending | 降序排序 | products.OrderByDescending(p => p.Price) |
ThenBy | 次级排序(升序) | products.OrderBy(p => p.Category).ThenBy(p => p.Price) |
ThenByDescending | 次级排序(降序) | products.OrderBy(p => p.Category).ThenByDescending(p => p.Price) |
3.5 分组操作符
操作符 | 描述 | 示例 |
---|---|---|
GroupBy | 按键分组 | products.GroupBy(p => p.Category) |
ToLookup | 创建查找表 | products.ToLookup(p => p.Category) |
3.6 聚合操作符
操作符 | 描述 | 示例 |
---|---|---|
Count | 计数 | products.Count() |
LongCount | 长整型计数 | products.LongCount() |
Sum | 求和 | products.Sum(p => p.Price) |
Min | 最小值 | products.Min(p => p.Price) |
Max | 最大值 | products.Max(p => p.Price) |
Average | 平均值 | products.Average(p => p.Price) |
Aggregate | 自定义聚合 | products.Aggregate((acc, p) => acc + p.Price) |
3.7 集合操作符
操作符 | 描述 | 示例 |
---|---|---|
Distinct | 去重 | products.Select(p => p.Category).Distinct() |
Union | 并集 | list1.Union(list2) |
Intersect | 交集 | list1.Intersect(list2) |
Except | 差集 | list1.Except(list2) |
Concat | 连接 | list1.Concat(list2) |
3.8 生成操作符
操作符 | 描述 | 示例 |
---|---|---|
Range | 生成数字序列 | Enumerable.Range(1, 10) |
Repeat | 重复元素 | Enumerable.Repeat("A", 5) |
Empty | 空序列 | Enumerable.Empty<int>() |
DefaultIfEmpty | 默认空值 | products.DefaultIfEmpty(new Product()) |
3.9 元素操作符
操作符 | 描述 | 示例 |
---|---|---|
First | 第一个元素 | products.First() |
FirstOrDefault | 第一个元素或默认值 | products.FirstOrDefault() |
Last | 最后一个元素 | products.Last() |
LastOrDefault | 最后一个元素或默认值 | products.LastOrDefault() |
Single | 唯一元素 | products.Single(p => p.Id == 1) |
SingleOrDefault | 唯一元素或默认值 | products.SingleOrDefault(p => p.Id == 1) |
ElementAt | 指定索引元素 | products.ElementAt(2) |
ElementAtOrDefault | 指定索引元素或默认值 | products.ElementAtOrDefault(2) |
3.10 转换操作符
操作符 | 描述 | 示例 |
---|---|---|
ToList | 转换为List | products.ToList() |
ToArray | 转换为数组 | products.ToArray() |
ToDictionary | 转换为字典 | products.ToDictionary(p => p.Id) |
ToLookup | 转换为查找表 | products.ToLookup(p => p.Category) |
四、LINQ查询执行机制
4.1 延迟执行
大多数LINQ操作都是延迟执行的,只有在实际枚举结果时才会执行查询:
var query = products.Where(p => p.Price > 100); // 查询未执行
foreach (var p in query) // 查询在此处执行
{
Console.WriteLine(p.Name);
}
4.2 立即执行
某些操作会立即执行并缓存结果:
var list = products.Where(p => p.Price > 100).ToList(); // 立即执行并缓存
五、LINQ to Objects详解
LINQ to Objects用于查询内存中的集合,是最常用的LINQ形式。
5.1 基本查询
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 查询偶数
var evenNumbers = numbers.Where(n => n % 2 == 0);
// 查询大于5的数并按降序排序
var largeNumbers = numbers.Where(n => n > 5)
.OrderByDescending(n => n);
// 查询前3个大于5的数
var top3LargeNumbers = numbers.Where(n => n > 5)
.OrderByDescending(n => n)
.Take(3);
5.2 复杂查询
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Price = 1200, Category = "Electronics" },
new Product { Id = 2, Name = "Phone", Price = 800, Category = "Electronics" },
new Product { Id = 3, Name = "Desk", Price = 200, Category = "Furniture" },
new Product { Id = 4, Name = "Chair", Price = 100, Category = "Furniture" },
new Product { Id = 5, Name = "Tablet", Price = 500, Category = "Electronics" }
};
// 查询每个类别中最贵的产品
var mostExpensiveInEachCategory = products
.GroupBy(p => p.Category)
.Select(g => g.OrderByDescending(p => p.Price).First());
// 查询价格在200到800之间的产品,并按价格排序
var midRangeProducts = products
.Where(p => p.Price >= 200 && p.Price <= 800)
.OrderBy(p => p.Price);
// 查询每个类别的产品数量
var categoryCounts = products
.GroupBy(p => p.Category)
.Select(g => new { Category = g.Key, Count = g.Count() });
六、LINQ to XML详解
LINQ to XML提供了强大的XML处理能力。
6.1 创建XML文档
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("Books",
new XElement("Book",
new XAttribute("Id", 1),
new XElement("Title", "C# in Depth"),
new XElement("Author", "Jon Skeet")
),
new XElement("Book",
new XAttribute("Id", 2),
new XElement("Title", "Clean Code"),
new XElement("Author", "Robert C. Martin")
)
)
);
doc.Save("books.xml");
6.2 查询XML文档
XDocument doc = XDocument.Load("books.xml");
// 查询所有书籍
var books = doc.Descendants("Book");
// 查询特定作者的书籍
var martinBooks = doc.Descendants("Book")
.Where(b => (string)b.Element("Author") == "Robert C. Martin");
// 查询书籍ID为1的标题
var bookTitle = doc.Descendants("Book")
.Where(b => (int)b.Attribute("Id") == 1)
.Select(b => (string)b.Element("Title"))
.FirstOrDefault();
// 创建新书籍并添加到文档
var newBook = new XElement("Book",
new XAttribute("Id", 3),
new XElement("Title", "Effective C#"),
new XElement("Author", "Bill Wagner")
);
doc.Root.Add(newBook);
doc.Save("books.xml");
七、LINQ to SQL/Entities详解
虽然LINQ to SQL已逐渐被Entity Framework取代,但了解其基本用法仍然有用。
7.1 LINQ to SQL基本用法
// 定义实体类
[Table(Name = "Customers")]
public class Customer
{
[Column(IsPrimaryKey = true)]
public int CustomerID { get; set; }
[Column]
public string Name { get; set; }
}
// 创建DataContext
DataContext db = new DataContext("Data Source=myServer;Initial Catalog=myDB;Integrated Security=SSPI");
// 查询客户
var customers = from c in db.GetTable<Customer>()
where c.Name.StartsWith("A")
select c;
foreach (var c in customers)
{
Console.WriteLine(c.Name);
}
7.2 Entity Framework基本用法
// 定义DbContext
public class MyDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
}
// 查询产品
using (var db = new MyDbContext())
{
var expensiveProducts = from p in db.Products
where p.Price > 100
orderby p.Name
select p;
foreach (var p in expensiveProducts)
{
Console.WriteLine($"{p.Name}: {p.Price}");
}
}
八、并行LINQ(PLINQ)详解
PLINQ是LINQ的并行版本,可以自动利用多核处理器提高查询性能。
8.1 基本用法
List<int> numbers = Enumerable.Range(1, 1000000).ToList();
// 并行查询偶数
var evenNumbers = numbers.AsParallel()
.Where(n => n % 2 == 0)
.ToList();
// 并行查询并排序
var sortedNumbers = numbers.AsParallel()
.AsOrdered() // 保持原始顺序
.Where(n => n > 500000)
.OrderBy(n => n)
.Take(10)
.ToList();
8.2 性能考虑
// 并行度设置
var result = numbers.AsParallel()
.WithDegreeOfParallelism(4) // 设置并行度为4
.Where(n => n % 2 == 0)
.ToList();
// 取消并行查询
var cts = new CancellationTokenSource();
var query = numbers.AsParallel()
.WithCancellation(cts.Token)
.Where(n => n % 2 == 0);
// 如果需要取消
cts.Cancel();
try
{
var result = query.ToList();
}
catch (OperationCanceledException)
{
Console.WriteLine("查询已取消");
}
九、LINQ最佳实践
9.1 查询性能优化
- 延迟执行:利用LINQ的延迟执行特性,只在需要时执行查询
- 避免多次枚举:将结果转换为列表或数组,如果需要多次使用
- 合理使用并行:只在数据量大且计算密集时使用PLINQ
- 选择合适的方法:对于简单查询使用方法语法,复杂查询使用查询表达式语法
9.2 代码可读性
- 适当使用换行:使复杂查询更易读
- 使用有意义的变量名:如
expensiveProducts
而不是q
- 分解复杂查询:将复杂查询拆分为多个简单查询
- 注释复杂逻辑:解释复杂的查询条件
9.3 错误处理
- 处理空集合:使用
DefaultIfEmpty
或检查Any()
- 处理异常:在查询外部捕获可能的异常
- 验证输入:确保查询的数据源有效
十、LINQ实战示例
10.1 数据分析
// 销售数据
List<Sale> sales = new List<Sale>
{
new Sale { Product = "Laptop", Amount = 1200, Date = new DateTime(2023, 1, 15) },
new Sale { Product = "Phone", Amount = 800, Date = new DateTime(2023, 1, 20) },
new Sale { Product = "Laptop", Amount = 1100, Date = new DateTime(2023, 2, 5) },
new Sale { Product = "Tablet", Amount = 500, Date = new DateTime(2023, 2, 10) },
new Sale { Product = "Phone", Amount = 850, Date = new DateTime(2023, 3, 1) }
};
// 按月统计销售额
var monthlySales = sales.GroupBy(s => new { s.Date.Year, s.Date.Month })
.Select(g => new
{
Year = g.Key.Year,
Month = g.Key.Month,
Total = g.Sum(s => s.Amount)
})
.OrderBy(m => m.Year)
.ThenBy(m => m.Month);
foreach (var m in monthlySales)
{
Console.WriteLine($"{m.Year}-{m.Month:D2}: {m.Total:C}");
}
// 找出最畅销的产品
var bestSellingProduct = sales
.GroupBy(s => s.Product)
.Select(g => new { Product = g.Key, Total = g.Sum(s => s.Amount) })
.OrderByDescending(g => g.Total)
.First();
Console.WriteLine($"Best selling product: {bestSellingProduct.Product} (${bestSellingProduct.Total})");
10.2 XML处理
// 创建XML文档
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("Configuration",
new XElement("AppSettings",
new XElement("Add", new XAttribute("Key", "ConnectionString"), new XAttribute("Value", "Server=myServer;Database=myDB;")),
new XElement("Add", new XAttribute("Key", "Timeout"), new XAttribute("Value", "30"))
),
new XElement("ConnectionStrings",
new XElement("add", new XAttribute("name", "Default"), new XAttribute("connectionString", "Server=myServer;Database=myDB;"))
)
)
);
// 查询特定设置
var connectionString = doc.Descendants("Add")
.FirstOrDefault(e => (string)e.Attribute("Key") == "ConnectionString");
if (connectionString != null)
{
Console.WriteLine($"Connection String: {connectionString.Attribute("Value").Value}");
}
// 添加新设置
doc.Descendants("AppSettings").First().Add(
new XElement("Add",
new XAttribute("Key", "MaxConnections"),
new XAttribute("Value", "100")));
doc.Save("config.xml");
10.3 数据库查询
// 使用Entity Framework查询
using (var db = new MyDbContext())
{
// 查询所有产品
var allProducts = db.Products.ToList();
// 查询价格大于100的产品并按名称排序
var expensiveProducts = from p in db.Products
where p.Price > 100
orderby p.Name
select p;
// 分组统计
var categoryStats = from p in db.Products
group p by p.Category into g
select new
{
Category = g.Key,
Count = g.Count(),
AveragePrice = g.Average(p => p.Price)
};
// 分页查询
var pagedProducts = db.Products
.OrderBy(p => p.Name)
.Skip(20)
.Take(10)
.ToList();
}
LINQ是C#中强大而灵活的查询技术,它统一了各种数据源的查询方式,使代码更加简洁、易读和可维护。通过本文的介绍,我们学习了:
- LINQ的基本语法和操作符
- LINQ to Objects的核心功能
- LINQ to XML的文档处理能力
- LINQ to SQL/Entities的数据库查询
- PLINQ的并行查询技术
- LINQ的最佳实践和性能优化