1、什么是Linq
关于什么是Linq 我们先看看这段代码。
代码语言:javascript复制 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所以浅显的梳理下。
每次使用委托的时候我们都要定义比较麻烦,所以框架已经为我们定义好了两个类型,Action
和Func
一个无返回值,一个有返回值,并且采用泛型
定义了多个委托以满足我们日常使用。
有了这两个系列的委托类型,上面的方式我们也可以不定义委托直接使用Func<int,int,int> comparison = ComparisonMax;
来实现。
3、Lambda
在看Lamda之前我们再看下委托方法的另外一种编写方式,匿名方法。
代码语言:javascript复制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变换为:
代码语言:javascript复制Func<int,int,int> comparison = (int i,int n) =>{ return i > n ? i : n; };
Lambda表达式参数类型也可以省略,输入参数类型必须全部为显式或全部为隐式;否则,便会生成 CS0748 编译器错误。 所以表达式还可以变换为:
代码语言:javascript复制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的另外一种写法
在刚开始的例子中我们换另外一种写法:
代码语言:javascript复制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的书写方式和阅读,了解就行了,要详细学习可以参看官方文档。