C# LINQ基础知识

发布于:2025-04-18 ⋅ 阅读:(23) ⋅ 点赞:(0)

简介

        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

使用场景

  1. 单元测试:验证方法返回的集合是否符合预期

  2. 集合变更检测:检查集合是否被修改

  3. 缓存验证:检查缓存数据是否仍然有效

范围状态

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功能强大,但在以下场景可能不太适用:

  1. 极高性能需求:对性能要求极高的算法

  2. 复杂的状态操作:需要精细控制迭代过程的场景

  3. 简单的单元素操作:只需要处理单个元素时

  4. 已有专用API:某些特定数据源有更优的专用查询API

实践建议

  1. 性能敏感代码:对性能关键部分进行基准测试,必要时回退到传统循环

  2. 数据库查询

    • 使用 IQueryable 保持查询在数据库端执行

    • 避免在内存中处理大型数据集

    • 使用 .AsNoTracking() 提高只读查询性能

  3. 调试技巧

    • 将复杂查询分解为多个步骤

    • 使用临时变量存储中间结果

    • 检查 LINQ 生成的 SQL(对于数据库查询)

  4. 代码可读性

    • 对复杂查询使用查询表达式语法

    • 为长的链式调用适当换行

    • 给复杂的 lambda 表达式添加注释

  5. 资源管理

    • 及时释放需要处置的资源(如数据库连接)

    • 对于大型查询考虑使用流式处理而非物化整个集合

LINQ 是 C# 中极其强大的工具,合理使用可以大幅提高开发效率和代码质量,但需要根据具体场景权衡其优缺点。


网站公告

今日签到

点亮在社区的每一天
去签到