Linq--本质是公共方法抽象封装库
Linq的诞生--framework 3.0
从诞生阶段看,只是对公共方法做了封装,利用了泛型和委托,将具体计算的功能,放给了具体提供委托的外部,内部只保留一个调用外部方法的引用而已。
目录
一、linq存在的意义
二、linq诞生过程
思考
1、方案一:通过循环和if判断来筛选数据
2、方案二:扩展方法
(1)最初版本
(2)进化版本
(3)最终版本
观察
三、本文代码
一、linq存在的意义
linq的存在是为了对数据进行操作,诞生于dotnet framework 3.0。
二、linq诞生过程
思考
现在有一个Person集合,我们需要根据不同的条件筛选出对应的人员,又哪些方案?
1、方案一:通过循环和if判断来筛选数据
缺点:
代码量大,不够简洁,特别是遇到复杂条件之后,会很麻烦
随着条件的变更,每次都要重新定义循环,有冗余代码
/// <summary>
/// 从一个数据集中过滤数据的一般方法
/// </summary>
public void GeneralMethod()
{
//获取全部的人员数据
var person = new Person();
var personList = person.GetPersopnList();
{
Console.WriteLine("==================常规情况下过滤数据================");
{
//1、要求查询id小于3的人员
var resultList = new List<Person>();
//可以使用for循环遍历,将满足条件的随想添加到resultList集合中
foreach (var item in personList)
{
if (item.Id < 3)
{
resultList.Add(item);
}
}
//循环完成之后,resultList中就是满足条件的对象
}
{
//2、获取名字为三个字的人员
var resultList = new List<Person>();
foreach (var item in personList)
{
if (item.Name.Length == 3)
{
resultList.Add(item);
}
}
//循环完成后,resultList中就是满足条件的对象
}
{
//3、获取名字小于三个数,id大于2,年龄大于15的人员(复杂条件)
var resultList = new List<Person>();
foreach (var item in personList)
{
if (item.Id > 2
&& item.Name.Length < 3
&& item.Age > 15)
{
resultList.Add(item);
}
}
//循环完成后,resultList中就是满足条件的对象
}
//由上面三种可以看出,通过循环是可以得到想要的数据,但是不够简洁,特别是遇到复杂条件之后,会很麻烦
}
}
思考:那么针对上面两个问题,解决方案是:封装,增加一个扩展方法,见方案二。
2、方案二:扩展方法
(1)最初版本
优点:
调用方代码量就很少了
缺点:
该方法只能满足一种条件的过滤
/// <summary>
/// 封装一下之后,调用方的代码量就很少了,但是缺点也很明显
///
/// 该方法只能满足一种条件的过滤,那如果想更换条件怎么办呢?
/// </summary>
/// <param name="persons"></param>
/// <returns></returns>
public static List<Person> MyWhere(this List<Person> persons)
{
var resultList = new List<Person>();
//可以使用for循环遍历,将满足条件的随想添加到resultList集合中
foreach (var item in persons)
{
if (item.Id < 3)
{
resultList.Add(item);
}
}
return resultList;
}
思考:那如果想更换条件怎么办呢?—>继续进化
(2)进化版本
分析:
筛选数据,不管条件则么变化,for循环是必须的,唯一的区别就是过滤条件不同(逻辑不同)
逻辑即动作,动作即方法,因此逻辑及方法,因为委托可以把方法当作参数传递
那么就很好办了,我们就可以使用委托来解决这个问题
那么这个委托怎么定义呢?
观察过滤条件,如 if (item.Id < 3),无论是根据名称还是id过滤,都必须需要当前过滤的对象
判断条件的返回值是个bool类型
因此: 只需要传递一个参数为Person,返回值为bool的委托
优点:
逻辑解耦,更加灵活
缺点:
不够灵活,只能给某个特定的对象使用,不能通用
/// <summary>
/// 如何解决上面方法带来的问题呢?
///
/// 分析:
///
/// 1、筛选数据,不管条件则么变化,for循环是必须的,唯一的区别就是过滤条件不同(逻辑不同)
/// 2、逻辑即动作,动作即方法,因此逻辑及方法,因为委托可以把方法当作参数传递
/// 3、那么就很好办了,我们就可以使用委托来解决这个问题
///
/// 那么这个委托怎么定义呢?
/// 1、观察过滤条件,如 if (item.Id < 3),无论是根据名称还是id过滤,都必须需要当前过滤的对象
/// 2、判断条件的返回值是个bool类型
/// 因此: 只需要传递一个参数为Person,返回值为bool的委托
/// /// </summary>
/// <param name="persons"></param>
/// <returns></returns>
public static List<Person> MyWhere(this List<Person> persons, Func<Person, bool> func)
{
var resultList = new List<Person>();
//可以使用for循环遍历,将满足条件的随想添加到resultList集合中
foreach (var item in persons)
{
if (func.Invoke(item))
{
resultList.Add(item);
}
}
return resultList;
}
思考:如何做到通用呢?答案是:泛型方法
(3)最终版本
优点:
逻辑解耦
通用
/// <summary>
///
/// 最终形态:
///
/// 这就是linq 的where扩展方法的本质
/// 把固定不变的逻辑封装起来,把可变的逻辑封装成委托来传递的扩展方法
/// </summary>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static List<T> MyWhere<T>(this List<T> resource, Func<T, bool> func) where T : class
{
var resultList = new List<T>();
//可以使用for循环遍历,将满足条件的随想添加到resultList集合中
foreach (var item in resource)
{
if (func.Invoke(item))
{
resultList.Add(item);
}
}
return resultList;
}
调用:
/// <summary>
/// 执行扩展方法
/// </summary>
public void ExecExtensionMethod()
{
var person = new Person();
var personList = person.GetPersopnList();
var resultList = new List<Person>();
Console.WriteLine("===============通过自定义扩展方法完成上述的情况=================");
{
//1、查询id小于3的人员
//实例化一个委托
Func<Person, bool> func = s => s.Id < 3;
//调用扩展方法
resultList = personList.MyWhere(func);
//进一步简化
resultList = personList.MyWhere(s => s.Id < 3);
person.PrintList(resultList);
}
{
//2、获取名字小于三个数,id大于2,年龄大于15的人员
resultList = personList.MyWhere(p => p.Name.Length < 3 && p.Id > 2 && p.Age > 15);
person.PrintList(resultList);
}
}
#endregion
输出结果:
观察
仔细观察下面两幅截图,能得到什么结论?
(1)自定义MyWhere扩展方法
(2)linq的where扩展方法
不难得出结论:其实二者本质是一样的,没有任何的区别-------没错,其实linq的where扩展方法就是这么实现的,linq的扩展方法就是这样(通过泛型方法和委托将固定不变的逻辑封装起来,将可变的逻辑封装成委托传递)实现的。
linq高级实现
目录
一、接口实现Where扩展
二、yield 迭代器关键字
对比
本质
核心
三、linq中where的本质
四、本文代码
一、接口实现Where扩展
上一章我们最终得到的where扩展方法实现如图,但是还是有缺点:只支持List调用改扩展方法,那么能不能让它更加灵活呢?
当然可以:可以通过IEnumerable接口来扩展,这样做的好处:
通过接口来扩展,只要实现了这个接口的,都可以使用这个扩展(如List,HashSet等)
扩展性更好,更加通用
/// <summary>
///
/// 将原来的List<T>改为IEnumerable<T>
/// 优点是什么?
///
/// 1、通过接口来扩展,只要实现了这个接口的,都可以使用这个扩展
/// 2、扩展性更好,更加通用
///
/// </summary>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IEnumerable<T> WhereEnumerable<T>(this IEnumerable<T> resource, Func<T, bool> func) where T : class
{
var resultList = new List<T>();
//可以使用for循环遍历,将满足条件的随想添加到resultList集合中
foreach (var item in resource)
{
if (func.Invoke(item))
{
resultList.Add(item);
}
}
return resultList;
}
思考:还能优化吗?
二、yield 迭代器关键字
yield关键字必须与IEnumerable配合使用,二者密不可分
按需取用:找到一个,取出并用一个,然后再找下一个
/// <summary>
///
/// yield关键字必须与IEnumerable<T>配合使用,二者密不可分
///
/// 优点是什么?
///
/// 1、按需取用:找到一个,取出并用一个,然后再找下一个
///
/// </summary>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IEnumerable<T> WhereIterator<T>(this IEnumerable<T> resource, Func<T, bool> func) where T : class
{
foreach (var item in resource)
{
Console.WriteLine("***************开始yield*****************");
if (func.Invoke(item))
{
yield return item;
}
}
}
对比
我们再循环里都打印一句话,如图:
调用
观察结果:
WhereEnumerable方法是循环完成之后将结果返回
WhereIterator方法是找到一个就直接返回并使用,然后再进入循环查找下一个
二者的代码运行顺序也是不同的:
(1)WhereEnumerable方法:
可以看到,点击F10之后,断点立刻进入WhereEnumerable方法中,步骤如下:
完成所有循环,返回结果
然后在调用方使用返回结果。
(2)WhereIterator方法:
可以看到,点击F10之后,断点不是立刻进入WhereIterator方法中,具体步骤如下:
而是先到返回结果使用的地方
然后再进入WhereIterator方法中,循环循环,一旦找到第一个匹配结果,立马返回到返回结果使用的地方使用该匹配结果
然后再次进入WhereIterator寻找匹配的结果(此时直接进入循环中匹配并返回结果,并不会再循行foreach这行代码)
本质
yield 关键字的本质是一个迭代器状态机,加了yield关键字的方法通过反编译工具反编译之后可以看到:
自动生成了一个泛型类副本
自动在方法上添加了IteratorStateMachine特性,参数是生成的泛型类
核心
迭代器状态机的核心要义:只要找到一个符合当前条件的数据,就直接返回。
通过反编译,我们可以在生成的泛型类中找到MoveNext方法,如下
上图可以看到,核心就是通过递归找到符合条件的数据,然后直接返回并使用,而后将状态恢复到原始状态,接着再次寻找下一个符合条件的数据并返回。
三、linq中where的本质
把固定不变的逻辑封装起来,把可变的逻辑封装成委托来传递。
Linq与lambda
lamda是个语法糖,本质是声明实现一个方法。 采用委托的方式声明、实现、调用。
Linq是对公共方法做了封装抽象,利用了泛型和委托,
将具体计算实现的函数,放给了具体提供委托的外部,
内部只保留一个调用外部方法的引用而已。
包括抽象出一套工具类、方法的接口规范而已。
怎么实现,还是要靠外部对委托的实现, linq只是负责在它规定的方法上调用了委托。
【我们自己封装也要这么封,不封装实现】
linq是微软认为当下程序已经不仅仅是简单的和数据库打交道了,我们的数据来源可能很多种,所以除了那个linq to sql,还出现了linq to xml等等,当然实际上我们最常用的就是linq to entity,这里entity就是内存对象了。说白了linq就是结构化查询语言,只不过不同于sql,他不局限于关系数据库。当然也引进了一些特征:如延迟执行,为了智能提示又改写了sql的习惯,入sql是select * from table ,linq中是from table selec var。
linq与lamda可以混用,因为有时候我们不想写太长的linq语句,然后微软对于很多类型,如list、dictionary等等集合都实现了或多或少的linq接口,你只要传一个方法给他就行了,然后因为这里的参数是方法,所以我们因为懒散,就又开始使用lamda了,等于传了一个具体实现是lambda的委托方法作为外部方法提供者, 但是内部调用交给了linq框架的接口上去调用invoke这个委托。
Linq扩展方法和表达式
在C#中,从功能上Linq可以分为两类:linq to object 和 linq to provider(如xml);从语法上可以分为linq 表达式和linq扩展方法,linq表达式和linq扩展方法本质相同,没有任何区别。
1、Linq To Object
linq to object针对IEnumerable类型数据。
IEnumerable类型数据:可以理解为内存数据
IQueryable类型数据:可以内存数据,也可以是数据库数据
注意: IEnumerable类型的扩展的方法和IQueryable类型扩展方法本质不同,
IQueryable类型扩展方法是将可变逻辑封装成表达式目录树(数据结构)
1、linq扩展方法
(1)本质
把不变的逻辑封装起来,把可变的逻辑封装成【委托】来传递,让开发者只关注自己的核心业务逻辑。
1
(2)延伸
Linq To Sql:打开数据库连接、查询数据(不变的逻辑)封装起来,把sql拼接(可变的逻辑)封装成委托。
Linq To Xml:数据解析(不变的逻辑)封装起来,筛选条件(可变逻辑)封装成委托传递
Linq To Redis:把固定的逻辑封装起来,可变的逻辑封装成委托(未实现)
Linq To Cache:同上(未实现)
Linq To Json:同上(未实现)
Linq To Everything:同上(未实现)
因此:Linq是一种封装思想,对于使用者而言,不用关注内部实现,只需要调用即可。
(3)常用扩展方法
<1> Where
<2> Select(投影)
Select被称作投影,可以基于数据源投影出一个新的对象(实体对象或者匿名对象均可),以便保留有效的属性或者字段
<3> First和FirstOrDefault
查询第一条数据
· First:如果集合是空的,会报错
· FirstOrDefault:如果集合是空的,不会报错,返回默认值
<9> Skip和Take 分页
· Skip:从起始位置(索引为0)起跳过指定的元素个数
· Take:读取指定的条数
<14> GroupBy 分组
<15>OrderBy、OrderByDescending和ThenBy、ThenByDescending
OrderBy:正向排序
OrderByDescending:逆向排序
ThenBy和ThenByDescending:配合OrderBy、OrderByDescending做多条件排序
<22> Join (inner join)
<23> GroupJoin
以left join 为例
2、Linq表达式
linq表达式和linq扩展方法本质相同,没有区别,linq表达式最终会被编译器成linq扩展方法。
(2)常用表达式---是linq的表达式,不是lamada,记住lamada是委托、方法
<1> from … in … where … select …
等同于where扩展方法
<2> join(相当于sql中的inner join)
等同于Join 扩展方法
<3> join into (Left join)
等同于扩展方法GroupJoin
<4> join into (Right join)
<7> 多关键字连接
在sql查询中,如果同时需要多个字段连接两张表的时候,可以用and或者or关键字,但是linq中不支持,那么应该如图连接,创建匿名对象包含连接关键字来作为连接条件