C# LINQ 基础教程

74 阅读12分钟

微软官方教程链接:C# 中的语言集成查询 (LINQ) | Microsoft Learn

LINQ 语言集成查询

LINQ (Language Integrated Query)翻译过来就是语言集成查询。它为查询跨各种数据源和格式的数据提供了一致的模型,所以叫集成查询。由于这种查询并没有制造新的语言而只是在现有的语言基础上来实现,所以叫语言集成查询

在 C# 中,从功能上 LINQ 可分为两类:

  • LINQ to Object
  • LINQ to XML

从语法上 LINQ 可以分为:

  • LINQ to Object

    // LINQ to Object
    var list1 = from user in users
               where user.Name.Contains("李")
               select user.Id;
    
    >>>System.Linq.Enumerable+WhereSelectListIterator`2[LINQConsole.User,System.Int32]
    
  • LINQ 扩展方法

    // LINQ扩展方法
    var list2 = users
              .Where(u => u.Name.Contains("李"))
              .Select(u => u.Id);
    
    >>>System.Linq.Enumerable+WhereSelectListIterator`2[LINQConsole.User,System.Int32]
    

大多数 LINQ to Object 都可以用 LINQ 扩展方法实现等同的效果,而且平时开发中用的最多的是 LINQ 扩展方法。

LINQ to Object 多用于映射数据库的查询,LINQ to XML 用于查询 XML 元素数据。使用 LINQ 查询的前提是对象必须是一个 IEnumerable 集合。另外,LINQ 查询大多是都是链式查询,即操作的数据源是 IEnumerable<T1> 类型,返回的是 IEnumerable<T2> 类型。

标准查询运算符按执行方式的分类

  • 立即执行:即时执行的运算符会立即执行查询并返回结果,查询会在调用时完成。
  • 延迟执行:延迟执行的运算符定义查询时并不立即执行,只有在实际枚举(遍历)时才会执行查询。
    • 延迟的流式处理执行:这类延迟执行的查询会返回 IEnumerable,查询结果在枚举时逐个项被执行(如 Select(), Where(), Take() 等),适用于处理内存中的集合。
    • 延迟非流式处理执行:这类延迟执行的查询返回 IQueryable,查询在与数据库等数据源交互时执行,支持构建复杂的查询表达式,并可以推迟到实际执行时发送到数据库执行(如 Entity Framework 中的查询)。

流式处理

流式处理运算符不需要在生成元素前读取所有源数据。 在执行时,流式处理运算符一边读取每个源元素,一边对该源元素执行运算,并在可行时生成元素。 流式处理运算符将持续读取源元素直到可以生成结果元素。 这意味着可能要读取多个源元素才能生成一个结果元素。

非流式处理

非流式处理运算符必须先读取所有源数据,然后才能生成结果元素。 排序或分组等运算均属于此类别。 在执行时,非流式处理查询运算符读取所有源数据,将其放入数据结构,执行运算,然后生成结果元素。

LINQ 分类表

立即执行

Aggregate 聚合

对序列应用累加器函数。 将指定的种子值用作累加器的初始值,并使用指定的函数选择结果值。

返回类型:TSource,执行方式:立即执行

public static void Main() {
    var numbers = new List<int> { 1, 2, 3, 4, 5 };

    // 使用初始值 10 计算总和
    var sumWithSeed = numbers.Aggregate(10, (accumulator, item) => accumulator + item);

    Console.WriteLine(sumWithSeed);
}

>>> 25
All 全部

确定序列中的所有元素是否都满足条件。

返回类型:Boolean,执行方式:立即执行

public static Main() {
    List<int> list =new List<int>() {1, 2, 3, 4, 5};
    
    var result = list.All(v => v > 0);
}
>>> true
Any 任何的

确定序列是否包含任何元素。一旦找到满足条件的值即可立即停止枚举。

返回类型:Boolean,执行方式:立即执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5};
    
    var result = list.Any(v => v > 4);
}
>>> true
Average 平均

计算数值序列的平均值。

返回类型:单个数值,执行方式:立即执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5, 6, 7, 8, 9};
    
    var result = list.Average();
}
>>> 5
Contains 包含

通过使用默认的相等比较器确定序列是否包含指定的元素。

返回类型:Boolean,执行方式:立即执行

public static Main() {
    List<string> list = new List<string>() {"小白", "小红"};

    var result = list1.Contains("小白");
}
>>> true
Count 总数

返回序列中的元素数量。

返回类型:Int32,执行方式:立即执行

public static Main() {
    List<string> list = new List<string>() {"小白", "小红"};

    var result = list.Count();
}
>>> 2
ElementAt 求索引位置

返回序列中指定索引处的元素。

返回类型:TSource,执行方式:立即执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5};
    var result = list.ElementAt(1);
}
>>> 2
ElementAtOrDefault 元素处于或默认

返回序列中指定索引处的元素;如果索引超出范围,则返回默认值。

返回类型:TSource,执行方式:立即执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5};
    var result = list.ElementAt(6);
}
>>> 0

==Tips==:引用类型和可为空类型的默认值为 null

Empty 空的

返回具有指定类型参数的空 IEnumerable。

返回类型:IEnumerable,执行方式:立即执行

First 第一位的

返回序列中的第一个元素。

返回类型:TSource,执行方式:立即执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5, 6};

    var result = list.First();
}
>>> 1
FirstOrDefault 第一位的或默认

返回序列中的第一个元素;如果未找到该元素,则返回默认值。

返回类型:TSource,执行方式:立即执行

public static main() {
    string[] names = { "Hartono, Tommy", "Adams, Terry",
                     "Andersen, Henriette Thaulow",
                     "Hedlund, Magnus", "Ito, Shu" };

	string firstLongName = names.FirstOrDefault(name => name.Length > 20);
    Console.WriteLine(firstLongName);
}

>>>""
Last 最后的

返回序列的最后一个元素。

返回类型:TSource,执行方式:立即执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5, 6};

    var result = list.Last();
}
>>> 6
LastOrDefault 最后的或默认

返回序列中的最后一个元素;如果未找到该元素,则返回默认值。

返回类型:TSource,执行方式:立即执行

public static main() {
    string[] names = { "Hartono, Tommy", "Adams, Terry",
                     "Andersen, Henriette Thaulow",
                     "Hedlund, Magnus", "Ito, Shu" };

	string LastLongName = names.LastOrDefault(name => name.Length > 20);
    Console.WriteLine(LastLongName);
}

>>>""
LongCount 长计数

返回表示序列中的元素数量的 Int64。

返回类型:Int64,执行方式:立即执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5, 6};

    var result = list.LongCount();
}
>>> 6

使用此方法,而不是 Count 在预期结果大于 MaxValue 时使用 。

Max 最大

返回值序列中的最大值。

返回类型:单个数值 TSource 或 TResult?,执行方式:立即执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5, 6};

    var result = list.Max();
}
>>> 6
Min 最小

返回值序列中的最小值。

返回类型:单个数值 TSource 或 TResult?,执行方式:立即执行

public static void Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5, 6};

    var result = list.Min();
}
>>> 1
SequenceEqual 相等断言

根据相等比较器确定两个序列是否相等。

如果根据相应类型的默认相等比较器,两个源序列的长度相等,且其相应元素相等,则为 true;否则为 false

返回类型:Boolean,执行方式:立即执行

class Pet
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public static void main() {
{
    Pet pet1 = new Pet { Name = "Turbo", Age = 2 };
    Pet pet2 = new Pet { Name = "Peanut", Age = 8 };

    // Create two lists of pets.
    List<Pet> pets1 = new List<Pet> { pet1, pet2 };
    List<Pet> pets2 = new List<Pet> { pet1, pet2 };

    bool equal = pets1.SequenceEqual(pets2);
}
    
>>> true
Single 单一的

返回序列中的单个特定元素。

返回类型:TSource,执行方式:立即执行

public static void main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5};
    Console.WriteLine(list.Single(l => l > 4));
}

>>> 5

==Tips==:如果序列为空或包含多个元素,它都会抛出异常。这也是和 First 不一样的地方。

SingleOrDefault 单一的或默认

返回序列中的单个特定元素;如果未找到该元素,则返回默认值。

返回类型:TSource?,执行方式:立即执行

public static void main() {
    List<string> list = new List<int>() {"1", "11", "111"};
    Console.WriteLine(list.Single(l => l.Length > 4));
}

>>> ""
Sum 求和

计算数值序列的和。

返回类型:单个数值,执行方式:立即执行

public static void Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5, 6};

    var result = list.Sum();
}
>>> 21
ToArray 转为数组

从 IEnumerable 中创建数组。

返回类型:TSource[],执行方式:立即执行

class Package
{
    public string Company { get; set; }
    public double Weight { get; set; }
}

public static void Main()
{
    List<Package> packages =
        new List<Package>
            { new Package { Company = "Coho Vineyard", Weight = 25.2 },
              new Package { Company = "Lucerne Publishing", Weight = 18.7 },
              new Package { Company = "Wingtip Toys", Weight = 6.0 },
              new Package { Company = "Adventure Works", Weight = 33.8 } };

    string[] companies = packages.Select(pkg => pkg.Company).ToArray();

    Console.WriteLine(JsonSerializer.Serialize(companies));
}
>>>["Coho Vineyard", "Lucerne Publishing", "Wingtip Toys", "Adventure Works"]
ToDictionary 转为字典

从 IEnumerable 创建一个 Dictionary。

返回类型:Dictionary,执行方式:立即执行

class Package
{
    public string Company { get; set; }
    public double Weight { get; set; }
    public long TrackingNumber { get; set; }
}

public static void Main()
{
    List<Package> packages =
        new List<Package>
            { new Package { Company = "Coho Vineyard", Weight = 25.2, TrackingNumber = 89453312L },
              new Package { Company = "Lucerne Publishing", Weight = 18.7, TrackingNumber = 89112755L },
              new Package { Company = "Wingtip Toys", Weight = 6.0, TrackingNumber = 299456122L },
              new Package { Company = "Adventure Works", Weight = 33.8, TrackingNumber = 4665518773L } };

    // Create a Dictionary of Package objects,
    // using TrackingNumber as the key.
    Dictionary<long, Package> dictionary =
        packages.ToDictionary(p => p.TrackingNumber);

    foreach (KeyValuePair<long, Package> kvp in dictionary)
    {
        Console.WriteLine(
            "Key {0}: {1}, {2} pounds",
            kvp.Key,
            kvp.Value.Company,
            kvp.Value.Weight);
    }
}

>>>  Key 89453312: Coho Vineyard, 25.2 pounds
     Key 89112755: Lucerne Publishing, 18.7 pounds
     Key 299456122: Wingtip Toys, 6 pounds
     Key 4665518773: Adventure Works, 33.8 pounds
ToList 转为数组

从 IEnumerable 创建一个 List。

返回类型:IList,执行方式:立即执行

public static void Main() {
    string[] fruits = { "apple", "passionfruit", "banana", "mango",
                      "orange", "blueberry", "grape", "strawberry" };

    List<int> lengths = fruits.Select(fruit => fruit.Length).ToList();

    Console.WriteLine(JsonSerializer.Serialize(length));
}

>>> [5, 12, 6, 5, 6, 9, 5, 10]
ToLookup 转为检查

从 IEnumerable 生成一个泛型 Lookup。

返回类型:ILookup,执行方式:立即执行

class Package
{
    public string Company { get; set; }
    public double Weight { get; set; }
    public long TrackingNumber { get; set; }
}

public static void Main()
{
    List<Package> packages =
        new List<Package>
            { new Package { Company = "Coho Vineyard",
                  Weight = 25.2, TrackingNumber = 89453312L },
              new Package { Company = "Lucerne Publishing",
                  Weight = 18.7, TrackingNumber = 89112755L },
              new Package { Company = "Wingtip Toys",
                  Weight = 6.0, TrackingNumber = 299456122L },
              new Package { Company = "Contoso Pharmaceuticals",
                  Weight = 9.3, TrackingNumber = 670053128L },
              new Package { Company = "Wide World Importers",
                  Weight = 33.8, TrackingNumber = 4665518773L } };

    ILookup<char, string> lookup =
        packages
        .ToLookup(p => p.Company[0],
                  p => p.Company + " " + p.TrackingNumber);

    foreach (IGrouping<char, string> packageGroup in lookup)
    {
        Console.WriteLine(packageGroup.Key);
        foreach (string str in packageGroup)
            Console.WriteLine("    {0}", str);
    }
}

>>>
 C
     Coho Vineyard 89453312
     Contoso Pharmaceuticals 670053128
 L
     Lucerne Publishing 89112755
 W
     Wingtip Toys 299456122
     Wide World Importers 4665518773

区别于 GroupBy

  • 立即执行ToLookup 会立即执行查询并返回 ILookup<TKey, TElement>,而 GroupBy 是延迟执行的,只有在枚举时才执行。
  • 不可变性ILookup<TKey, TElement> 是不可变的,不能像字典那样进行修改操作(如添加或删除键)。
  • 快速查找ILookup<TKey, TElement> 可以通过键高效地查找每个分组,而 GroupBy 返回的 IGrouping<TKey, TElement> 是按顺序枚举的,通常需要手动遍历。

延迟的流式处理执行

AsEnumerable 作为可列举的

返回类型化为 IEnumerable 的输入。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

class Clump<T> : List<T>
{
    // 自定义的where方法
    public IEnumerable<T> Where(Func<T, bool> predicate)
    {
        Console.WriteLine("在Clump的Where()实现中。");
        return Enumerable.Where(this, predicate);
    }
}

static void AsEnumerableEx1()
{
    Clump<string> fruitClump =
        new Clump<string> { "apple", "passionfruit", "banana",
            "mango", "orange", "blueberry", "grape", "strawberry" };

    //第一次调用Where():
	//使用谓词调用cluster的Where()方法。
    IEnumerable<string> query1 =
        fruitClump.Where(fruit => fruit.Contains("o"));

    Console.WriteLine("query1 has been created.\n");

    //第二次调用Where():
//首先调用AsEnumerable()来隐藏Clump的Where()方法
//强制System.Linq。Enumerable要调用的Where()方法。
    IEnumerable<string> query2 =
        fruitClump.AsEnumerable().Where(fruit => fruit.Contains("o"));

    // Display the output.
    Console.WriteLine("query2 has been created.");
}
Cast 投射

将 IEnumerable 的元素强制转换为指定的类型。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

System.Collections.ArrayList fruits = new System.Collections.ArrayList();
fruits.Add("mango");
fruits.Add("apple");
fruits.Add("lemon");

IEnumerable<string> query =
    fruits.Cast<string>().OrderBy(fruit => fruit).Select(fruit => fruit);

foreach (string fruit in query)
{
    Console.WriteLine(fruit);
}
Concat 合并多个数组

连接两个序列。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static Main() {
    List<int> list1 = new List<int>() {1, 2, 3, 4, 5};
    List<int> list2 = new List<int>() {6, 7, 8, 9, 10};
    var result = list1.Concat(list2).Count();
}
>>> 10
DefaultIfEmpty 为空时返回默认序列

返回 IEnumerable 的元素,如果序列为空,则返回默认值单一实例集合。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

List<int> numbers = new List<int>();

// 使用了空列表返回了0有一个数值
foreach (int number in numbers.DefaultIfEmpty())
{
    Console.WriteLine(number);
}
>>> 0
Distinct 不同的

通过使用指定的 IEqualityComparer 对值进行比较,返回序列中的非重复元素。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static Main() {
    List<int> list = new List<int>() {1, 2, 3, 4, 5, 4, 3};
    var result = list.Distinct();
}
>>> {1,2,3,4,5}
DistinctBy 不同的条件

它用于 根据指定的键选择器 去重集合中的元素。与 Distinct 不同,DistinctBy 允许你指定某个属性或表达式(即“键”),然后基于这个键来判断元素是否重复。这样,你可以去重集合中的元素,但不必对整个元素进行完全相等的比较,而是根据指定的字段或属性去重。

DistinctBy 主要用于去除基于某个属性或表达式的重复项,这对于复杂类型(如对象)尤其有用。

public class Person
{
    public string Name { get; set; }
    public DateTime BirthDate { get; set; }
}

public static void Main() {
    var people = new List<Person>
    {
        new Person { Name = "Alice", BirthDate = new DateTime(1990, 1, 1) },
        new Person { Name = "Bob", BirthDate = new DateTime(1992, 5, 5) },
        new Person { Name = "Charlie", BirthDate = new DateTime(1990, 1, 1) },
        new Person { Name = "David", BirthDate = new DateTime(1993, 7, 9) }
    };

    // 使用 DistinctBy 按年份去重
    var distinctPeople = people.DistinctBy(p => p.BirthDate.Year);

    foreach (var person in distinctPeople)
    {
        Console.WriteLine($"Name: {person.Name}, Birth Year: {person.BirthDate.Year}");
    }
}
>>> Name: Alice, Birth Year: 1990
    Name: Bob, Birth Year: 1992
    Name: David, Birth Year: 1993
OfType 类型筛选

返回类型:IEnumerable,执行方式:延迟的流式处理执行

根据指定类型筛选 IEnumerable 的元素。

public static void Main() {
    var mixedCollection = new ArrayList() { 1, "Hello", 3.14, "World", true, "C#" };

	var strings = mixedCollection.OfType<string>();
    Console.WriteLine(JsonSerializer,Serialze(strings));
}

>>>["Hello", "World", "C#"]
Range 范围

生成指定范围内的整数的序列。静态方法。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static main() {
    IEnumerable<int> list = Enumerable.Range(1, 10);
    Console.WriteLine(JsonSerializer.Serialize(list));
}

>>>[1,2,3,4,5,6,7,8,9,10]

==Tips==:Range 中的两个参数第一个代表开始值,第一个代表个数(不是结束值)。

Repeat 重复

生成包含一个重复值的序列。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static main() {
    IEnumerable<int> list = Enumerable.Repeat(1, 10);
    Console.WriteLine(JsonSerializer.Serialize(list));
}

>>>[1,1,1,1,1,1,1,1,1,1]
Select 选择

将序列中的每个元素投影到新表单。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static main() {
    string[] fruits = { "apple", "banana", "mango", "orange",
                      "passionfruit", "grape" };

    var query =
        fruits.Select((fruit, index) =>
                          new { index, str = fruit.Substring(0, index) });

    foreach (var obj in query)
    {
        Console.WriteLine("{0}", obj);
    }
}

>>>  { index = 0, str =  }
     { index = 1, str = b }
     { index = 2, str = ma }
     { index = 3, str = ora }
     { index = 4, str = pass }
     { index = 5, str = grape }
SelectMany 超多选择

将序列的每个元素投影到 IEnumerable 并将结果序列合并为一个序列。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public class Student
{
    public string Name { get; set; }
    public List<string> Courses { get; set; }
}

public static void Main() {
    var students = new List<Student>
    {
        new Student { Name = "Alice", Courses = new List<string> { "Math", "English" } },
        new Student { Name = "Bob", Courses = new List<string> { "Science", "History" } },
        new Student { Name = "Charlie", Courses = new List<string> { "Art", "Math" } }
    };

    // 使用 SelectMany 扁平化课程列表
    var allCourses = students.SelectMany(student => student.Courses);

    foreach (var course in allCourses)
    {
        Console.WriteLine(course);
    }
}

>>> 
Math
English
Science
History
Art
Math

SelectMany 主要用于将集合中的每个元素映射成多个元素,并将这些元素合并成一个单一的集合。这对于处理多层嵌套集合、集合中的集合(如列表中的列表、数组中的数组等)非常有用。

Skip 跳过

跳过序列中指定数量的元素,然后返回剩余的元素。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static void main() {
    int[] grades = { 59, 82, 70, 56, 92, 98, 85 };
    Console.WriteLine(JsonSerializer.Serialize(grades.Skip(3)));
}
>>> [56, 92, 98, 85]
SkipWhile 跳过判断

如果指定的条件为 true,则跳过序列中的元素,然后返回剩余的元素。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static void main() {
    int[] grades = { 59, 82, 70, 56, 92, 98, 85 };

    IEnumerable<int> lowerGrades =
        grades
        .OrderByDescending(grade => grade)
        .SkipWhile(grade => grade >= 80);
    Console.WriteLine(JsonSerializer.Serialize(lowerGrades));
}
>>> [70, 59, 56]

==Tips==:SkipWhile 方法在序列中从开始处逐个检查元素,直到条件不满足为止。在遇到第一个不满足条件的元素时,它会停止跳过元素,剩下的元素(包括当前元素)都会被保留。

Take 取出

从序列的开头返回指定数量的相邻元素。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static void main() {
    int[] grades = { 59, 82, 70, 56, 92, 98, 85 };

    IEnumerable<int> topThreeGrades = grades.Take(3);
    
    Console.WriteLine(JsonSerializer.Serialize(topThreeGrades));
}
>>> [59, 82, 70]
TakeWhile 取出判断

如果指定的条件为 true,则返回序列中的元素,然后跳过剩余的元素。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static void Main() {
    string[] fruits = { "apple", "passionfruit", "banana", "mango",
                      "orange", "blueberry", "grape", "strawberry" };

    IEnumerable<string> query =
        fruits.TakeWhile((fruit, index) => fruit.Length >= index);
}

>>> ["apple", "passionfruit", "banana", "mango", "orange", "blueberry"]

==Tips==:TakeWhileSkipWhile 是相反的。

Union 并集

生成两个序列的并集。

返回类型:IEnumerable,执行方式:延迟的流式处理执行

public static void Main() {
    int[] ints1 = { 5, 3, 9, 7, 5, 9, 3, 7 };
    int[] ints2 = { 8, 3, 6, 4, 4, 9, 1, 0 };

    IEnumerable<int> union = ints1.Union(ints2);
    
    Console.WriteLine(JsonSerializer.Serialize(union));
}
Where 哪里

返回类型:IEnumerable,执行方式:延迟的流式处理执行

基于谓词筛选值序列。

public static void Main() {
    List<string> fruits =
    new List<string> { "apple", "passionfruit", "banana", "mango",
                    "orange", "blueberry", "grape", "strawberry" };

    IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);
    Console.WriteLine(JsonSerializer.Serialize(query));
}

>>> ["apple", "mango", "grape"]

延迟非流式处理执行

GroupBy 分组

对序列中的元素进行分组。

返回类型:IEnumerable,执行方式:延迟非流式处理执行

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public static void Main() {
    var people = new List<Person>
    {
        new Person { Name = "Alice", Age = 30 },
        new Person { Name = "Bob", Age = 25 },
        new Person { Name = "Charlie", Age = 30 },
        new Person { Name = "Dave", Age = 25 }
    };

    var groupedByAge = people.GroupBy(p => p.Age);

    foreach (var group in groupedByAge)
    {
        Console.WriteLine($"Age: {group.Key}");
        foreach (var person in group)
        {
            Console.WriteLine($"  {person.Name}");
        }
    }
}

>>> Age: 30
      Alice
      Charlie
    Age: 25
      Bob
      Dave
OrderBy 排序

按升序对序列的元素进行排序。

返回类型:IOrderedEnumerable,执行方式:延迟非流式处理执行

public static void Main() {
    var numbers = new List<int> { 5, 2, 8, 1, 4 };

	var sortedNumbers = numbers.OrderBy(n => n);
    
    Console.WriteLine(JsonSerializer.Serialze(sortedNumbers));
}

>>> [1, 2, 4, 5, 8]
OrderByDescending 排序降序

按降序对序列的元素排序。

返回类型:IOrderedEnumerable,执行方式:延迟非流式处理执行

public static void Main() {
    var numbers = new List<int> { 5, 2, 8, 1, 4 };

	var sortedNumbers = numbers.OrderByDescending(n => n);
    
    Console.WriteLine(JsonSerializer.Serialze(sortedNumbers));
}

>>> [8, 5, 4, 2, 1]
Reverse 逆转

反转序列中元素的顺序。

返回类型:IEnumerable,执行方式:延迟非流式处理执行

public static main() {
    list<int> list = new List<int>() {1, 2, 3, 4};
    Console.WriteLine(JsonSerializer.Serialize(Enumerable.Reverse(list)));
}

>>>[4, 3, 2, 1]
ThenBy 次要排序

按升序对序列中的元素执行后续排序。

返回类型:IOrderedEnumerable,执行方式:延迟非流式处理执行

public static void Main() {
    string[] fruits = { "grape", "passionfruit", "banana", "mango",
                      "orange", "raspberry", "apple", "blueberry" };

    IEnumerable<string> query =
        fruits.OrderBy(fruit => fruit.Length).ThenBy(fruit => fruit);
    
    Console.WriteLine(JsonSerializer.Serialize(query));
}

>>> ["apple", "grape", "mango", "banana", "orange", "blueberry", "raspberry", "passionfruit"]
ThenByDescending 次要排序降序

按降序对序列中的元素执行后续排序。

返回类型:IOrderedEnumerable,执行方式:延迟非流式处理执行

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var people = new List<Person>
{
    new Person { Name = "Alice", Age = 30 },
    new Person { Name = "Bob", Age = 25 },
    new Person { Name = "Charlie", Age = 30 },
    new Person { Name = "Dave", Age = 25 }
};

// 按 Age 升序排序,如果 Age 相同,再按 Name 降序排序
var sortedPeople = people
    .OrderBy(p => p.Age)  // 第一个排序条件:Age 升序
    .ThenByDescending(p => p.Name);  // 第二个排序条件:Name 降序

foreach (var person in sortedPeople)
{
    Console.WriteLine($"{person.Name}, Age: {person.Age}");
}

>>> Bob, Age: 25
    Dave, Age: 25
    Alice, Age: 30
    Charlie, Age: 30

延迟的流式处理执行和延迟非流式处理执行

Except 不包括

生成两个序列的差集。

返回类型:IEnumerable,执行方式:延迟的流式处理执行、延迟非流式处理执行

public static Main() {
    List<int> list1 = new List<int>() {1, 2, 3, 4, 5, 6};
    List<int> list2 = new List<int>() {5, 6, 7, 8, 9, 10};
    var result = list1.Excpet(list2);
}
>>> {1,2,3,4}
GroupJoin 连接后分组

基于键值等同性将两个序列的元素进行关联,并对结果进行分组。

返回类型:IEnumerable,执行方式:延迟的流式处理执行、延迟非流式处理执行

Intersect 相交

生成两个序列的交集。

返回类型:IEnumerable,执行方式:延迟的流式处理执行、延迟非流式处理执行

public static Main() {
    List<int> list1 = new List<int>() {1, 2, 3, 4, 5, 6};
    List<int> list2 = new List<int>() {5, 6, 7, 8, 9, 10};
    var result = list1.Intersect(list2);
}
>>> {5,6}
Join 连接

基于匹配键对两个序列的元素进行关联。

返回类型:IEnumerable,执行方式:延迟的流式处理执行、延迟非流式处理执行

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Order
{
    public int UserId { get; set; }
    public string Product { get; set; }
}

var users = new List<User>
{
    new User { Id = 1, Name = "小王" },
    new User { Id = 2, Name = "小李" },
    new User { Id = 3, Name = "小张" }
};

var orders = new List<Order>
{
    new Order { UserId = 1, Product = "手机" },
    new Order { UserId = 2, Product = "电脑" },
    new Order { UserId = 3, Product = "电视" },
    new Order { UserId = 1, Product = "耳机" }
};

// 使用 Join 连接 users 和 orders
var query = users.Join(
    orders,                    // 第二个集合是 orders
    user => user.Id,           // 用户集合中的连接键是 Id
    order => order.UserId,     // 订单集合中的连接键是 UserId
    (user, order) => new       // 连接后创建新对象
    {
        UserName = user.Name,
        Product = order.Product
    }
);

foreach (var item in query)
{
    Console.WriteLine($"{item.UserName} 购买了 {item.Product}");
}

>>> 小王 购买了 手机
    小王 购买了 耳机
    小李 购买了 电脑
    小张 购买了 电视
Zip 合并

用于将两个集合按顺序 合并 成一个新的集合。它会将两个集合的元素按索引一一对应地组合成一个新的结果集。如果两个集合的长度不同,Zip 会使用较短集合的长度进行匹配,多余的元素会被忽略。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public static void Main() {
        var list1 = new List<string> { "John", "Jane", "Jake" };
    var list2 = new List<int> { 28, 32, 35 };

    var zipped = list1.Zip(list2, (name, age) => new Person { Name = name, Age = age });

    foreach (var person in zipped)
    {
        Console.WriteLine($"{person.Name}, {person.Age}");
    }
}
>>> John, 28
    Jane, 32
    Jake, 35