.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<int,int,int> 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<int,int> fun = i => {return i++;}或者Func<int,int> fun = i =>i++
关于更多的lambda知识可以参看文档:Lambda 表达式

4、实现一个Linq

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

关于扩展方法:

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

  • 定义扩展方法
    public static class MyLinq
    {
        public static List<T> MyLinqWhere<T>(this List<T> list, Func<T, bool> predicate)
        {
            List<T> tempList = new List<T>();
            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的书写方式和阅读,了解就行了,要详细学习可以参看官方文档。

posted @ 2022-07-21 14:12  XSpringSun  阅读(1267)  评论(1编辑  收藏  举报