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类的方法,当然不用扩展方法也是可以实现,直接写方法然后调用,但是为了还原框架实现方式,我们模仿扩展方法扩展List。

关于扩展方法:

扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种静态方法,但可以像扩展类型上的实例方法一样进行调用。

扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。 它们的第一个参数指定方法操作的类型。 参数前面是 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的书写方式和阅读,了解就行了,要详细学习可以参看官方文档。

最新文章

  1. Java虚拟机4:内存溢出
  2. 2.头文件&lt;bits/stdc++.h&gt;
  3. C语言:内存字节对齐详解[转载]
  4. Eclipse为Unity3d编写jar组件
  5. 【转】如何使用Android Studio把自己的Android library分发到jCenter和Maven Central
  6. C#基础精华----枚举
  7. cas4.2.7 取消https
  8. 实用css小技巧
  9. hadoop/storm以及hive/hbase/pig区别整理
  10. iOS开发中的零碎知识点笔记 韩俊强的博客
  11. 【转】pyhton之Reportlab模块——生成pdf文件
  12. unity API 之EventSystem.current.IsPointerOverGameObject()
  13. json、数组、html标签的修改删除
  14. NSCache的简单使用
  15. No mapping found for HTTP request with URI [/Portal/download] in DispatcherServlet with name &#39;springmvc&#39;
  16. commit your changes or stash them before you can merge
  17. EF事务处理封装公用
  18. HDOJ1008
  19. 接口测试maven管理
  20. Python&amp;Appium实现安卓手机图形解锁

热门文章

  1. 10个 Linux 命令,让你的操作更有效率
  2. 异步加载数据——turn.js
  3. 调试F9/F10/F11/F8
  4. 如何在 pyqt 中解决启用 DPI 缩放后 QIcon 模糊的问题
  5. 115_Power Pivot之HR薪酬计算:公积金、社保、个税、实发工资相关
  6. 110_Power Pivot特殊结算日期及财年日期
  7. linux系统下文件误删除该如何恢复?
  8. 『忘了再学』Shell基础 — 24、Shell正则表达式的使用
  9. Centos免密登陆
  10. 模块re正则