C# LINQ基础知识
简介
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 peoplewhere p.Age > 25orderby p.Nameselect 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 numberswhere n % 2 == 0select 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 numbersorderby valueselect 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 listorderby animal.height, animal.weightselect 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 numbersorderby value descendingselect 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 listorderby animal.height, animal.weight descendingselect 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 numbersgroup 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.0int?[] 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(); // 10DateTime[] 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); // falsestring[] 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); // 2List<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); // 4int[] 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# 中极其强大的工具,合理使用可以大幅提高开发效率和代码质量,但需要根据具体场景权衡其优缺点。
相关文章:
C# LINQ基础知识
简介 LINQ(Language Integrated Query),语言集成查询,是一系列直接将查询功能集成到 C# 语言的技术统称。使用LINQ表达式可以对数据集合进行过滤、排序、分组、聚合、串联等操作。 例子: public class Person {public int Id;public string…...
GCoNet+:更强大的团队协作 Co-Salient 目标检测器 2023 GCoNet+(翻译)
摘要 摘要:本文提出了一种新颖的端到端群体协作学习网络,名为GCoNet,它能够高效(每秒250帧)且有效地识别自然场景中的共同显著目标。所提出的GCoNet通过基于以下两个关键准则挖掘一致性表示,实现了共同显著…...
QT常见输入类控件及其属性
Line Edit QLineEdit用来表示单行输入框,可以输入一段文本,但是不能换行 核心属性: 核心信号 信号 说明 void cursorPositionChanged(int old,int new) 当鼠标移动时发出此型号,old为先前位置,new为新位置 void …...
Few-shot medical image segmentation with high-fidelity prototypes 论文总结
题目:Few-shot medical image segmentation with high-fidelity prototypes(高精确原型) 论文:Few-shot medical image segmentation with high-fidelity prototypes - ScienceDirect 源码:https://github.com/tntek/D…...
DBA工作常见问题整理
MVCC机制: PostgreSQL的多版本并发控制(MVCC)是其核心特性之一,它允许数据库在高并发环境下保持高性能的同时提供事务隔离。 MVCC通过维护数据的多个版本实现: 读操作不阻塞写操作写操作不阻塞读操作避免使用锁实现并发控制 PostgreSQL的MVCC特点 写时…...
深入理解Java包装类:自动装箱拆箱与缓存池机制
深入理解Java包装类:自动装箱拆箱与缓存池机制 对象包装器 Java中的数据类型可以分为两类:基本类型和引用类型。作为一门面向对象编程语言, 一切皆对象是Java语言的设计理念之一。但基本类型不是对象,无法直接参与面向对象操作&…...
如何使用Node-RED采集西门子PLC数据通过MQTT协议实现数据交互并WEB组态显示
需求概述 本章节主要实现一个流程:使用纵横智控的EG网关通过Node-red(可视化编程)采集PLC数据,并通过MQTT协议和VISION(WEB组态)实现数据交互。 以采集西门子PLC为例,要采集的PLC的IP、端口和点…...
【cocos creator 3.x】速通3d模型导入, 模型创建,阴影,材质使用,模型贴图绑定
1、右键创建平面,立方体 2、点击场景根节点,shadows勾选enabled3、点击灯光,shadow enabled勾选 4、点击模型,勾选接收阴影,投射阴影(按照需要勾选) 5、材质创建 6、选中节点,找…...
批量创建OpenStack实例
在Linux终端实现批量创建OpenStack实例,支持支持统计、并发创建、安全确认、重试机制、日志。 #!/bin/bash # # 增强版OpenStack实例创建脚本(修复日志功能) # 功能:支持统计、并发创建、安全确认、重试机制 # 更新日期…...
常用的 SQL 语句分类整理
以下是常用的 SQL 语句分类整理,覆盖数据查询、操作、表管理和高级功能,适用于大多数关系型数据库(如 MySQL、PostgreSQL、SQL Server): 目录 一、数据查询(DQL) 1. 基础查…...
驱动开发硬核特训 · Day 15:电源管理核心知识与实战解析
在嵌入式系统中,电源管理(Power Management)并不是“可选项”,而是实际部署中影响系统稳定性、功耗、安全性的重要一环。今天我们将以 Linux 电源管理框架 为基础,从理论结构、内核架构,再到典型驱动实战&a…...
【零基础】基于DeepSeek-R1与Qwen2.5Max的行业洞察自动化平台
自动生成行业报告,通过调用两个不同的大模型(DeepSeek 和 Qwen),完成从行业趋势分析到结构化报告生成的全过程。 完整代码:https://mp.weixin.qq.com/s/6pHi_aIDBcJKw1U61n1uUg 🧠 1. 整体目的与功能 该脚本实现了一个名为 ReportGenerator 的类,用于: 调用 DeepSe…...
Web前端 (CSS篇)
什么是CSS? css(Cascading Style Sheets)是层叠样式表或级联样式表,是一组设置规则,用于控制web页面外观。 为什么使用CSS? CSS 用于定义网页的样式,包括针对不同设备和屏幕尺寸的设计和布局。 CSS 实例 body {background-col…...
C 语言联合与枚举:自定义类型的核心解析
目录 1.联合体 1.1联合体的声明与创建 1.2联合体在内存中的存储 1.3相同成员的结构体与内存比较 1.4联合体内存空间大小的计算 1.5联合体的应用 2.枚举类型 2.1枚举变量的声明 2.2枚举变量的优点 2.3枚举的使用 上篇博客中,我们通过学习了解了C语言中一种自…...
基于Canal+Spring Boot+Kafka的MySQL数据变更实时监听实战指南
前期知识背景 binlog 什么是binlog 它记录了所有的DDL和DML(除 了数据查询语句)语句,以事件形式记录,还包含语句所执行的消耗的时间,MySQL 的二进制日志是事务安全型的。一般来说开启二进制日志大概会有1%的性能损耗。 binlog分类 MySQL Bi…...
MySQL运维三部曲初级篇:从零开始打造稳定高效的数据库环境
文章目录 一、服务器选型——给数据库一个舒适的家二、系统调优——打造高性能跑道三、MySQL配置——让数据库火力全开四、监控体系——数据库的体检中心五、备份恢复——数据安全的最后防线六、主从复制——数据同步的艺术七、安全加固——守护数据长城 引言:从小白…...
golang context源码
解析 context结构 Deadline:返回 context 的过期时间; Done:返回 context 中的 channel; Err:返回错误; Value:返回 context 中的对应 key 的值. type Context interface {Deadline() (deadl…...
【MySQL】MySQL的基础语法及其语句的介绍
1、基础语法 mysql -h【主机名】 -u【用户名】 -p //登录MySQL exit或quit; //退出MySQL show database; //查看MySQL下的所有数据库 use 【数据库名】; //进入数据库 show tables; //查看数据库下的所有表名 *MySQL的启动和关闭 &am…...
大模型应用开发自学笔记
理论学习地址: https://zh.d2l.ai/chapter_linear-networks/index.html autodl学术加速: source /etc/network_turboconda常见操作: 删除: conda remove --name myenv --all -y导出: conda env export > environment.yml…...
Spring能够有效地解决单例Bean之间的循环依赖问题
在Spring框架中,earlySingletonObjects和singletonObjects是两个与Bean实例化过程密切相关的概念,它们都存储在DefaultSingletonBeanRegistry类中。这两个概念主要用于Spring的依赖注入机制,特别是针对单例Bean的创建过程。 singletonObject…...
【计算机视觉】三维视觉项目 - Colmap二维图像重建三维场景
COLMAP 3D重建 项目概述项目功能项目运行方式1. 环境准备2. 编译 COLMAP3. 数据准备4. 运行 COLMAP 常见问题及解决方法1. **编译问题**2. **运行问题**3. **数据问题** 项目实战建议项目参考文献 项目概述 COLMAP 是一个开源的三维重建软件,专注于 Structure-from…...
Linux 离线部署 Docker 18.06.3 终极指南(附一键安装卸载脚本)
Linux 离线部署 Docker 18.06.3 终极指南(附一键安装/卸载脚本) 摘要:本文针对无外网环境的 Linux 服务器,提供基于二进制包的 Docker 18.06.3 离线安装全流程指南。包含自动化脚本设计、服务配置优化及安全卸载方案,…...
ALSA架构学习2(驱动MAX98357A)
1 前言和环境 之前其实写过两篇,一篇是讲ALSA,一篇是I2S。 ALSA架构学习1(框架)_alsa框架学习-CSDN博客 总线学习5--I2S_max98357接喇叭教程-CSDN博客 在ALSA那篇的结尾,也提了几个小练习。比如: ### 4…...
数据结构*集合框架顺序表-ArrayList
集合框架 常见的集合框架 什么是顺序表 顺序表是一种线性表数据结构,它借助一组连续的存储单元来依次存储线性表中的数据元素。一般情况下采用数组存储。 在数组上完成数据的增删查改。 自定义简易版的顺序表 代码展示: public interface IArray…...
VMware Workstation 保姆级 Linux(CentOS) 创建教程(附 iso)
文章目录 一、下载二、创建 一、下载 CentOS-7.9-x86_64-DVD-2009.iso 二、创建 VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包)...
51、项⽬中的权限管理怎么实现的
答:权限管理有三个很重要的模块; (1)⽤⼾模块:可以给⽤⼾分配不同的⻆⾊ (2)⻆⾊模块:可以授于⽤⼾不同的⻆⾊,不同的⻆⾊有不同权限 (3)权限模块:⽤于管理系统中的权限接⼝,为⻆⾊提供对…...
软考-信息系统项目管理师-2 信息技术发展
总结思维导图 云计算(掌握) (3)多租户和访问控制管理访问控制管理是云计算应用的核心问题之一云计算访问控制的研究主要集中在云计算访问控制模型、基于ABE密码体制的云计算访问控制、云中多租户及虚拟化访问控制研究云中多租户及虚拟化访问控制是云计算的典型特征。 大数据(…...
Spring Boot JPA 开发之Not an entity血案
项目状况介绍 项目环境 JDK 21Spring Boot 3.4.3Hibernate: 6.6.13.Final项目描述 因为是微服务架构,项目层级如下 project-parent project-com project-A … project-X 其中: project-parent定义依赖库的版本project-com 定义了一些公用的方法和配置,包括持久层的配置。…...
HTMLCSS实现轮播图效果
这段代码实现了一个具有自动轮播、手动切换功能的图片轮播图,并且配有指示器(小圆点)来显示当前图片位置。轮播图可通过左右箭头按钮进行手动切换,也能自动定时切换,当鼠标悬停在轮播图上时,自动轮播会暂停…...
嵌入式学习——opencv图像库编程
环境配置 OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和图像处理库,广泛用于各种计算机视觉任务,如图像处理、视频分析、人脸识别、物体检测、机器学习等。它提供了丰富的函数和工具,用于处理…...
