文章目录
引言
LINQ(Language Integrated Query,语言集成查询)是.NET平台上一种强大的查询语言技术,它允许开发者使用统一的语法对不同类型的数据源进行查询操作。无论是操作内存中的对象集合、数据库中的表,还是XML文档,开发者都可以使用相同的语法结构。
本文将详细介绍LINQ的基础语法,包括两种主要的语法风格、查询的基本结构、常用关键字以及类型推断机制,帮助你掌握LINQ的基本用法。
1. 查询语法 vs 方法语法
LINQ提供了两种编写查询的语法风格:查询语法和方法语法。两者在功能上完全等价,最终都会被编译器转换为对扩展方法的调用,区别主要在于代码的表达方式和可读性。
1.1 查询语法 (Query Syntax)
查询语法类似于SQL语句,使用关键字如from
、where
、select
等组织查询逻辑。这种语法对于有SQL经验的开发人员来说更加直观。
// 查询语法示例:查找所有长度大于5的字符串
// 首先定义一个字符串数组作为数据源
string[] fruits = { "apple", "banana", "orange", "grape", "watermelon", "kiwi" };
// 使用查询语法查找长度大于5的水果名称
var longFruits = from fruit in fruits
where fruit.Length > 5
orderby fruit
select fruit;
// 遍历并输出结果
foreach (var fruit in longFruits)
{
Console.WriteLine(fruit);
}
// 输出:
// banana
// orange
// watermelon
1.2 方法语法 (Method Syntax)
方法语法使用扩展方法和Lambda表达式,更加紧凑,有时能提供查询语法不支持的功能。对于有函数式编程经验的开发人员来说,这种语法可能更加自然。
// 方法语法示例:实现与上面相同的功能
string[] fruits = { "apple", "banana", "orange", "grape", "watermelon", "kiwi" };
// 使用方法语法查找长度大于5的水果名称
var longFruits = fruits
.Where(fruit => fruit.Length > 5) // 过滤条件
.OrderBy(fruit => fruit) // 排序
.Select(fruit => fruit); // 投影(这里没有改变,但可以在此处进行转换)
// 遍历并输出结果
foreach (var fruit in longFruits)
{
Console.WriteLine(fruit);
}
// 输出与查询语法相同
1.3 两种语法的比较
特性 | 查询语法 | 方法语法 |
---|---|---|
语法风格 | 类似SQL,声明式 | 链式方法调用 |
可读性 | 对于复杂查询更好 | 对于简单查询更好 |
功能覆盖 | 不支持部分高级操作 | 支持全部LINQ操作 |
适用场景 | 复杂的查询逻辑 | 简单查询或需要特殊操作 |
学习曲线 | 熟悉SQL的人更易上手 | 熟悉函数式编程的人更易上手 |
2. 基本的 LINQ 查询结构
LINQ查询通常包含以下几个主要部分:
2.1 数据源
每个LINQ查询都必须有一个数据源,它可以是数组、列表、字典或其他任何实现了IEnumerable<T>
接口的集合。
// 数据源示例
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 列表
int[] numbersArray = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 数组
Dictionary<string, int> ages = new Dictionary<string, int> // 字典
{
{ "Alice", 25 },
{ "Bob", 30 },
{ "Charlie", 35 }
};
2.2 查询操作
查询操作定义了我们要对数据源执行的操作,如筛选、排序、分组等。
// 基本查询结构(查询语法)
var query = from element in dataSource // 数据源和范围变量
where condition // 过滤条件(可选)
orderby property // 排序条件(可选)
group element by key // 分组条件(可选)
select result; // 结果选择
2.3 查询执行
LINQ查询通常采用延迟执行模式,这意味着查询定义和查询执行是分开的。查询只有在被枚举(如使用foreach循环)或调用强制执行方法(如ToList())时才会执行。
// 定义查询(此时不会执行)
var evenNumbers = from n in numbers
where n % 2 == 0
select n;
// 执行查询的方式1:遍历结果
foreach (var num in evenNumbers) // 此时查询被执行
{
Console.WriteLine(num);
}
// 执行查询的方式2:强制立即执行
List<int> evenNumbersList = evenNumbers.ToList(); // 立即执行并将结果存储到列表中
int[] evenNumbersArray = evenNumbers.ToArray(); // 立即执行并将结果存储到数组中
3. 查询表达式中的关键字
查询语法中使用了一系列关键字来构建查询表达式。以下是最常用的关键字及其功能:
3.1 基本关键字
from
定义查询的数据源和范围变量(迭代变量)。
// from关键字示例
// 创建学生类
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
// 创建学生列表
List<Student> students = new List<Student>
{
new Student { Id = 1, Name = "张三", Age = 18 },
new Student { Id = 2, Name = "李四", Age = 20 },
new Student { Id = 3, Name = "王五", Age = 19 }
};
// 使用from定义数据源和范围变量
var query = from student in students // student是范围变量,students是数据源
select student.Name;
where
指定过滤条件,类似SQL中的WHERE子句。
// where关键字示例
// 查询年龄大于18的学生
var adultStudents = from student in students
where student.Age > 18 // 使用where指定过滤条件
select student;
// 使用多个条件
var filteredStudents = from student in students
where student.Age > 18 && student.Name.StartsWith("李") // 组合条件
select student;
select
将查询结果投影为指定的形式,可以是原始对象、新对象或单个属性。
// select关键字示例
// 选择学生姓名
var studentNames = from student in students
select student.Name; // 选择单个属性
// 创建匿名类型
var studentInfo = from student in students
select new { student.Name, student.Age }; // 创建包含部分属性的匿名类型
// 创建新类型
var studentViewModels = from student in students
select new StudentViewModel // 创建新的具名类型
{
FullName = student.Name,
YearsOld = student.Age
};
orderby
指定结果的排序方式,支持升序(默认)和降序,也支持多级排序。
// orderby关键字示例
// 按年龄升序排序
var sortedByAge = from student in students
orderby student.Age // 默认为升序
select student;
// 按年龄降序排序
var sortedByAgeDesc = from student in students
orderby student.Age descending // 使用descending指定降序
select student;
// 多级排序:先按年龄降序,再按姓名升序
var complexSort = from student in students
orderby student.Age descending, student.Name // 多级排序
select student;
group by
根据指定的键对结果进行分组。
// group by关键字示例
// 按年龄分组
var groupedByAge = from student in students
group student by student.Age; // 生成IGrouping<int, Student>集合
// 遍历分组结果
foreach (var group in groupedByAge)
{
Console.WriteLine($"年龄: {group.Key}"); // 使用Key属性访问分组键
foreach (var student in group) // 遍历组内元素
{
Console.WriteLine($" - {student.Name}");
}
}
// 创建更复杂的分组结果
var customGroupResult = from student in students
group student by student.Age into ageGroup // 使用into子句创建临时变量
select new
{
Age = ageGroup.Key,
Count = ageGroup.Count(),
Students = ageGroup.ToList()
};
join
实现两个数据源之间的连接,类似SQL中的JOIN操作。
// join关键字示例
// 创建课程类
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public int StudentId { get; set; } // 外键,关联到学生
}
// 创建课程列表
List<Course> courses = new List<Course>
{
new Course { Id = 1, Name = "数学", StudentId = 1 },
new Course { Id = 2, Name = "语文", StudentId = 1 },
new Course { Id = 3, Name = "英语", StudentId = 2 },
new Course { Id = 4, Name = "物理", StudentId = 3 },
new Course { Id = 5, Name = "化学", StudentId = 3 }
};
// 使用join连接学生和课程
var studentCourses = from student in students
join course in courses
on student.Id equals course.StudentId // 指定连接条件
select new
{
StudentName = student.Name,
CourseName = course.Name
};
// 输出连接结果
foreach (var item in studentCourses)
{
Console.WriteLine($"学生: {item.StudentName}, 课程: {item.CourseName}");
}
3.2 其他常用关键字
- into: 用于创建查询的临时结果,通常与group by或join一起使用。
- let: 在查询表达式中引入临时变量。
// let关键字示例
// 计算学生姓名长度并使用
var nameAnalysis = from student in students
let nameLength = student.Name.Length // 定义临时变量
where nameLength > 1 // 使用临时变量
select new
{
student.Name,
NameLength = nameLength
};
4. LINQ 中的 var 关键字与类型推断
4.1 var关键字的使用
var
关键字允许编译器自动推断变量的类型,无需显式指定。在LINQ中,var
特别有用,因为查询结果的类型有时很复杂或难以预先确定。
// var关键字示例
// 不使用var(显式类型)
IEnumerable<string> names1 = from student in students
select student.Name;
// 使用var(隐式类型推断)
var names2 = from student in students
select student.Name; // 编译器推断为IEnumerable<string>
// 当结果是匿名类型时,必须使用var
var studentSummary = from student in students
select new { student.Name, NameLength = student.Name.Length };
// 无法使用显式类型,因为匿名类型没有名称
4.2 类型推断的工作原理
编译器根据右侧表达式推断变量的类型。在LINQ查询中,推断的类型取决于查询的结构和select子句的结果。
// 类型推断示例
// 1. 基本查询 - 推断为IEnumerable<T>,其中T是元素类型
var allStudents = from s in students select s; // IEnumerable<Student>
// 2. 选择单个属性 - 推断为该属性类型的IEnumerable
var ages = from s in students select s.Age; // IEnumerable<int>
// 3. 创建匿名类型 - 推断为匿名类型的IEnumerable
var studentInfo = from s in students
select new { s.Name, s.Age }; // IEnumerable<匿名类型>
// 4. 分组查询 - 推断为IEnumerable<IGrouping<K, T>>
var groupedStudents = from s in students
group s by s.Age; // IEnumerable<IGrouping<int, Student>>
4.3 类型推断的优缺点
优点:
- 代码更简洁,减少冗余
- 处理复杂的查询结果类型
- 处理匿名类型(没有显式类型名)
- 适应类型变化,减少修改
缺点:
- 可能降低代码可读性,尤其对于不熟悉的开发者
- 更依赖IDE的类型提示功能
5. 实际应用示例
下面通过一个综合示例,展示LINQ基础语法的实际应用:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqBasicDemo
{
// 产品类
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 创建产品列表
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "笔记本电脑", Category = "电子产品", Price = 5999, Stock = 10 },
new Product { Id = 2, Name = "手机", Category = "电子产品", Price = 2999, Stock = 20 },
new Product { Id = 3, Name = "耳机", Category = "配件", Price = 199, Stock = 100 },
new Product { Id = 4, Name = "键盘", Category = "配件", Price = 299, Stock = 50 },
new Product { Id = 5, Name = "鼠标", Category = "配件", Price = 99, Stock = 80 },
new Product { Id = 6, Name = "显示器", Category = "电子产品", Price = 1299, Stock = 15 },
new Product { Id = 7, Name = "平板电脑", Category = "电子产品", Price = 3599, Stock = 8 },
new Product { Id = 8, Name = "充电器", Category = "配件", Price = 59, Stock = 200 }
};
Console.WriteLine("===== 使用查询语法 =====");
// 1. 基本查询:价格超过1000元的产品
Console.WriteLine("\n1. 价格超过1000元的产品:");
var expensiveProducts = from p in products
where p.Price > 1000
orderby p.Price descending
select p;
foreach (var product in expensiveProducts)
{
Console.WriteLine($"{product.Name} - ¥{product.Price}");
}
// 2. 投影查询:选择部分属性
Console.WriteLine("\n2. 产品名称和价格列表:");
var productInfos = from p in products
select new { p.Name, p.Price };
foreach (var info in productInfos)
{
Console.WriteLine($"{info.Name} - ¥{info.Price}");
}
// 3. 分组查询:按类别分组
Console.WriteLine("\n3. 按类别分组的产品:");
var groupedProducts = from p in products
group p by p.Category into categoryGroup
select new
{
Category = categoryGroup.Key,
Count = categoryGroup.Count(),
Products = categoryGroup.ToList()
};
foreach (var group in groupedProducts)
{
Console.WriteLine($"类别: {group.Category} (共{group.Count}个产品)");
foreach (var product in group.Products)
{
Console.WriteLine($" - {product.Name} - ¥{product.Price}");
}
}
Console.WriteLine("\n===== 使用方法语法 =====");
// 4. 过滤和排序:查找库存少于20的产品并按库存升序排列
Console.WriteLine("\n4. 库存少于20的产品(库存升序):");
var lowStockProducts = products
.Where(p => p.Stock < 20)
.OrderBy(p => p.Stock)
.Select(p => p);
foreach (var product in lowStockProducts)
{
Console.WriteLine($"{product.Name} - 库存: {product.Stock}");
}
// 5. 复杂查询:查找价格区间内的配件类产品,计算均价
Console.WriteLine("\n5. 价格在50-300元的配件类产品:");
var accessories = products
.Where(p => p.Category == "配件" && p.Price >= 50 && p.Price <= 300)
.ToList();
decimal averagePrice = accessories.Average(p => p.Price);
foreach (var accessory in accessories)
{
Console.WriteLine($"{accessory.Name} - ¥{accessory.Price}");
}
Console.WriteLine($"平均价格: ¥{averagePrice}");
// 6. 组合查询:查找电子产品中最贵和最便宜的产品
Console.WriteLine("\n6. 电子产品价格范围:");
var electronicProducts = products.Where(p => p.Category == "电子产品");
var mostExpensive = electronicProducts.OrderByDescending(p => p.Price).First();
var cheapest = electronicProducts.OrderBy(p => p.Price).First();
Console.WriteLine($"最贵产品: {mostExpensive.Name} - ¥{mostExpensive.Price}");
Console.WriteLine($"最便宜产品: {cheapest.Name} - ¥{cheapest.Price}");
}
}
}
总结
LINQ的基础语法提供了一种强大而灵活的方式来处理各种数据源中的数据。通过掌握查询语法和方法语法,了解基本的查询结构和关键字,以及利用类型推断,你可以编写出简洁、可读性强的代码来实现复杂的数据操作。
LINQ的优势在于:
- 统一的查询语法:对不同数据源使用相同的查询结构
- 强类型和IDE支持:编译时类型检查和智能提示
- 可读性和表达力:声明式风格使代码意图更清晰
- 延迟执行:优化性能和资源使用
- 可组合性:查询可以轻松组合和重用
随着你对LINQ基础语法的熟悉,你将能够更有效地处理数据,编写出更简洁、更易维护的代码。
进一步学习资源
-
LINQPad工具 - 学习和测试LINQ查询的实用工具