C#基础--Lambda和LINQ

C#基础–Lambda 和 LINQ

一、Lambda 的前世今生

在.NetFramewok的不同版本都有不同的样子;在.NetCore下也都是支持的;

1.1 .Netframework1.0/1.1时代

public delegate void NoReturnWithPara(int x, string y);

NoReturnWithPara method = new NoReturnWithPara(Study);
method.Invoke(123, "Richard");

private void Study(int id, string name)
{
    Console.WriteLine($"{id} {name} 学习.Net高级班");
}

1.2 .NetFramework2.0

匿名方法 增加了一个delegate关键字, 可以访问到除了参数以外的局部变量

public delegate void NoReturnWithPara(int x, string y);

int i =0;
NoReturnWithPara method = new NoReturnWithPara(delegate (int id, string name)
{
	Console.WriteLine($"{id} {name} 学习.Net高级班");
	Console.WriteLine(i);	//可以访问到除了参数以外的局部变量 i
});
method.Invoke(123, "Richard");

1.3 .NetFramework3.0 前期

去掉了delegate关键字,添加了一个=> goes to

public delegate void NoReturnWithPara(int x, string y);

int i =0;
NoReturnWithPara method = new NoReturnWithPara((int id, string name) =>
{
	Console.WriteLine($"{id} {name} 学习.Net高级班");
	Console.WriteLine(i);
});
method.Invoke(123, "Richard");

1.4 .NetFramework3.0 后期

去掉了匿名方法后的参数类型,编译器自动推断来的/编译器提供的便捷功能(语法糖)

public delegate void NoReturnWithPara(int x, string y);

int i =0;
NoReturnWithPara method = new NoReturnWithPara((id, name) =>  //编译器自动推断来的/编译器提供的便捷功能(语法糖)
{
	Console.WriteLine($"{id} {name} 学习.Net高级班");
	Console.WriteLine(i);
});
method.Invoke(123, "Richard");

如果匿名方法体中只有一行代码,可以省略方法体的大括号:

public delegate void NoReturnWithPara(int x, string y);

NoReturnWithPara method = new NoReturnWithPara((id, name) => Console.WriteLine($"{id} {name} 学习.Net高级班"));
method.Invoke(123, "Richard");
public delegate void NoReturnWithPara(int x, string y);

NoReturnWithPara method = (id, name) => Console.WriteLine($"{id} {name} 学习.Net高级班");
method.Invoke(123, "Richard");

如果只有一个参数的时候:

Action<string> method = sn => Console.WriteLine($"欢迎{sn} 来到.Net高级班进阶学习");
method.Invoke("牧羊人");

如果有返回值? 如果lambda表达式中只有一行代码,且有返回值,可以省略return;

Func<string> func0 = () => { return "黄大仙" };

//如果有返回值? 如果lambda表达式中只有一行代码,且有返回值,可以省略 `大括号` + `return`;
Func<string> func = () => "黄大仙";
Func<int, string> func1 = i => i.ToString();

大家觉得Lambda表达式本质是什么?

多播委托中可以把Lambda表达式+=,但是不能把Lambda表达式-=。因为Lambda表达式其实是一个方法,不同的lambda表达式就是不同的方法。

Lambda的本质是一个方法。

语法糖:编译器提供的便捷功能

二、LINQ

初始化数据

List<Student> studentList = new List<Student>()
{
    new Student() { Id=1, Name="赵亮", ClassId=2, Age=35 },
    new Student() { Id=2, Name="再努力一点", ClassId=2, Age=23 },
    new Student() { Id=3, Name="王炸", ClassId=2, Age=27 },
    new Student() { Id=4, Name="疯子科学家", ClassId=2, Age=26 },
    new Student() { Id=5, Name="灭", ClassId=2, Age=25 },
    new Student() { Id=6, Name="黑骑士", ClassId=2, Age=24 },
    new Student() { Id=7, Name="故乡的风", ClassId=2, Age=21 },
    new Student() { Id=8, Name="晴天", ClassId=2, Age=22 }
};

2.1 Linq 扩展方法&表达式

var list = studentList.Where<Student>(s => s.Age < 30); //list里面必然是符合要求的数据;
var list = from s in studentList
			where s.Age < 30
			select s;   //list里面必然是符合要求的数据;

以上两种都是LINQ。

2.2 linq to object

Linq – Linq to object:就是针对IEnumerable类型数据

​ 1. IEnumerable 类型数据:可以理解为内存中的数据;其实就是把不变的逻辑封装起来,把可变的逻辑封装成委托来传递;

​ 2. IQueryable 类型数据:可以理解成内存数据—来自于数据库的数据;

延伸:

Linq to Sql:就是把打开数据库连接,查询数据(不变的的逻辑),把sql 的拼装(可变的逻辑)

Linq to Xml:把数据解析这类动作封装起来(不变的逻辑), 把数据筛选条件封装成委托来传递(可变的逻辑)

Linq to Redis:把固定的逻辑封装起来,把不变的逻辑封装成委托传递

Linq to Cache:…

Linq to JSON:…

Linq to Everything:…

IQueryable 和 IEnumerable 存在本质的区别:

  1. 使用IQueryable 查询
List<Student> studentList = this.GetStudentList();

var query = studentList.AsQueryable();
query.Where(a => a.Age < 30);

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
  1. 使用 IEnumerable 查询
List<Student> studentList = this.GetStudentList();

var query = studentList.AsEnumerable();
query.Where(a => a.Age < 30);

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

IEnumerable 传递的是一个委托,而 IQueryable 传递的是一个表达式目录树(数据结构)

现在大家觉得Linq 是什么?

Linq 是一种封装思想,对于用户来说,就把不需要关心内部怎么实现,只需要Linq一下即可;微软之前很推崇这种思想,只让开发者关注自己的核心逻辑,不需要关注内部逻辑,彻底的把开发者变成编程小白;如果大家有经验的话,你会发现像Asp .Net MVC就属于傻瓜式开发,成套的;我们不用去关心原理就可以直接上手。

但是现在又变了,现在有更高的要求,像是Linq to JSON、Linq to Redis等并没有人来封装这些。如果大家有兴趣,可以去尝试封装一下。

2.3 基本查询

var list = studentList.Where<Student>(s => s.Age < 30)
                                     .Select(s => new
                                     {
                                         IdName = s.Id + s.Name,
                                         ClassName = s.ClassId == 2 ? "高级班" : "其他班"
                                     });
var list = from s in studentList
            where s.Age < 30
            select new
			{
                IdName = s.Id + s.Name,
                ClassName = s.ClassId == 2 ? "高级班" : "其他班"
            };

Select 方法:

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

**备注:**使用 studentList.Where(s => s.Age < 30) 查询并返回数据后,实际内容还是一个 IEnumerable 的数据。但是接着再使用了Select 做投影的话,里面创建了一个匿名对象,返回的也是一个匿名对象。

2.4 分页

var list = studentList.Where<Student>(s => s.Age < 30)//条件过滤
                                     .Select(s => new//投影
                                     {
                                         Id = s.Id,
                                         ClassId = s.ClassId,
                                         IdName = s.Id + s.Name,
                                         ClassName = s.ClassId == 2 ? "高级班" : "其他班"
                                     })
                                     .OrderBy(s => s.Id)//排序
                                     .ThenBy(s=>s.ClassName)    //多重排序,可以多个字段排序都生效
                                     .OrderByDescending(s => s.ClassId)//倒排
                                     .Skip(2)//跳过几条
                                     .Take(3)//获取几条
                                     ;

性能怎样?=> 本质其实都是循环;

2.5 分组

var list = from s in studentList
            where s.Age < 30
            group s by s.ClassId into sg
            select new
            {
                key = sg.Key,
                maxAge = sg.Max(t => t.Age)
            };
var list = studentList.Where(a=>a.Age<30)
    					.GroupBy(c => c.ClassId)
    					.Select(sg => new
                		{
                            key = sg.Key,
                            maxAge = sg.Max(t => t.Age)
                        });

2.6 内连接 – inner join

var list = from s in studentList
            join c in classList on s.ClassId equals c.Id
            select new
            {
                Name = s.Name,
                CalssName = c.ClassName
            };
var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new
                {
                    Name = s.Name,
                    CalssName = c.ClassName
                });

2.7 左连接 – left join

var list = from s in studentList
   		join c in classList on s.ClassId equals c.Id
   		into scList
           from sc in scList.DefaultIfEmpty()
           select new
           {
               Name = s.Name,
               CalssName = sc == null ? "无班级" : sc.ClassName//c变sc,为空则用
           };
var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new
                {
                    Name = s.Name,
                    CalssName = c.ClassName
                }).DefaultIfEmpty();

三、自定义封装

3.1 针对一个具体类的封装

/// <summary>
/// 如果换个条件怎么办?
/// 使用委托:委托可以把方法当做参数传递;方法其实是逻辑,委托可以把逻辑当做参数传递;
/// 委托:应该是返回值为bool的委托,参数是一个Student
/// </summary>
/// <param name="resource"></param>
/// <returns></returns>
public static List<Student> RichardWhere(this List<Student> resource, Func<Student, bool> func)
{
    List<Student> list = new List<Student>();
    foreach (var item in resource)
    {
        //if (item.Name.Length > 2) //条件的判断其实就是逻辑---动作 //唯一的区别就在与中间这里的条件不一样;
        if (func.Invoke(item))
        {
            list.Add(item);
        }
    }
    return list;
}

3.2 通用–泛型封装

/// <summary>
/// 这就是Linq中where的本质;
/// 1. 是把固定不变的逻辑,封装起来,把可变的逻辑封装成委托来传递
/// 就可以让开发者只需要关注自己的核心业务,其他别的都以LINQ封装
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static List<T> RichardWhere<T>(this List<T> resource, Func<T, bool> func) where T:class
{
    List<T> list = new List<T>();
    foreach (var item in resource)
    {
        //if (item.Name.Length > 2) //条件的判断其实就是逻辑---动作 //唯一的区别就在与中间这里的条件不一样;
        if (func.Invoke(item))
        {
            list.Add(item);
        }
    }
    return list;
}
/// <summary>
/// 有什么好处?
/// 通过接口来扩展,只要实现了这个接口的,都可以使用当前这个扩展方法
/// 相比而言:自然扩展抽象要好一些,扩展性更好
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>

public static IEnumerable<T> RichardWhereEnumerable<T>(this IEnumerable<T> resource, Func<T, bool> func) where T : class
{
    List<T> list = new List<T>();
    foreach (var item in resource)
    {
        //if (item.Name.Length > 2) //条件的判断其实就是逻辑---动作 //唯一的区别就在与中间这里的条件不一样;
        if (func.Invoke(item))
        {
            list.Add(item);
        }
    }
    return list;
}

3.3 实际运用

如果是学生类的实现对象的话,3.1 和3.2都是的话下面代码一样,无需任何修改

var list1 = MethodExtension.RichardWhere(studentList, item => item.Age < 30);
var list2 = studentList.RichardWhere(item => item.Age < 30);

var list1 = MethodExtension.RichardWhere(studentList, item => item.Name.Length > 2);
var list2 = studentList.RichardWhere(item => item.Name.Length > 2);

var list1 = MethodExtension.RichardWhere(studentList, item => item.Id > 1
                                         && item.Name != null
                                         && item.ClassId == 1
                                         && item.Age > 20);
//循环完毕以后,list里面必然是符合要求的数据;
var list2 = studentList.RichardWhere<Student>(item => item.Id > 1
                                              && item.Name != null
                                              && item.ClassId == 1
                                              && item.Age > 20);

四、yield 使用

/// <summary>
/// yield` 必须和 IEnumerable<T> 配合使用,可以做到按需获取
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IEnumerable<T> RichardWhereIterator<T>(this IEnumerable<T> resource, Func<T, bool> func) where T : class
{
    foreach (var item in resource)
    {
        if (func.Invoke(item))
        {
            yield return item;
        }
    }
    return list;
}

var list3 = studentList.RichardWhereIterator(item => item.Age < 30);
foreach (var item in list3)
{
    Console.WriteLine("Name={0}  Age={1}", item.Name, item.Age);
}

yield 必须和 IEnumerable<T> 配合使用,可以做到按需获取;如果返回值换成 List<T> 就会产生报错

按需获取: var list3 = studentList.RichardWhereIterator(item => item.Age < 30); 执行完之后,并没有实质性执行到 RichardWhereIterator 方法内部,直到 foreach (var item in list3) 的时候,才执行到方法体内,而且是循环一次就到 RichardWhereIterator 获取一条数据。

image-20220313181704872

使用yield 的使用,编译器会生成一个泛型的,会有个迭代状态机 [IteratorStateMachine] (属于特性)