.NET-LINQ

.NET-LINQ

enumerable中文释义 :可数的,可枚举的

委托

.NET框架提供了两个委托ActionFunc

它们分别用于表示没有返回值的方法和有返回值的方法

Action 是一个没有返回值的委托类型,它可以接受零个到十六个参数。常见的形式如下:

  • Action:无参数,无返回值。
  • Action<T>:一个参数,无返回值。
  • Action<T1, T2>:两个参数,无返回值。
  • Action<T1, T2, T3>:三个参数,无返回值。
  • ...以此类推,最多支持十六个参数。

Func 是一个有返回值的委托类型,它可以接受零个到十六个参数,并且必须返回一个值。常见的形式如下:

  • Func<TResult>:无参数,有返回值。
  • Func<T, TResult>:一个参数,有返回值。
  • Func<T1, T2, TResult>:两个参数,有返回值。
  • Func<T1, T2, T3, TResult>:三个参数,有返回值。
  • ...以此类推,最多支持十六个参数。

Lambda

委托指向的方法不止可以是普通方法,还可以是匿名方法

// 有返回值的匿名方法委托
Func<int, int, int> fun1 = delegate(int x, int y)
{
    return x + y;
};

// 无返回值的匿名方法委托
Action<int, string> action = delegate(int age, string name)
{
    Console.WriteLine($"age:{age},name:{name}");
};

使用Lambda表达式改写之后为

Func<int, int, int> fun1 = (int x, int y) => x + y;
Action<int, string> action = (int age, string name) => { Console.WriteLine($"age:{age},name:{name}"); };

LINQ的原理

让我们来手搓一个LINQ的WHERE方法

namespace LINQ
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 建立一个数组
            int[] nums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            
            // 调用MyWhere方法,传入数组,通过委托传入一个匿名方法(lambda)
            var enumerable = MyWhere(nums, (num) => num > 5);
            
            // 遍历结果集
            foreach (var i in enumerable)
            {
                Console.WriteLine(i);
            }
        }

        // 在这里 我们自定义了一个类Where的功能
        // 第一个参数为items ,为被传入的集合
        // 第二个参数为.NET提供的委托,Func f;其中一个参数为int,返回值为布尔类型
        static IEnumerable<int> MyWhere(IEnumerable<int> items, Func<int, bool> f)
        {
            //实例化一个List用来保存查询结果,注意,List集合也是IEnumerable接口的实现类
            List<int> result = new List<int>();
            ;
            // 遍历items
            foreach (var item in items)
            {
                // 将items传入委托方法做判断,如果委托方法返回为true,则将此项压入结果集
                if (f(item))
                {
                    result.Add(item);
                }
            }

            // 返回结果集
            return result;
        }
    }
}

小小的改写
static IEnumerable<int> MyWhere(IEnumerable<int> items, Func<int, bool> f)
{
    // 遍历items
    foreach (var item in items)
    {
        if (f(item))
        {   
            // 在这里我们使用yield的方式来返回结果集,这也就无需实例化一个list集合作为结果集了
            yield return item;
        }
    }
}

LINQ的方法

LINQ是IEnumerable的扩展方法,其目的是为了简化数据处理,大部分都在System.Linq命名空间中

也正因为这一特点,所以LINQ可应用于IEnumerable的子类:数组、List、Dictionary、Set……

LINQ很多方法的参数是一个委托方法,即传入一个返回值为布尔类型的匿名函数,作为操作的条件

LINQ的返回值多有变化,所以可以使用C#的类型推断特性(var)来简化类型接收


实体类和数据
record Employee
{
    public long Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public bool Gender { get; set; }
    public int Salary { get; set; }
}

static void Main(string[] args)
{
    List<Employee> list = new List<Employee>();

    list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
    list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
    list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
    list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
    list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
    list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
    list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
    list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 8000 });
}

Where方法

Where方法的返回值是IEnumerable<T>

// 检索年龄大于30并且工资大于8000的所有员工信息
var enumerable = list.Where(e => e.Age > 30 && e.Salary > 8000);
// 遍历结果集
foreach (var employee in enumerable)
{
    Console.WriteLine(employee);
}

Count方法

Count方法的返回值是一个int值

统计满足条件结果集的个数

// 统计list集合中所有条数
Console.WriteLine(list.Count()); // 8
// 统计list集合中所有满足条件的员工信息的个数
Console.WriteLine(list.Count(e => e.Age > 30)); // 5

Any方法

Any方法的返回值是一个布尔值

判断结果中是否至少存在一条满足条件的员工信息

Console.WriteLine(list.Any(e => e.Salary > 9000)); // false
Console.WriteLine(list.Any(e => e.Salary >= 9000)); // true

Single方法

有且只有一条满足要求的数据(其他情况都报错)

var single = list.Single(e=> e .Name == "jerry");
SingleOrDefault方法

最多只有一条满足要求的数据(1条或者0条,如果大于1条则报错)

First方法

至少有一条,返回第一条(大于1条,如果0条则报错)

FirstOrDefault方法

返回第一条或默认值


Order方法

对简单类型数据正序排序

OrderByDescending方法

对数据按照一定规则进行倒序排序

OrderBy方法

对数据按照一定规则进行正序排序

需要注意的是,所有的排序方法的返回值都是IOrderedEnumerable对象,而它也是IEnumerable的接口

// 对工资进行升序排序
var orderedEnumerable = list.OrderBy(e => e.Salary);
foreach (var employee in orderedEnumerable)
{
    Console.WriteLine(employee);
}

// 对年龄进行降序排序
var orderedEnumerable = list.OrderByDescending(e => e.Age);
foreach (var employee in orderedEnumerable)
{
    Console.WriteLine(employee);
}
多规则排序

使用链式调用.ThenBy()和.ThenByDescending()方法来对数据进行多规则排序

// 对工资进行正序排序,如有相同,再对年龄进行倒序排序
var orderedEnumerable = list.OrderBy(e => e.Salary)
    .ThenByDescending(e => e.Age);

Skin(n)&Take(n)方法

使用skin来跳过n条数据,使用take来获取n条数据(可以链式调用)

一般使用这两个方法来实现分页查询


聚合函数

LINQ中所有的扩展方法几乎都是针对IEnumerable接口的,而几乎所有能返回集合的都返回IEnumerable,所以是可以把几乎所有方法都进行链式调用的

函数名说明
Max()取最大值
Min()取最小值
Average()取平均值
Sum()取和
Count()取数量

分组

使用GroupBy对数据进行指定项的分组

不难看出,最后的分组结果是以键值对的方式存在一个集合中,key为分组的指定项,value为被分组的数据

List<Map<int,List<Employee>>>

IEnumerable<IGrouping<int, Employee>> groupBy = list.GroupBy(e => e.Age);
// 遍历大集合
foreach (var employeese in groupBy)
{
    // 打印key
    Console.WriteLine(employeese.Key);
    
    // 输出每一组的最大工资
    Console.WriteLine($"最大工资:{employeese.Max(e => e.Salary)}");
    
    // 循环遍历value中内容
    foreach (var employee in employeese)
    {
        Console.WriteLine(employee);
    }

    Console.WriteLine("****************");
}

Select方法

        // 将投影的列额外的封装成对象
        IEnumerable<User> enumerable = list.Select(e => new User()
        {
            name = e.Name,
            age = e.Age,
        });
        foreach (var user in enumerable)
        {
            Console.WriteLine($"姓名:{user.name},年龄:{user.age}");
        }
class User
{
    public string name;
    public int age;
}
匿名类型

C# 中的匿名类型是一种没有名称的类,可以在程序中临时创建对象而无需显式定义类

匿名类型主要用于简化代码,特别是在需要快速创建一个对象并立即使用该对象的情况下

var people = new { Name = "xiaobai", age = "18" };

在Java中,我们连接数据库查询到的数据除了使用DTO封装好的类型进行包装之外,还可以使用Map集合来临时包装这些数据然后将其返回

在C#中,我们可以使用匿名类型和类型推断来完成对数据临时的封装

匿名类型与投影
var enumerable = list.Select(e => new
{
  Name = e.Name,
  Age = e.Age,
});

集合转换

LINQ语句的返回值大多都是IEnumerable接口的数据类型

但实际开发中,我们需要结果为List<T>或者数组类型

可以通过ToArray()方法和ToList()方法来进行集合转换


查询语法

查询表达式语法是一种更接近 SQL 语法的形式

它使用 fromwhereselect 等关键字来构建查询。这种语法通常更易于阅读和理解,特别是对于熟悉 SQL 的开发者来说

// 方法语法
var employees = list.Where(e => e.Salary > 3000);
foreach (var employee in employees)
{
    Console.WriteLine(employee);
}        

//查询表达式语法
var enumerable = from e in list
    where e.Salary > 3000
    select e;
foreach (var employee in enumerable)
{
    Console.WriteLine(employee);
}