• .net 温故知新:【6】Linq是什么


    1、什么是Linq

    关于什么是Linq 我们先看看这段代码。

                List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, 7, 8, 10, 12 };
                var linqList = list.Where(t => t < 10)              //列表中值小于10
                               .GroupBy(t => t)                     //分组
                               .Where(t => t.Count() > 1)           //分组后出现次数大于1
                               .OrderByDescending(t => t.Count())   //按照出现次数倒序
                               .Select(t => t.Key);                 //选择值
                Console.WriteLine(string.Join(' ',linqList));
    
    


    这段代码使用Linq对List列表进行筛选、分组、排序等一系列操作展示了Linq的强大和便捷,那么我们为什么需要学习Linq?可以看到这样一堆逻辑只几行Linq很快就可以实现,如果要我们自己实现方法去处理这个List肯定是比较繁琐的。
    Linq是什么?如下是官方文档对于Linq的描述:

    语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。 数据查询历来都表示为简单的字符串,没有编译时类型检查或 IntelliSense 支持。 此外,需要针对每种类型的数据源了解不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等。 借助 LINQ,查询成为了最高级的语言构造,就像类、方法和事件一样。
    对于编写查询的开发者来说,LINQ 最明显的“语言集成”部分就是查询表达式。 查询表达式采用声明性查询语法编写而成。 使用查询语法,可以用最少的代码对数据源执行筛选、排序和分组操作。 可使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO .NET 数据集、XML 文档和流以及 .NET 集合中的数据。

    Linq的使用频率和范围可以说是很高很广的,基本每天应该都会用到,那么Linq到底是什么呢?怎么实现的?
    要学习Linq首先需要先了解委托Lambda 表达式,因为Linq是由 委托->Lambda->Linq 的一个变换过程。

    2、委托

    委托简单来讲就是指向方法的指针,就像变量是用来指向具体实现。例如String对象,我们定义一个对象string str="变量"那么str就是指向具体实例化对象的地址,String就是类型。
    按照这个思路,如果我们要定义一个指向方法的变量,委托就是为了实现该目的。委托使用 delegate 关键字来声明委托类型。
    用类似于定义方法签名的语法来定义委托类型。 只需向定义添加 delegate 关键字即可,如下我们定义一个比较两个数字的委托类型。

    //比较两个数字
    public delegate int Comparison(int i, int n);
    
    

    接着我们定义委托变量comparison并指向方法ComparisonMax方法,该方法比较两个int大小,返回大的一个。

    委托是和类平级的应以,理应放类同级别,但是C#支持类嵌套定义,所以我们把和本类关联性强的委托可以嵌套定义,委托变量comparison指向方法后,调用comparison(1, 2)执行委托方法并打印。
    当然委托可以有返回值也可以定义void无返回值,关于委托的其它方面这里不再赘述,这里主要是为了看清Linq所以浅显的梳理下。
    每次使用委托的时候我们都要定义比较麻烦,所以框架已经为我们定义好了两个类型,ActionFunc一个无返回值,一个有返回值,并且采用泛型定义了多个委托以满足我们日常使用。


    有了这两个系列的委托类型,上面的方式我们也可以不定义委托直接使用Func comparison = ComparisonMax;来实现。

    3、Lambda

    在看Lamda之前我们再看下委托方法的另外一种编写方式,匿名方法

    delegate 运算符创建一个可以转换为委托类型的匿名方法
    如下我们直接在委托变量后面使用delegate 将参数方法体直接写,而不用声明其名称的方式。

    
    Func<int,int,int> comparison = delegate(int i,int n) { return i > n ? i : n; };
             
    

    运行打印下结果:

    从 C# 3 开始,lambda 表达式提供了一种更简洁和富有表现力的方式来创建匿名函数。 使用 => 运算符构造 Lambda
    在 lambda 表达式中,lambda 运算符 将左侧的输入参数与右侧的 lambda 主体分开。

    使用 Lambda 表达式来创建匿名函数。 使用 lambda 声明运算符=>(读作 goes to) 从其主体中分离 lambda 参数列表。 Lambda 表达式可采用以下任意一种形式:

    其中第一种后面写表达式,第二种是使用大括号{}的代码块作为主体,语句 lambda 与表达式 lambda 类似,只是语句括在大括号中。
    其实 表达式lambda 就是 语句lambda 在只有一行的情况下可以省略大括号和return。表达式 lambda 的主体可以包含方法调用。 不过若在表达式树中,则不得在 Lambda 表达式中使用方法调用。表达式树是另外一个东西,我们现在使用的ORM框架就是将lambda转换为sql,这个过程使用表达式树技术,比如EF查询中,如果我们写一个Console.WriteLine()表达式树是没办法转换的,想一下这个调用对于sql查询来说是没有意义的,表达式树以后再讨论吧。

    因此上面的匿名函数可以通过lambda变换为:

    
    Func<int,int,int> comparison = (int i,int n) =>{ return i > n ? i : n; };
             
    

    Lambda表达式参数类型也可以省略,输入参数类型必须全部为显式或全部为隐式;否则,便会生成 CS0748 编译器错误。
    所以表达式还可以变换为:

    
    Func<int,int,int> comparison = (i,n) =>{ return i > n ? i : n; };
             
    

    将 lambda 表达式的输入参数括在括号中。 如果没有参数则直接写():Action ac = () => {Console.WriteLine();}或者Action ac = () => Console.WriteLine()
    如果 lambda 表达式只有一个输入参数,则括号是可选:Func fun = i => {return i++;}或者Func fun = i =>i++
    关于更多的lambda知识可以参看文档:Lambda 表达式

    4、实现一个Linq

    有了委托和Lambda 的知识,我们可以自己写一个简易的Linq实现,写一个where吧。
    我们需要扩展List类的方法,当然不用扩展方法也是可以实现,直接写方法然后调用,但是为了还原框架实现方式,我们模仿扩展方法扩展List。

    关于扩展方法:

    扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种静态方法,但可以像扩展类型上的实例方法一样进行调用。
    扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。 它们的第一个参数指定方法操作的类型。 参数前面是 this 修饰符。 仅当你使用 using 指令将命名空间显式导入到源代码中之后,扩展方法才位于范围中。

    • 定义扩展方法
        public static class MyLinq
        {
            public static List<T> MyLinqWhere<T>(this List list, Funcbool> predicate)
            {
                List tempList = new List();
                foreach (var item in list)
                {
                    if (predicate(item))
                    {
                        tempList.Add(item);
                    }
                }
                return tempList;
            }
        }
    

    List类是泛型,所以我们定义泛型MyLinqWhere 方法,第一个参数使用this关键字修饰,然后predicate为一个输入参数是T返回时bool的委托用来进行对List里面的每一个元素进行筛选,返回的bool结果判断是否符合要求。
    我们将符合要求的元素放到一个新的List里面最后返回该List。

    • 使用Linq方式调用自定义的where方法
         List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, 7, 8, 10, 12 };
          var listWhere = list.MyLinqWhere(x => x < 7);
          Console.WriteLine(string.Join(' ', listWhere));
    

    这样就实现了一个简单的Linq,虽然实际的IEnumerable扩展方法里面还有其它操作,但是通过这个过程我们知道了Linq的实现。
    在IEnumerable扩展方法返回参数仍然是IEnumerable,所以可以像开始我们写的那样进行链式调用

    5 Linq的另外一种写法

    在刚开始的例子中我们换另外一种写法:

    var linqList2 = from t in list
                       where t < 10
                       group t by t into t
                       where t.Count() > 1
                       orderby t.Count() descending
                       select t.Key;
    

    输出的结果和方法调用,使用Lambda出来的结果时一样的。

    这种方式称为语言集成查询,查询表达式采用声明性查询语法编写而成。 使用查询语法,可以用最少的代码对数据源执行筛选、排序和分组操作。 可使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO .NET 数据集、XML 文档和流以及 .NET 集合中的数据。
    这种写法只是一种语法方式,或者说语法糖,在编译阶段生成的代码和Lambda表达式生成的代码是一致的,虽然这种方法看起来比较炫酷,但是目前大家还是比较习惯Lambda的书写方式和阅读,了解就行了,要详细学习可以参看官方文档。

  • 相关阅读:
    设计模式-结构型-06-桥接模式
    房子再小,也要有自己的装修设计!福州中宅装饰,福州装修
    java毕业生设计采购物料质量检验系统计算机源码+系统+mysql+调试部署+lw
    “CardReordering“app Tech Support(URL)
    【华为OD机试真题 JS】根据某条件聚类最少交换次数
    mysql同步数据到es
    软考高级系统架构设计师系列之:微服务
    【Vue 基础知识】v-for的使用和注意事项
    RecId
    菜鸟踩坑之MybatisPlus查询时过滤不想要的字段
  • 原文地址:https://www.cnblogs.com/SunSpring/p/16498439.html