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 迭代器关键字

对比

本质

核心

三、linqwhere的本质

四、本文代码

一、接口实现Where扩展

上一章我们最终得到的where扩展方法实现如图,但是还是有缺点:只支持List调用改扩展方法,那么能不能让它更加灵活呢?

 

当然可以:可以通过IEnumerable接口来扩展,这样做的好处:

 

通过接口来扩展,只要实现了这个接口的,都可以使用这个扩展(ListHashSet)

扩展性更好,更加通用

/// <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方法是找到一个就直接返回并使用,然后再进入循环查找下一个

 

二者的代码运行顺序也是不同的:

1WhereEnumerable方法:

可以看到,点击F10之后,断点立刻进入WhereEnumerable方法中,步骤如下:

完成所有循环,返回结果

然后在调用方使用返回结果。

 

2WhereIterator方法:

可以看到,点击F10之后,断点不是立刻进入WhereIterator方法中,具体步骤如下:

 

而是先到返回结果使用的地方

然后再进入WhereIterator方法中,循环循环,一旦找到第一个匹配结果,立马返回到返回结果使用的地方使用该匹配结果

然后再次进入WhereIterator寻找匹配的结果(此时直接进入循环中匹配并返回结果,并不会再循行foreach这行代码)

 

 

本质

yield 关键字的本质是一个迭代器状态机,加了yield关键字的方法通过反编译工具反编译之后可以看到:

 

自动生成了一个泛型类副本

自动在方法上添加了IteratorStateMachine特性,参数是生成的泛型类

 

核心

迭代器状态机的核心要义:只要找到一个符合当前条件的数据,就直接返回。

通过反编译,我们可以在生成的泛型类中找到MoveNext方法,如下

上图可以看到,核心就是通过递归找到符合条件的数据,然后直接返回并使用,而后将状态恢复到原始状态,接着再次寻找下一个符合条件的数据并返回。

 

三、linqwhere的本质

把固定不变的逻辑封装起来,把可变的逻辑封装成委托来传递。

 

Linq与lambda

 

lamda是个语法糖本质是声明实现一个方法 采用委托的方式声明、实现、调用。

 

Linq是对公共方法做了封装抽象,利用了泛型和委托,

将具体计算实现的函数,放给了具体提供委托的外部,

内部只保留一个调用外部方法的引用而已。

包括抽象出一套工具类、方法的接口规范而已。

 

怎么实现,还是要靠外部对委托的实现, linq只是负责在它规定的方法上调用了委托。

【我们自己封装也要这么封,不封装实现】

 

 

linq是微软认为当下程序已经不仅仅是简单的和数据库打交道了,我们的数据来源可能很多种,所以除了那个linq to sql,还出现了linq to xml等等,当然实际上我们最常用的就是linq to entity,这里entity就是内存对象了。说白了linq就是结构化查询语言,只不过不同于sql,他不局限于关系数据库。当然也引进了一些特征:如延迟执行,为了智能提示又改写了sql的习惯,入sqlselect * from table ,linq中是from table selec var

 

linqlamda可以混用,因为有时候我们不想写太长的linq语句,然后微软对于很多类型,如listdictionary等等集合都实现了或多或少的linq接口,你只要传一个方法给他就行了,然后因为这里的参数是方法,所以我们因为懒散,就又开始使用lamda等于传了一个具体实现是lambda的委托方法作为外部方法提供者, 但是内部调用交给了linq框架的接口上去调用invoke这个委托。

 

 

Linq扩展方法和表达式

C#中,从功能上Linq可以分为两类:linq to object linq to provider(如xml);从语法上可以分为linq 表达式linq扩展方法,linq表达式和linq扩展方法本质相同,没有任何区别。

 

 

1Linq To Object

linq to object针对IEnumerable类型数据。

 

IEnumerable类型数据:可以理解为内存数据

IQueryable类型数据:可以内存数据,也可以是数据库数据

 

注意: IEnumerable类型的扩展的方法和IQueryable类型扩展方法本质不同,

IQueryable类型扩展方法是将可变逻辑封装成表达式目录树(数据结构

1linq扩展方法

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>OrderByOrderByDescendingThenByThenByDescending

OrderBy:正向排序

OrderByDescending:逆向排序

ThenByThenByDescending:配合OrderByOrderByDescending做多条件排序

 <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中不支持,那么应该如图连接,创建匿名对象包含连接关键字来作为连接条件

 

3Linq To Xml