C# LINQ基础知识

news2025/4/21 21:44:17

简介

        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
);
特性ToLookupGroupBy
执行时机立即执行延迟执行
结果类型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();
方法返回类型执行时机主要用途
AsEnumerableIEnumerable<T>延迟强制使用 LINQ to Objects
AsQueryableIQueryable<T>延迟转换为可查询的表达式树
CastIEnumerable<T>延迟类型转换/非泛型转泛型
ToArrayT[]立即转换为固定大小数组
ToDictionaryDictionary<T,K>立即创建键值对字典
ToListList<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# 中极其强大的工具,合理使用可以大幅提高开发效率和代码质量,但需要根据具体场景权衡其优缺点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2339682.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GCoNet+:更强大的团队协作 Co-Salient 目标检测器 2023 GCoNet+(翻译)

摘要 摘要&#xff1a;本文提出了一种新颖的端到端群体协作学习网络&#xff0c;名为GCoNet&#xff0c;它能够高效&#xff08;每秒250帧&#xff09;且有效地识别自然场景中的共同显著目标。所提出的GCoNet通过基于以下两个关键准则挖掘一致性表示&#xff0c;实现了共同显著…

QT常见输入类控件及其属性

Line Edit QLineEdit用来表示单行输入框&#xff0c;可以输入一段文本&#xff0c;但是不能换行 核心属性&#xff1a; 核心信号 信号 说明 void cursorPositionChanged(int old,int new) 当鼠标移动时发出此型号&#xff0c;old为先前位置&#xff0c;new为新位置 void …

Few-shot medical image segmentation with high-fidelity prototypes 论文总结

题目&#xff1a;Few-shot medical image segmentation with high-fidelity prototypes&#xff08;高精确原型&#xff09; 论文&#xff1a;Few-shot medical image segmentation with high-fidelity prototypes - ScienceDirect 源码&#xff1a;https://github.com/tntek/D…

如何使用Node-RED采集西门子PLC数据通过MQTT协议实现数据交互并WEB组态显示

需求概述 本章节主要实现一个流程&#xff1a;使用纵横智控的EG网关通过Node-red&#xff08;可视化编程&#xff09;采集PLC数据&#xff0c;并通过MQTT协议和VISION&#xff08;WEB组态&#xff09;实现数据交互。 以采集西门子PLC为例&#xff0c;要采集的PLC的IP、端口和点…

【cocos creator 3.x】速通3d模型导入, 模型创建,阴影,材质使用,模型贴图绑定

1、右键创建平面&#xff0c;立方体 2、点击场景根节点&#xff0c;shadows勾选enabled3、点击灯光&#xff0c;shadow enabled勾选 4、点击模型&#xff0c;勾选接收阴影&#xff0c;投射阴影&#xff08;按照需要勾选&#xff09; 5、材质创建 6、选中节点&#xff0c;找…

驱动开发硬核特训 · Day 15:电源管理核心知识与实战解析

在嵌入式系统中&#xff0c;电源管理&#xff08;Power Management&#xff09;并不是“可选项”&#xff0c;而是实际部署中影响系统稳定性、功耗、安全性的重要一环。今天我们将以 Linux 电源管理框架 为基础&#xff0c;从理论结构、内核架构&#xff0c;再到典型驱动实战&a…

【零基础】基于DeepSeek-R1与Qwen2.5Max的行业洞察自动化平台

自动生成行业报告,通过调用两个不同的大模型(DeepSeek 和 Qwen),完成从行业趋势分析到结构化报告生成的全过程。 完整代码:https://mp.weixin.qq.com/s/6pHi_aIDBcJKw1U61n1uUg 🧠 1. 整体目的与功能 该脚本实现了一个名为 ReportGenerator 的类,用于: 调用 DeepSe…

C 语言联合与枚举:自定义类型的核心解析

目录 1.联合体 1.1联合体的声明与创建 1.2联合体在内存中的存储 1.3相同成员的结构体与内存比较 1.4联合体内存空间大小的计算 1.5联合体的应用 2.枚举类型 2.1枚举变量的声明 2.2枚举变量的优点 2.3枚举的使用 上篇博客中&#xff0c;我们通过学习了解了C语言中一种自…

基于Canal+Spring Boot+Kafka的MySQL数据变更实时监听实战指南

前期知识背景 binlog 什么是binlog 它记录了所有的DDL和DML(除 了数据查询语句)语句&#xff0c;以事件形式记录&#xff0c;还包含语句所执行的消耗的时间&#xff0c;MySQL 的二进制日志是事务安全型的。一般来说开启二进制日志大概会有1%的性能损耗。 binlog分类 MySQL Bi…

MySQL运维三部曲初级篇:从零开始打造稳定高效的数据库环境

文章目录 一、服务器选型——给数据库一个舒适的家二、系统调优——打造高性能跑道三、MySQL配置——让数据库火力全开四、监控体系——数据库的体检中心五、备份恢复——数据安全的最后防线六、主从复制——数据同步的艺术七、安全加固——守护数据长城 引言&#xff1a;从小白…

【MySQL】MySQL的基础语法及其语句的介绍

1、基础语法 mysql -h【主机名】 -u【用户名】 -p //登录MySQL exit或quit; //退出MySQL show database; //查看MySQL下的所有数据库 use 【数据库名】; //进入数据库 show tables; //查看数据库下的所有表名 *MySQL的启动和关闭 &am…

【计算机视觉】三维视觉项目 - Colmap二维图像重建三维场景

COLMAP 3D重建 项目概述项目功能项目运行方式1. 环境准备2. 编译 COLMAP3. 数据准备4. 运行 COLMAP 常见问题及解决方法1. **编译问题**2. **运行问题**3. **数据问题** 项目实战建议项目参考文献 项目概述 COLMAP 是一个开源的三维重建软件&#xff0c;专注于 Structure-from…

ALSA架构学习2(驱动MAX98357A)

1 前言和环境 之前其实写过两篇&#xff0c;一篇是讲ALSA&#xff0c;一篇是I2S。 ALSA架构学习1&#xff08;框架&#xff09;_alsa框架学习-CSDN博客 总线学习5--I2S_max98357接喇叭教程-CSDN博客 在ALSA那篇的结尾&#xff0c;也提了几个小练习。比如&#xff1a; ### 4…

数据结构*集合框架顺序表-ArrayList

集合框架 常见的集合框架 什么是顺序表 顺序表是一种线性表数据结构&#xff0c;它借助一组连续的存储单元来依次存储线性表中的数据元素。一般情况下采用数组存储。 在数组上完成数据的增删查改。 自定义简易版的顺序表 代码展示&#xff1a; public interface IArray…

VMware Workstation 保姆级 Linux(CentOS) 创建教程(附 iso)

文章目录 一、下载二、创建 一、下载 CentOS-7.9-x86_64-DVD-2009.iso 二、创建 VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包)

软考-信息系统项目管理师-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实现轮播图效果

这段代码实现了一个具有自动轮播、手动切换功能的图片轮播图&#xff0c;并且配有指示器&#xff08;小圆点&#xff09;来显示当前图片位置。轮播图可通过左右箭头按钮进行手动切换&#xff0c;也能自动定时切换&#xff0c;当鼠标悬停在轮播图上时&#xff0c;自动轮播会暂停…

嵌入式学习——opencv图像库编程

环境配置 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和图像处理库&#xff0c;广泛用于各种计算机视觉任务&#xff0c;如图像处理、视频分析、人脸识别、物体检测、机器学习等。它提供了丰富的函数和工具&#xff0c;用于处理…

【每日八股】复习 MySQL Day1:事务

文章目录 复习 MySQL Day1&#xff1a;事务MySQL 事务的四大特性&#xff1f;并发事务会出现什么问题&#xff1f;MySQL 事务的隔离级别&#xff1f;不同事务隔离级别下会发生什么问题&#xff1f;MVCC 的实现原理&#xff1f;核心数据结构版本链构建示例可见性判断算法MVCC 可…