简介
LINQ(Language Integrated Query),语言集成查询,是一系列直接将查询功能集成到 C# 语言的技术统称。使用LINQ表达式可以对数据集合进行过滤、排序、分组、聚合、串联等操作。
例子:
public class Person
{
public int Id;
public string Name;
public int Age;
public string City;
}
// 示例数据
List<Person> people = new List<Person>
{
new Person { Id = 1, Name = "张三", Age = 25, City = "北京" },
new Person { Id = 2, Name = "李四", Age = 30, City = "上海" },
new Person { Id = 3, Name = "王五", Age = 28, City = "北京" },
new Person { Id = 4, Name = "赵六", Age = 35, City = "广州" },
new Person { Id = 5, Name = "钱七", Age = 22, City = "上海" }
};
// 获取北京的平均年龄
double beijingAvgAge = people
.Where(p => p.City == "北京")
.Average(p => p.Age);
// 按城市分组统计人数
var cityGroups = people
.GroupBy(p => p.City)
.Select(g => new { City = g.Key, Count = g.Count() });
// 查找年龄最大的3个人
var oldestThree = people
.OrderByDescending(p => p.Age)
.Take(3);
查询语法和方法语法
查询语法 (Query Syntax)
1、查询语法以from子句开头,后跟 Range 变量。
2、在from子句之后,可以使用不同的标准查询运算符来过滤,分组和联接集合中的元素。
3、始终以select或group子句结尾。
var query = from p in people
where p.Age > 25
orderby p.Name
select p;
方法语法 (Method Syntax)
var query = people.Where(p => p.Age > 25)
.OrderBy(p => p.Name);
延迟执行和立即执行
延迟执行
LINQ 查询通常是延迟执行的,只有在实际迭代结果时才会执行查询:
var query = people.Where(p => p.Age > 25); // 查询尚未执行
foreach(var person in query) // 此时执行查询
{
Console.WriteLine(person.Name);
}
立即执行
如果需要立即执行查询,可以使用以下方法:
List<Person> resultList = people.Where(p => p.Age > 25).ToList();
Person[] resultArray = people.Where(p => p.Age > 25).ToArray();
Person first = people.First(p => p.City == "北京");
标准查询运算符
LINQ中提供了50多个标准查询运算符,它们提供了不同的功能,例如过滤,排序,分组,聚合,串联等。可以根据标准查询运算符提供的功能对其进行分类,如下表所示:
类别 | 标准查询运算符 |
---|---|
过滤 | Where, OfType |
排序 | OrderBy, OrderByDescending, ThenBy, ThenByDescending, Reverse |
分组 | GroupBy, ToLookup |
联合 | GroupJoin, Join |
投射 | Select, SelectMany |
聚合 | Aggregate, Average, Count, LongCount, Max, Min, Sum |
修饰 | All, Any, Contains |
元素 | ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault |
集合 | Distinct, Except, Intersect, Union |
分区 | Skip, SkipWhile, Take, TakeWhile |
串联 | Concat |
相等 | SequenceEqual |
范围状态 | DefaultEmpty, Empty, Range, Repeat |
转换 | AsEnumerable, AsQueryable, Cast, ToArray, ToDictionary, ToList |
过滤
Where
简单条件筛选
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// 筛选偶数
var evens = numbers.Where(n => n % 2 == 0);
// 结果: 2, 4, 6
// 查询语法等效写法
var evensQuery = from n in numbers
where n % 2 == 0
select n;
多条件组合
var results = products
.Where(p => p.Price > 50)
.Where(p => p.Name.Contains("Pro"))
.Where(p => p.InStock);
// 等同于
var results = products.Where(p => p.Price > 50 &&
p.Name.Contains("Pro") &&
p.InStock);
OfType
用于从集合中筛选出指定类型的元素。
// 创建包含多种类型的集合
ArrayList mixedList = new ArrayList { 1, "苹果", 3.14, true, "香蕉", 100 };
// 提取所有字符串
var fruits = mixedList.OfType<string>();
// 结果: "苹果", "香蕉"
// 提取所有整数
var numbers = mixedList.OfType<int>();
// 结果: 1, 100
//结合LINQ用法
var result = mixedList.OfType<int>().Where(value => value > 5);
处理对象继承关系
class Animal { }
class Dog : Animal { public void Bark() => Console.WriteLine("汪汪"); }
class Cat : Animal { public void Meow() => Console.WriteLine("喵喵"); }
List<Animal> pets = new List<Animal> { new Dog(), new Cat(), new Dog() };
// 提取所有Dog对象并调用特有方法
foreach (var dog in pets.OfType<Dog>())
{
dog.Bark(); // 输出: 汪汪 汪汪
}
排序
运算符 | 描述 |
OrderBy | 升序排序 |
ThenBy | 按升序执行次要排序 |
OrderByDescending | 降序排序 |
ThenByDescending | 按降序执行次要排序 |
Reverse | 用于反转序列中元素的顺序 |
OrderBy
查询语法
var numbers = new List<int> { 5, 2, 8, 1, 9 };
var query = from value in numbers
orderby value
select value;
foreach (var value in query)
{
Console.WriteLine(value);
}
方法语法
var temp = numbers.OrderBy(x =>x).Select(x => x).ToList();
foreach (var value in temp)
{
Console.WriteLine(value);
}
ThenBy
class Animal
{
public int id;
public string name;
public float height;
public float weight;
}
查询语法
var list = new List<Animal>()
{
new Animal(){id = 1,name = "小猫",height = 1.5f,weight = 23},
new Animal(){id = 1,name = "小狗",height = 1.4f,weight = 30},
new Animal(){id = 1,name = "小兔子",height = 1.5f,weight = 16},
new Animal(){id = 1,name = "小仓鼠",height = 1.4f,weight = 45},
new Animal(){id = 1,name = "小乌龟",height = 1.6f,weight = 30}
};
var temp = from animal in list
orderby animal.height, animal.weight
select animal.name;
foreach (var name in temp)
{
Console.WriteLine(name);
}
方法语法
var temp = list.OrderBy(x =>x.height)
.ThenBy(x => x.weight)
.Select(x=>x.name).ToList();
OrderByDescending
查询语法
var numbers = new List<int> { 5, 2, 8, 1, 9 };
var query = from value in numbers
orderby value descending
select value;
方法语法
var temp = numbers.OrderByDescending(x =>x).Select(x => x).ToList();
foreach (var value in temp)
{
Console.WriteLine(value);
}
ThenByDescending
查询语法
var temp = from animal in list
orderby animal.height, animal.weight descending
select animal.name;
方法语法
var temp = list.OrderBy(value=> value.height)
.ThenByDescending(a=>a.weight)
.Select(a=>a.name);
Reverse
用于反转序列中元素的顺序,使用时需要缓冲整个序列才能执行反转。对于于大型集合,可能会消耗较多内存。如果只需要反向迭代而不需要具体反转后的集合,可以考虑使用 for 循环从末尾开始遍历。
//根据奇偶性分组
List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];
var data = numbers.Select(x => x);
var reslult = data.Reverse();
var str = string.Join(",", reslult);
Console.WriteLine(str);
分组
分组是指将数据分到不同的组,使每组中的元素拥有公共的属性。
GroupBy
//根据奇偶性分组
List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];
//查询语法
IEnumerable<IGrouping<int, int>> query = from number in numbers
group number by number % 2;
//方法语法
query = numbers.GroupBy(number => number % 2);
foreach (var group in query)
{
Console.WriteLine(group.Key == 0 ? "\n偶数:" : "\n奇数:");
foreach (int i in group)
{
Console.WriteLine(i);
}
}
ToLookup
用于创建一个基于键的查找表,类似于字典,但允许一个键对应多个值。
从 IEnumerable<T> 生成一个泛型 Lookup<TKey,TElement>。
适合需要频繁按键查找分组数据的场景。
var list = new List<Animal>()
{
new Animal(){id = 1,name = "小猫",height = 1.5f,weight = 23},
new Animal(){id = 1,name = "小狗",height = 1.4f,weight = 30},
new Animal(){id = 1,name = "小兔子",height = 1.5f,weight = 16},
new Animal(){id = 1,name = "小仓鼠",height = 1.4f,weight = 45},
new Animal(){id = 1,name = "小乌龟",height = 1.6f,weight = 30}
};
var result = list.ToLookup(data => data.name, data => data.id);
var nameLookup = list.ToLookup(
keySelector: p => p.name,
elementSelector: p => p
);
特性 | ToLookup | GroupBy |
---|---|---|
执行时机 | 立即执行 | 延迟执行 |
结果类型 | ILookup<TKey, TElement> | IEnumerable<IGrouping<TKey, TElement>> |
使用场景 | 需要立即缓存分组结果 | 需要延迟处理分组 |
查找效率 | O(1) | 每次查找都需要重新计算 |
性能特点
1、立即执行:调用 ToLookup 时会立即执行查询并构建查找表
2、高效查找:基于哈希表实现,按键查找非常高效(O(1))
3、不可变:创建后不能添加或删除元素
联合
用于实现类似 SQL 中的连接操作
GroupJoin
实现分组连接(group join),将第一个序列的每个元素与第二个序列中所有匹配的元素分组关联。
特点:
-
类似于 SQL 的 LEFT OUTER JOIN + GROUP BY
-
对于第一个序列(outer)中的每个元素,都会在结果中出现一次
-
第二个序列(inner)中的匹配元素会被分组收集
-
如果没有匹配项,则关联一个空集合
-
延迟执行
// 使用与Join相同的部门和员工数据
// 分组连接:每个部门及其所有员工
var groupJoinedData = departments.GroupJoin(
employees,
d => d.Id,
e => e.DepartmentId,
(d, emps) => new
{
Department = d.Name,
Employees = emps.Select(e => e.Name)
});
/*
结果:
{
Department = "HR",
Employees = ["Alice", "Charlie"]
},
{
Department = "IT",
Employees = ["Bob"]
}
(即使没有员工匹配的部门也会显示)
*/
Join
实现内连接(inner join),基于匹配键将两个序列的元素相关联。
特点:
-
类似于 SQL 的 INNER JOIN
-
只有当两个序列中都存在匹配键时才会包含在结果中
-
默认使用默认的相等比较器
class Department
{
public int Id { get; set; }
public string Name { get; set; }
}
class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int DepartmentId { get; set; }
}
List<Department> departments = new List<Department>
{
new Department { Id = 1, Name = "HR" },
new Department { Id = 2, Name = "IT" }
};
List<Employee> employees = new List<Employee>
{
new Employee { Id = 1, Name = "Alice", DepartmentId = 1 },
new Employee { Id = 2, Name = "Bob", DepartmentId = 2 },
new Employee { Id = 3, Name = "Charlie", DepartmentId = 1 },
new Employee { Id = 4, Name = "David", DepartmentId = 3 } // 无匹配部门
};
// 内连接:员工和部门
var joinedData = departments.Join(
employees,
d => d.Id,
e => e.DepartmentId,
(d, e) => new { e.Name, Department = d.Name });
/*
结果:
{ Name = "Alice", Department = "HR" }
{ Name = "Bob", Department = "IT" }
{ Name = "Charlie", Department = "HR" }
(没有David,因为部门ID 3不存在)
*/
投射
Select
将序列中的每个元素投影到新形式(一对一映射)。
特点:
-
一对一转换:每个输入元素对应一个输出元素
-
不改变元素数量(与源序列数量相同)
-
延迟执行
-
可以访问元素索引(使用带索引的重载)
int[] numbers = { 1, 2, 3, 4, 5 };
// 简单转换:数字转为字符串
var strings = numbers.Select(n => n.ToString()); // ["1", "2", "3", "4", "5"]
// 带索引的转换
var indexed = numbers.Select((n, index) => $"数字{n}在位置{index}");
// ["数字1在位置0", "数字2在位置1", ...]
SelectMany
将序列的每个元素投影到 IEnumerable<T>
并将结果序列合并为一个序列(一对多映射)。
int[][] numberSets = { new[] { 1, 2, 3 }, new[] { 4, 5 }, new[] { 6 } };
// 展平二维数组
var allNumbers = numberSets.SelectMany(nums => nums); // [1, 2, 3, 4, 5, 6]
// 笛卡尔积示例
var colors = new[] { "红", "绿", "蓝" };
var sizes = new[] { "大", "中", "小" };
var products = colors.SelectMany(
color => sizes,
(color, size) => $"{color}色的{size}号");
// ["红色的", "红色的中号", ... "蓝色的小号"]
聚合
用于对集合中的元素进行计算并返回单个结果。
Aggregate
对序列应用累加器函数,可以自定义聚合逻辑。
// 计算数字乘积
int[] numbers = { 1, 2, 3, 4 };
int product = numbers.Aggregate(1, (acc, num) => acc * num); // 1*2*3*4 = 24
// 字符串连接
string[] words = { "Hello", "World", "!" };
string sentence = words.Aggregate((current, next) => current + " " + next); // "Hello World !"
// 复杂聚合
double[] doubles = { 1.5, 2.0, 3.5 };
double sumOfSquares = doubles.Aggregate(
0.0,
(sum, val) => sum + Math.Pow(val, 2),
result => Math.Sqrt(result)); // 平方和的平方根
Average
计算数值序列的平均值。
int[] numbers = { 10, 20, 30 };
double avg = numbers.Average(); // 20.0
int?[] nullableNumbers = { 10, null, 30 };
double? nullableAvg = nullableNumbers.Average(); // 20.0
Count
返回序列中元素的数量,或满足条件的元素数量。
int[] numbers = { 1, 2, 3, 4, 5 };
int totalCount = numbers.Count(); // 5
int evenCount = numbers.Count(n => n % 2 == 0); // 2
LongCount
与 Count 类似,但返回 long 类型,用于可能超过 int 范围的超大集合。
var largeRange = Enumerable.Range(0, int.MaxValue).Concat(Enumerable.Range(0, 100));
long hugeCount = largeRange.LongCount(); // 2147483747
Max
返回序列中的最大值。
int[] numbers = { 5, 10, 3, 8 };
int max = numbers.Max(); // 10
DateTime[] dates = { new DateTime(2020, 1, 1), new DateTime(2023, 1, 1) };
DateTime latest = dates.Max(); // 2023-01-01
Min
返回序列中的最小值。
int[] numbers = { 5, 10, 3, 8 };
int min = numbers.Min(); // 3
Sum
计算数值序列的总和。
int[] numbers = { 1, 2, 3, 4 };
int sum = numbers.Sum(); // 10
修饰
All
检查集合中的所有元素是否都满足指定的条件。
int[] numbers = { 1, 2, 3, 4, 5 };
bool allLessThan10 = numbers.All(n => n < 10); // true
bool allEven = numbers.All(n => n % 2 == 0); // false
Any
检查集合中是否有任意元素满足指定的条件
int[] numbers = { 1, 2, 3, 4, 5 };
bool anyGreaterThan4 = numbers.Any(n => n > 4); // true
bool anyNegative = numbers.Any(n => n < 0); // false
bool hasElements = numbers.Any(); // true
Contains
检查集合中是否包含指定的元素。
int[] numbers = { 1, 2, 3, 4, 5 };
bool contains3 = numbers.Contains(3); // true
bool contains10 = numbers.Contains(10); // false
string[] names = { "Alice", "Bob", "Charlie" };
bool containsBob = names.Contains("Bob"); // true
bool containsCaseInsensitive = names.Contains("bob", StringComparer.OrdinalIgnoreCase); // true
元素
用于从序列中获取特定位置的元素
ElementAt
返回序列中指定索引处的元素。
ElementAtOrDefault
返回序列中指定索引处的元素,如果索引超出范围则返回默认值。
int[] numbers = { 10, 20, 30, 40, 50 };
var third = numbers.ElementAt(2); // 30
var sixth = numbers.ElementAt(5); // 抛出异常
var safeSixth = numbers.ElementAtOrDefault(5); // 0
First
返回序列中的第一个元素。
FirstOrDefault
返回序列中的第一个元素,如果序列为空则返回默认值。
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var first = numbers.First(); // 1
var firstEven = numbers.First(n => n % 2 == 0); // 2
List<int> empty = new List<int>();
var safeFirst = empty.FirstOrDefault(); // 0
var missing = numbers.FirstOrDefault(n => n > 10); // 0
Last
返回序列中的最后一个元素。
LastOrDefault
返回序列中的最后一个元素,如果序列为空则返回默认值。
int[] numbers = { 1, 2, 3, 4, 5 };
var last = numbers.Last(); // 5
var lastEven = numbers.Last(n => n % 2 == 0); // 4
int[] empty = Array.Empty<int>();
var safeLast = empty.LastOrDefault(); // 0
var missing = numbers.LastOrDefault(n => n > 10); // 0
Single
返回序列中的唯一元素,如果序列不包含恰好一个元素则抛出异常。
SingleOrDefault
返回序列中的唯一元素,如果序列为空则返回默认值,如果序列包含多个元素则抛出异常。
int[] singleNumber = { 5 };
int[] multipleNumbers = { 1, 2, 3 };
int[] empty = Array.Empty<int>();
var one = singleNumber.Single(); // 5
var fromEmpty = empty.SingleOrDefault(); // 0
// 以下会抛出异常
// var error1 = empty.Single();
// var error2 = multipleNumbers.Single();
// var error3 = multipleNumbers.SingleOrDefault();
方法 | 空序列行为 | 多个元素行为 | 默认返回值 | 使用场景 |
---|---|---|---|---|
ElementAt | 抛出异常 | 返回指定位置元素 | - | 已知索引访问 |
ElementAtOrDefault | 返回默认值 | 返回指定位置元素 | 有 | 安全索引访问 |
First | 抛出异常 | 返回第一个元素 | - | 获取首个元素 |
FirstOrDefault | 返回默认值 | 返回第一个元素 | 有 | 安全获取首个元素 |
Last | 抛出异常 | 返回最后一个元素 | - | 获取末尾元素 |
LastOrDefault | 返回默认值 | 返回最后一个元素 | 有 | 安全获取末尾元素 |
Single | 抛出异常 | 必须恰好一个元素 | - | 确保唯一性 |
SingleOrDefault | 返回默认值 | 必须最多一个元素 | 有 | 安全确保唯一性 |
集合
用于处理集合之间的关系和去重操作
Distinct
从序列中返回不重复的元素。
int[] numbers = { 1, 2, 2, 3, 4, 4, 5 };
var uniqueNumbers = numbers.Distinct(); // [1, 2, 3, 4, 5]
// 自定义比较器(不区分大小写的字符串比较)
string[] words = { "apple", "Apple", "banana", "Banana" };
var distinctWords = words.Distinct(StringComparer.OrdinalIgnoreCase); // ["apple", "banana"]
Except
返回两个序列的差集(存在于第一个序列但不在第二个序列中的元素)。
int[] numbers1 = { 1, 2, 3, 4, 5 };
int[] numbers2 = { 4, 5, 6, 7, 8 };
var difference = numbers1.Except(numbers2); // [1, 2, 3]
// 自定义比较器
string[] names1 = { "Alice", "Bob", "Charlie" };
string[] names2 = { "alice", "BOB" };
var uniqueNames = names1.Except(names2, StringComparer.OrdinalIgnoreCase); // ["Charlie"]
Intersect
返回两个序列的交集(同时存在于两个序列中的元素)。
int[] set1 = { 1, 2, 3, 4, 5 };
int[] set2 = { 4, 5, 6, 7, 8 };
var commonNumbers = set1.Intersect(set2); // [4, 5]
// 自定义比较器
Product[] dbProducts = GetDatabaseProducts();
Product[] localProducts = GetLocalProducts();
var syncedProducts = dbProducts.Intersect(localProducts, new ProductEqualityComparer());
Union
返回两个序列的并集(存在于任一序列中的元素)。
特点:
-
合并两个序列并去除重复项
-
相当于数学中的集合并集 (A ∪ B)
int[] numbers1 = { 1, 2, 3 };
int[] numbers2 = { 3, 4, 5 };
var allUniqueNumbers = numbers1.Union(numbers2); // [1, 2, 3, 4, 5]
// 自定义比较器
string[] tags1 = { "C#", "LINQ", "ASP.NET" };
string[] tags2 = { "c#", "linq", "Entity Framework" };
var uniqueTags = tags1.Union(tags2, StringComparer.OrdinalIgnoreCase);
// ["C#", "LINQ", "ASP.NET", "Entity Framework"]
分区
用于从序列中截取特定部分的元素,是 LINQ 中实现数据分页和条件筛选的重要工具。
Skip
跳过序列中指定数量的元素,返回剩余元素。
特点:
-
跳过前
count
个元素 -
如果
count
大于序列长度,返回空序列 -
如果
count
为 0,返回完整序列 -
延迟执行
int[] numbers = { 1, 2, 3, 4, 5 };
var result1 = numbers.Skip(2); // [3, 4, 5]
var result2 = numbers.Skip(5); // []
var result3 = numbers.Skip(0); // [1, 2, 3, 4, 5]
SkipWhile
跳过满足指定条件的元素,返回从第一个不满足条件的元素开始的所有元素。
特点:
-
跳过所有满足条件的起始元素
-
一旦遇到第一个不满足条件的元素,返回该元素及之后的所有元素
-
即使后面再有满足条件的元素也不会跳过
-
延迟执行
int[] numbers = { 1, 3, 5, 2, 4, 6 };
var result1 = numbers.SkipWhile(n => n < 4); // [5, 2, 4, 6]
var result2 = numbers.SkipWhile((n, index) => n < 4 && index < 2); // [5, 2, 4, 6]
Take
从序列开头返回指定数量的连续元素。
特点:
-
返回前
count
个元素 -
如果
count
大于序列长度,返回完整序列 -
如果
count
为 0,返回空序列 -
延迟执行
int[] numbers = { 1, 2, 3, 4, 5 };
var result1 = numbers.Take(3); // [1, 2, 3]
var result2 = numbers.Take(10); // [1, 2, 3, 4, 5]
var result3 = numbers.Take(0); // []
TakeWhile
返回满足指定条件的连续元素,直到遇到第一个不满足条件的元素为止。
特点:
-
从序列开头开始返回元素
-
一旦遇到第一个不满足条件的元素,立即停止返回
-
即使后面再有满足条件的元素也不会返回
-
延迟执行
int[] numbers = { 1, 2, 3, 4, 3, 2, 1 };
var result1 = numbers.TakeWhile(n => n < 4); // [1, 2, 3]
var result2 = numbers.TakeWhile((n, index) => n < 4 && index < 3); // [1, 2, 3]
串联
Concat
用于将两个序列连接成一个序列。
功能描述
-
将两个序列按顺序连接起来
-
保留两个序列中的所有元素
-
不修改原始序列,返回一个新的序列
-
延迟执行(只有在枚举结果时才会执行连接操作)
int[] numbers1 = { 1, 2, 3 };
int[] numbers2 = { 4, 5, 6 };
var combined = numbers1.Concat(numbers2);
// 结果: 1, 2, 3, 4, 5, 6
相等
SequenceEqual
用于判断两个序列是否包含相同顺序的相同元素。
功能描述
-
比较两个序列的长度和元素顺序
-
如果两个序列都为 null,返回 true
-
如果一个序列为 null 而另一个不为 null,返回 false
-
可以自定义相等比较器(IEqualityComparer)
int[] numbers1 = { 1, 2, 3 };
int[] numbers2 = { 1, 2, 3 };
int[] numbers3 = { 3, 2, 1 };
int[] numbers4 = { 1, 2, 3, 4 };
bool result1 = numbers1.SequenceEqual(numbers2); // true
bool result2 = numbers1.SequenceEqual(numbers3); // false (顺序不同)
bool result3 = numbers1.SequenceEqual(numbers4); // false (长度不同)
自定义比较器示例
class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
class ProductComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{
return x.Id == y.Id && x.Name == y.Name;
}
public int GetHashCode(Product obj)
{
return obj.Id.GetHashCode() ^ obj.Name.GetHashCode();
}
}
List<Product> products1 = new List<Product>
{
new Product { Id = 1, Name = "Apple" },
new Product { Id = 2, Name = "Banana" }
};
List<Product> products2 = new List<Product>
{
new Product { Id = 1, Name = "Apple" },
new Product { Id = 2, Name = "Banana" }
};
bool areEqual = products1.SequenceEqual(products2, new ProductComparer()); // true
使用场景
-
单元测试:验证方法返回的集合是否符合预期
-
集合变更检测:检查集合是否被修改
-
缓存验证:检查缓存数据是否仍然有效
范围状态
DefaultEmpty
如果序列为空,则返回包含默认值的单元素序列;否则返回原序列。
List<int> numbers = new List<int>();
var result1 = numbers.DefaultIfEmpty(); // 返回包含 0 的序列(int 的默认值)
var result2 = numbers.DefaultIfEmpty(100); // 返回包含 100 的序列
List<string> names = new List<string> { "Alice", "Bob" };
var result3 = names.DefaultIfEmpty("Unknown"); // 返回原序列 ["Alice", "Bob"]
Empty
返回指定类型的空序列。
IEnumerable<int> emptyNumbers = Enumerable.Empty<int>();
// 常用于条件查询
var products = someCondition
? dbContext.Products.Where(p => p.Price > 100)
: Enumerable.Empty<Product>();
Range
生成指定范围内的整数序列。
// 生成 1 到 10 的数字
var numbers = Enumerable.Range(1, 10); // [1, 2, 3, ..., 10]
// 生成 5 个从 10 开始的数字
var from10 = Enumerable.Range(10, 5); // [10, 11, 12, 13, 14]
// 结合其他 LINQ 方法使用
var squares = Enumerable.Range(1, 5).Select(x => x * x); // [1, 4, 9, 16, 25]
Repeat
生成包含重复值的序列。
// 生成 5 个 "Hello"
var hellos = Enumerable.Repeat("Hello", 5); // ["Hello", "Hello", ..., "Hello"]
// 生成 3 个默认构造的对象
var defaults = Enumerable.Repeat(new MyClass(), 3);
// 结合其他 LINQ 方法使用
var indices = Enumerable.Repeat(0, 10).Select((_, i) => i); // [0, 1, 2, ..., 9]
方法 | 类别 | 执行时机 | 主要用途 |
---|---|---|---|
DefaultIfEmpty | 转换方法 | 延迟 | 处理空序列时提供默认值 |
Empty | 生成方法 | 立即 | 创建指定类型的空序列 |
Range | 生成方法 | 延迟 | 生成数字范围序列 |
Repeat | 生成方法 | 延迟 | 生成重复值序列 |
转换
用于将集合或查询结果转换为特定类型的集合
AsEnumerable
将输入序列转换为 IEnumerable<T>
类型,强制后续操作使用 LINQ to Objects。
// 在 Entity Framework 中
var query = dbContext.Products
.Where(p => p.Price > 100)
.AsEnumerable() // 后续操作在内存中执行
.Select(p => new { p.Name, DiscountedPrice = p.Price * 0.9 });
AsQueryable
将 IEnumerable<T>
转换为 IQueryable<T>
,使后续操作可以使用表达式树。
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// 转换为 IQueryable 后可以使用表达式树
var queryable = numbers.AsQueryable()
.Where(x => x % 2 == 0)
.OrderByDescending(x => x);
Cast
将非泛型集合转换为指定类型的泛型集合,或尝试将元素强制转换为指定类型。主要用于处理非泛型集合(如 ArrayList)。
ArrayList list = new ArrayList { 1, 2, 3 };
// 转换为 IEnumerable<int>
var numbers = list.Cast<int>();
// 处理混合类型集合(会抛出异常如果类型不匹配)
ArrayList mixed = new ArrayList { 1, "two", 3 };
// var ints = mixed.Cast<int>(); // 运行时抛出 InvalidCastException
ToArray
将序列转换为数组。
IEnumerable<int> numbers = Enumerable.Range(1, 5);
int[] array = numbers.ToArray(); // [1, 2, 3, 4, 5]
// 常用于缓存查询结果
var cachedResults = dbContext.Products.Where(p => p.Price > 100).ToArray();
ToDictionary
根据键选择器将序列转换为字典。
class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
List<Person> people = new List<Person>
{
new Person { Id = 1, Name = "Alice" },
new Person { Id = 2, Name = "Bob" }
};
// 简单字典:Id 为键,整个对象为值
Dictionary<int, Person> dict1 = people.ToDictionary(p => p.Id);
// 复杂字典:Id 为键,Name 为值
Dictionary<int, string> dict2 = people.ToDictionary(
p => p.Id,
p => p.Name);
// 带比较器的字典(忽略键的大小写)
Dictionary<string, Person> dict3 = people.ToDictionary(
p => p.Name,
StringComparer.OrdinalIgnoreCase);
ToList
将序列转换为 List<T>
。
IEnumerable<int> numbers = Enumerable.Range(1, 5);
List<int> list = numbers.ToList(); // List<int> 包含 1-5
// 可以修改列表
list.Add(6);
// 常用于缓存查询结果
var productList = dbContext.Products.Where(p => p.Price > 100).ToList();
方法 | 返回类型 | 执行时机 | 主要用途 |
---|---|---|---|
AsEnumerable | IEnumerable<T> | 延迟 | 强制使用 LINQ to Objects |
AsQueryable | IQueryable<T> | 延迟 | 转换为可查询的表达式树 |
Cast | IEnumerable<T> | 延迟 | 类型转换/非泛型转泛型 |
ToArray | T[] | 立即 | 转换为固定大小数组 |
ToDictionary | Dictionary<T,K> | 立即 | 创建键值对字典 |
ToList | List<T> | 立即 | 转换为可变列表 |
优点
1. 代码简洁性和可读性
-
声明式语法:LINQ 采用类似 SQL 的声明式语法,使代码更易读易懂
-
减少样板代码:相比传统循环和条件语句,LINQ 可以显著减少代码量
-
直观表达意图:查询逻辑更接近自然语言表达
// 传统方式
List<int> evenNumbers = new List<int>();
foreach(var num in numbers)
{
if(num % 2 == 0)
{
evenNumbers.Add(num);
}
}
// LINQ方式
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
2. 强类型检查和编译时验证
-
编译时检查查询语法和类型安全
-
避免运行时错误
-
Visual Studio 提供智能感知(IntelliSense)支持
3. 统一查询接口
-
提供一致的查询模式,适用于:
-
内存对象 (LINQ to Objects)
-
数据库 (LINQ to Entities/EF Core)
-
XML (LINQ to XML)
-
其他数据源 (如第三方 LINQ 提供程序)
-
4. 延迟执行
-
查询定义与实际执行分离
-
只有在真正需要结果时才执行查询
-
提高性能,避免不必要的计算
5. 强大的功能集
-
提供丰富的操作符:筛选(Where)、排序(OrderBy)、分组(GroupBy)、连接(Join)等
-
支持聚合函数:Count、Sum、Average、Max、Min 等
-
支持分页:Skip、Take
-
支持集合操作:Distinct、Union、Intersect、Except
6. 与 C# 语言深度集成
-
可以直接在 C# 代码中嵌入查询表达式
-
支持 lambda 表达式和方法链式调用
-
与匿名类型、扩展方法等语言特性协同工作
缺点
1. 性能开销
-
LINQ to Objects 比传统循环有额外开销
-
每个 LINQ 操作符调用都会创建迭代器和委托
-
复杂查询可能产生多轮迭代
2. 调试困难
-
方法链式调用难以单步调试
-
匿名类型在调试时显示为不可读的编译器生成名称
-
复杂的 lambda 表达式可能难以诊断
3. 学习曲线
-
初学者需要理解的概念较多:延迟执行、表达式树、IQueryable 等
-
需要适应函数式编程思维
-
查询语法和方法语法并存可能造成混淆
4. 某些场景不适用
-
极高性能要求的场景
-
需要精细控制迭代过程的算法
-
需要副作用(修改状态)的操作
5. 数据库查询可能低效
-
LINQ to SQL/EF 生成的 SQL 可能不是最优
-
复杂的 LINQ 查询可能转换为低效的 SQL
-
N+1 查询问题常见
6. 内存消耗
-
某些操作(如排序、分组)需要内存中缓存数据
-
大型数据集可能导致内存压力
-
延迟执行可能导致资源保持打开状态
适用场景
1. 数据查询和筛选
-
从集合中查找特定条件的元素
-
对数据进行过滤和筛选
2. 数据转换和投影
-
将数据从一种形式转换为另一种形式
-
选择对象的部分属性(DTO投影)
3. 排序和分组
-
对数据进行排序和组织
-
按特定条件分组统计
4. 集合操作和比较
-
比较两个集合的差异
-
合并、交集等集合操作
5. 数据库访问
-
使用LINQ to Entities查询数据库
-
构建动态查询条件
6. XML数据处理(LINQ to XML)
-
查询和操作XML文档
-
将XML转换为对象
7. 分页处理
-
Web应用中的数据分页显示
-
大数据集的批处理
8. 聚合计算
-
统计数据指标
-
计算汇总值
9. 复杂数据关系处理
-
处理一对多、多对多关系
-
连接不同数据源
10. 并行数据处理
-
CPU密集型数据处理
-
可并行化的大规模数据操作
不适用的场景
虽然LINQ功能强大,但在以下场景可能不太适用:
-
极高性能需求:对性能要求极高的算法
-
复杂的状态操作:需要精细控制迭代过程的场景
-
简单的单元素操作:只需要处理单个元素时
-
已有专用API:某些特定数据源有更优的专用查询API
实践建议
-
性能敏感代码:对性能关键部分进行基准测试,必要时回退到传统循环
-
数据库查询:
-
使用
IQueryable
保持查询在数据库端执行 -
避免在内存中处理大型数据集
-
使用
.AsNoTracking()
提高只读查询性能
-
-
调试技巧:
-
将复杂查询分解为多个步骤
-
使用临时变量存储中间结果
-
检查 LINQ 生成的 SQL(对于数据库查询)
-
-
代码可读性:
-
对复杂查询使用查询表达式语法
-
为长的链式调用适当换行
-
给复杂的 lambda 表达式添加注释
-
-
资源管理:
-
及时释放需要处置的资源(如数据库连接)
-
对于大型查询考虑使用流式处理而非物化整个集合
-
LINQ 是 C# 中极其强大的工具,合理使用可以大幅提高开发效率和代码质量,但需要根据具体场景权衡其优缺点。