C# Linq语法

介绍

Linq(Language Integrated Query),语言集成查询,是一种使用 类似SQL语句 操作多种数据源的功能。

从.net framework3.5中开始引入,能够提升程序数据处理能力和开发效率,具有集成性、统一性、可扩展性、抽象性、说明式编程、可组成型、可转换性等优势。

支持的数据源

包括但不限于:

  • 内存中的集合(如 List,Array,SortedSet,Stack,Queue 等)
  • 数据库(如SQL Server)
  • XML文档
  • Web服务

类比 Java 的 stream

类似 Java 的 stream,但功能更强大

实现方式

有两种:

  • 方法查询。类似 Java 的 stream,通过集合的 扩展方法 和 lambda表达式 实现

  • 查询表达式语法,类似 SQL 语句(某些功能不支持,需要方法查询)

原理: 标准查询运算符是一组 扩展方法,它们提供了一种 函数式 的方式来构建查询。这些方法定义在 System.Linq.Enumerable 类中,适用于所有实现了 IEnumerable<T> 接口的集合。

前提条件

使用 LINQ 方法语法需满足:

  • 引用 System.Linq 命名空间(默认在 C# 项目中已包含)。
  • 数据源需实现 IEnumerable<T> 或 IQueryable<T> 接口(如 List<T>、Array、数据库查询结果等)。

方法查询

方法语法(也称为流利语法)主要利用 System.Linq.Enumerable 类中定义的扩展方法和 Lambda表达式 方式进行查询,类似于如何调用任何类的扩展方法

例子

以下是一个示例LINQ方法语法的查询,返回数组中的偶数:

var nums = new int[] { 56, 97, 98, 57, 74, 86, 31, 90 };

var result = nums.Where(p => p % 2 == 0).ToArray();

从上面的示例代码中可以看出:方法语法包括扩展方法和Lambda表达式。 扩展方法 Where()在Enumerable类中定义。

准备数据

using System;
using System.Collections.Generic;
using System.Linq;

// 定义实体类
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Major { get; set; }
    public double Score { get; set; }
}

// 数据源:学生列表
List<Student> students = new List<Student>
{
    new Student { Id = 1, Name = "张三", Age = 20, Major = "计算机", Score = 85.5 },
    new Student { Id = 2, Name = "李四", Age = 22, Major = "数学", Score = 92.0 },
    new Student { Id = 3, Name = "王五", Age = 19, Major = "计算机", Score = 78.0 },
    new Student { Id = 4, Name = "赵六", Age = 21, Major = "数学", Score = 88.5 },
    new Student { Id = 5, Name = "钱七", Age = 20, Major = "计算机", Score = 90.0 }
};

过滤 Where

作用:筛选出符合条件的元素,返回 IEnumerable<T>。
参数:Lambda 表达式(Func<TSource, bool>),定义筛选条件。

// 示例:查询“计算机”专业的学生
var csStudents = students.Where(s => s.Major == "计算机");

// 遍历结果
foreach (var s in csStudents)
{
    Console.WriteLine($"{s.Id}: {s.Name} ({s.Major})");
}
// 输出:
// 1: 张三 (计算机)
// 3: 王五 (计算机)
// 5: 钱七 (计算机)

投影 Select

作用:将元素转换为新的形式(如提取部分字段、转换类型),返回 IEnumerable<TResult>。
参数:Lambda 表达式(Func<TSource, TResult>),定义转换规则。

// 示例:提取学生的“姓名”和“分数”,转换为匿名类型
var nameAndScores = students.Select(s => new { s.Name, s.Score });

foreach (var item in nameAndScores)
{
    Console.WriteLine($"{item.Name}: {item.Score}");
}
// 输出:
// 张三: 85.5
// 李四: 92.0
// ...(省略其他)

排序(OrderBy / OrderByDescending)

作用:对元素按指定字段排序,OrderBy 升序,OrderByDescending 降序。
参数:Lambda 表达式(Func<TSource, TKey>),指定排序字段。

// 示例:按分数降序排列所有学生
var sortedByScore = students.OrderByDescending(s => s.Score);

foreach (var s in sortedByScore)
{
    Console.WriteLine($"{s.Name}: {s.Score}");
}
// 输出:
// 李四: 92.0
// 钱七: 90.0
// 张三: 85.5
// 赵六: 88.5(注意:88.5 < 90.0,所以在钱七之后)
// 王五: 78.0

限制结果(Take / Skip)

  • Take(n):获取前 n 个元素。
  • Skip(n):跳过前 n 个元素,获取剩余元素。
// 示例:取分数最高的前2名学生
var top2 = students.OrderByDescending(s => s.Score).Take(2);

foreach (var s in top2)
{
    Console.WriteLine($"{s.Name}: {s.Score}");
}
// 输出:
// 李四: 92.0
// 钱七: 90.0

聚合(Count / Sum / Average / Max / Min)

作用:对集合进行统计计算(立即执行,非延迟执行)。

// 示例:统计计算机专业学生的数量、平均年龄、最高分数
int csCount = students.Where(s => s.Major == "计算机").Count();
double avgAge = students.Where(s => s.Major == "计算机").Average(s => s.Age);
double maxScore = students.Where(s => s.Major == "计算机").Max(s => s.Score);

Console.WriteLine($"计算机专业人数:{csCount},平均年龄:{avgAge},最高分数:{maxScore}");
// 输出:计算机专业人数:3,平均年龄:19.666...,最高分数:90.0

分组(GroupBy)

作用:按指定字段将元素分组,返回 IEnumerable<IGrouping<TKey, TSource>>。

// 示例:按专业分组,获取每个专业的学生
var groupByMajor = students.GroupBy(s => s.Major);

foreach (var group in groupByMajor)
{
    Console.WriteLine($"专业:{group.Key},人数:{group.Count()}");
    foreach (var s in group)
    {
        Console.WriteLine($"  {s.Name}");
    }
}
// 输出:
// 专业:计算机,人数:3
//   张三
//   王五
//   钱七
// 专业:数学,人数:2
//   李四
//   赵六

连接(Join)

作用:类似 SQL 的 JOIN,根据两个集合的共同字段关联数据。

假设有第二个数据源(课程):

List<Course> courses = new List<Course>
{
    new Course { CourseId = 101, CourseName = "C#编程", Major = "计算机" },
    new Course { CourseId = 102, CourseName = "高等数学", Major = "数学" }
};

public class Course
{
    public int CourseId { get; set; }
    public string CourseName { get; set; }
    public string Major { get; set; }
}
// 示例:关联学生和课程(按专业),获取“学生姓名-专业-课程名”
var studentCourses = students.Join(
    courses,
    s => s.Major,    // 学生集合的关联字段
    c => c.Major,    // 课程集合的关联字段
    (s, c) => new {   // 关联后的结果
        StudentName = s.Name,
        Major = s.Major,
        CourseName = c.CourseName
    }
);

foreach (var item in studentCourses)
{
    Console.WriteLine($"{item.StudentName} - {item.Major} - {item.CourseName}");
}
// 输出:
// 张三 - 计算机 - C#编程
// 王五 - 计算机 - C#编程
// 钱七 - 计算机 - C#编程
// 李四 - 数学 - 高等数学
// 赵六 - 数学 - 高等数学

方法链(Method Chaining)

LINQ 方法语法支持链式调用,将多个方法组合成一个查询,可读性和灵活性更高。

// 示例:查询计算机专业中年龄>19、分数>80的学生,按分数升序,取前2名,只显示姓名和分数
var result = students
    .Where(s => s.Major == "计算机" && s.Age > 19 && s.Score > 80)  // 过滤
    .OrderBy(s => s.Score)  // 排序
    .Take(2)  // 限制数量
    .Select(s => new { s.Name, s.Score });  // 投影

foreach (var item in result)
{
    Console.WriteLine($"{item.Name}: {item.Score}");
}
// 输出:
// 张三: 85.5
// 钱七: 90.0

延迟执行 vs 立即执行

  • 延迟执行:Where、Select、OrderBy、GroupBy 等方法不会立即执行,仅定义查询规则,直到枚举结果时才执行(如 foreach、ToList())。
    优点:可组合多个查询步骤,最终一次性执行,提高效率。

  • 立即执行:Count()、Sum()、ToList()、ToArray() 等方法会立即执行查询并返回结果。

// 延迟执行:此时未执行查询
var query = students.Where(s => s.Age > 20);

// 立即执行:调用 ToList() 时执行查询
var list = query.ToList();

总结

LINQ 方法语法通过扩展方法和 Lambda 表达式,提供了简洁、灵活的查询能力,核心优势:

  1. 统一查询语法:对各种数据源(集合、数据库、XML 等)使用相同的查询方式。
  2. 类型安全:编译时检查查询语法和类型,减少运行时错误。
  3. 可组合性:通过方法链组合多个操作,实现复杂查询。

掌握常用方法(Where、Select、OrderBy、GroupBy 等)及其组合使用,是高效开发 .NET 应用的基础。

查询表达式语法

类似 SQL 语句(某些功能不支持,需要方法查询)

常见的标准查询运算符包括:

  • Where:过滤集合中的元素。
  • Select:投影每个元素。
  • OrderBy 和 OrderByDescending:按升序或降序排序。
  • GroupBy:根据键值对元素进行分组。
  • Join:将两个集合基于键值进行关联。
  • Any 和 All:检查集合是否满足某些条件。
  • Count 和 Sum:计算集合的大小或求和。

整体语法

from source in collection:指定查询的数据源。
where condition:指定筛选条件。
orderby key [descending]:指定排序条件。
select result:定义查询结果,一般以 select 结尾

基本查询

语法

from item in collection:指定查询的数据源
select item:定义查询结果

理解: from item in collection 类似 foreach( var item in collection )

例子

var nums = new int[] { 56, 97, 98, 57, 74, 86, 31, 90 };
//num为数据中的每个元素(相当于foreach中迭代变量)
var queryInfo = from num in nums select num;

foreach (var item in queryInfo)
{
    System.Diagnostics.Debug.WriteLine(item);
}

执行结果:

97
98
74
86
90

where条件查询

where子句用来指定筛选的条件

语法

from item in collection:指定查询的数据源。
where condition:指定筛选条件。
select item:定义查询结果

例子

var nums = new int[] { 56, 97, 98, 57, 74, 86, 31, 90 };
//num为数据中的每个元素(相当于foreach中迭代变量)
var queryInfo = from num in nums where num >60 select num;

foreach (var item in queryInfo)
{
    System.Diagnostics.Debug.WriteLine(item);
}

执行结果:

97
98
74
86
90

select查询列

select子句,基于查询结果返回需要的 字段,并能够对返回值指定类型。对任意想要获取到返回结果的linq语句,必须以 select 或 group 结束。

准备工作:

class Person
{
    public string Name { set; get; }
    public int Age { set; get; }
    public string Gender { set; get; }
    public override string ToString() => Name;
}

初始化 List:

static List<Person> init()
{
    List<Person> personList = new List<Person>
    {
        new Person
        {
            Name = "李雷", Age = 18, Gender = "Male"

        },
        new Person
        {
            Name = "张三", Age = 19, Gender = "Male",
        },
        new Person
        {
            Name = "韩梅梅", Age = 17,Gender = "Female",
        }
    };
    return personList;
}

查询一列

List<Person> list = init();

// 当 item 是对象时,可以只查询某个属性
var queryInfo = from item in list select item.Name;

foreach (var item in queryInfo)
{
    Console.WriteLine(item);
}

执行结果:

李雷
张三
韩梅梅

查询整个对象

List<Person> list = init();

// 当 item 是对象时,也可以查询 item
var queryInfo = from item in list select item;

foreach (var item in queryInfo)
{
    Console.WriteLine(item.Name+","+item.Age);
}

执行结果:

李雷,18
张三,19
韩梅梅,17

查询多列

List<Person> list = init();

// Select 后面跟匿名类,封装查询的字段
var queryInfo = from item in list select new { username = item.Name, uesrAge = item.Age };

foreach (var item in queryInfo)
{
    Console.WriteLine(item.username + ","+item.uesrAge);
}

执行结果:

李雷,18
张三,19
韩梅梅,17

order by

linq中语句默认展示为升序,降序使用 descending

语法

from item in collection:指定查询的数据源。
where condition:指定筛选条件。
orderby key [descending]:指定排序条件。
select 字段:定义查询结果,一般以 select 结尾

例子-升序

var scores = new int[] { 56, 97, 98, 57, 74, 86, 31, 90 };
var query = from item in scores orderby item select item;//升序

foreach (var item in query)
{
    Console.WriteLine(item);
}

例子-降序

//var orderQuery = from item in scores orderby item descending select item;//降序

group by

group by 子句用来对查询结果进行分组

  • 如果结果分为两组,且 未指定key 的情况下,key 取值默认是 true 和 false。
  • 如果分为多组,获取数据结果时需要手动遍历 key 获取对应的 value

语法

from item in collection:指定查询的数据源。
group item by item.属性

例子

List<Person> list = init();
// Select 后面跟匿名类,封装查询的字段
var queryInfo = from item in list
                group item by item.Gender;

foreach (var item in queryInfo)
{
    Console.WriteLine(item.Key);
}

group by 聚合查询

例子

List<Person> list = init();
// Select 后面跟匿名类,封装查询的字段
var queryInfo = from item in list
                group item by item.Gender into g
                select new {
                    Key = g.Key,
                    count = g.Count(),
                    maxAge = g.Max( x=> x.Age),
                    minAge = g.Min(x => x.Age),
                    sumAge = g.Sum(x => x.Age),
                    avgAge = g.Average(s => s.Age)
                };

foreach (var item in queryInfo)
{
    Console.WriteLine($"{item.Key}---总行数:{item.count},最大年龄:{item.maxAge},最小年龄:{item.minAge},年龄和:{item.sumAge},平均年龄:{item.avgAge}");
}

执行结果:

Male---总行数:2,最大年龄:19,最小年龄:18,年龄和:37,平均年龄:18.5
Female---总行数:1,最大年龄:17,最小年龄:17,年龄和:17,平均年龄:17

参考:
https://blog.csdn.net/m0_56259289/article/details/144134122
https://blog.csdn.net/qq_34550459/article/details/113540805
https://zhuanlan.zhihu.com/p/146747701