C#进阶之路(五):Linq初识
关于LINQ的文章,网上有很多,所以这篇文章我主要是总结下我自己的学习心得。
首先需要先了解的相关技术
1.隐式类型、匿名类型、对象初始化器
1)隐式类型,使用var关键字创建,C#编译器会根据用于初始化局部变量的初始值推断出变量的数据类型。(不过我个人认为,能用具体类型的地方尽量不要用var关键字,因为这样会让你遗忘“被封装类库”方法的返回值类型--有损可读性)
隐式类型使用限制:
a) 隐式类型只能应用于方法或者属性内局部变量的声明,不能使用var来定义返回值、参数的类型或类型的数据成员。
b)使用var进行声明的局部变量必须赋初始值,并且不能以null作为初始值。
2) 匿名类型,只是一个继承了Object的、没有名称的类。C#编译器会在编译时自动生成名称唯一的类。
3) 对象初始化器,提供一种非常简洁的方式来创建对象和为对象的属性赋值。(相关还有“集合初始化器”)
由于C#强类型语言,即我们在声明变量时必须指定变量的具体类型。所以在创建匿名对象时,需要结合隐式类型、匿名类型、对象初始化器一起创建匿名对象。(避免类型转换)
示例:
var person = new { name = “heyuquan” , age = 24 }
由于C#强类型语言,即我们在声明变量时必须指定变量的具体类型。所以在创建匿名对象时,需要结合隐式类型、匿名类型、对象初始化器一起创建匿名对象。(避免类型转换)
2.Lambda表达式,Func委托
1)Lambda表达式只是用更简单的方式来书写匿名方法,从而彻底简化.NET委托类型的使用。
Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。
2)Func委托
Func委托,是微软为我们预定义的常用委托,封装一个具有:零个或多个指定类型的输入参数并返回一个指定类型的结果值的方法。
示例:
static void Main(string[] args) { // 委托函数 Func<string, string, string> func1 = Hello; // 匿名方法 Func<string, string, string> func2 = delegate(string a, string b) { return "欢迎光临我的博客" + Environment.NewLine + a + " " + b; }; // Lambda表达式 Func<string, string, string> func3 = (a, b) => { return "欢迎光临我的博客" + Environment.NewLine + a + " " + b; }; // 调用Func委托 string helloStr = func2("滴答的雨", @"http://www.cnblogs.com/heyuquan/"); Console.WriteLine(helloStr); } static string Hello(string a, string b) { return "欢迎光临我的博客" + Environment.NewLine + a + " " + b; }
3.扩展方法
1)扩展方法声明在静态类中,定义为一个静态方法,其第一个参数需要使用this关键字标识,指示它所扩展的类型。
2)扩展方法可以将方法写入最初没有提供该方法的类中。还可以把方法添加到实现某个接口的任何类中,这样多个类就可以使用相同的实现代码。(LINQ中,System.Linq.Queryable.cs和System.Linq.Enumerable.cs 正是对接口添加扩展方法)
3)扩展方法虽定义为一个静态方法,但其调用时不必提供定义静态方法的类名,只需引入对应的命名空间,访问方式同实例方法。
4)扩展方法不能访问它所扩展的类型的私有成员。
示例:
public static IEnumerable<TSource> MyWhere<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate) { foreach (TSource item in source) { if (predicate(item)) yield return item; } }
4.Yield迭代器,延迟计算
1)Yield迭代器
在上面定义的MyWhere扩展方法中,我们使用了yield迭代器。使我们不必“显示”实现IEnumerable或IEnumerator接口。只需要简单的使用yield 关键字,由 JIT 编译器帮我们编译成实现 IEnumerable或IEnumerator接口的对象(即:本质还是传统遍历,只是写法上非常简洁),就能使用foreach进行遍历。
2)延迟计算(Lazy evaluation)
a)定义:来源自函数式编程,在函数式编程里,将函数作为参数来传递,传递过程中不会执行函数内部耗时的计算,直到需要这个计算结果的时候才调用,这样就可以因为避免一些不必要的计算而改进性能。
b)Yield迭代器的延迟计算原理:JIT 编译器会帮助我们将迭代主体编译到IEnumerator.MoveNext()方法中。从上图foreach的执行流程来看,迭代主体是在每次遍历执行到 in 的时候才会调用MoveNext(),所以其迭代器耗时的指令是延迟计算的。
c)LINQ查询的延迟计算原理:通过给LINQ扩展方法传递方法委托,作为yield迭代器的主体,让遍历执行到MoveNext()时才执行耗时的指令。
5.表达式树
表达式树:表达式树允许在运行期间建立对数据源的查询,因为表达式树存储在程序集中。(后续会有详细介绍)
Language Integrated Query(LINQ,语言集成查询)
从这幅图中,我们可以知道LINQ包括五个部分:LINQ to Objects、LINQ to XML、LINQ to SQL、LINQ to DataSet、LINQ to Entities。
目前,还可以下载其他第三方提供程序,例如LINQ to JSON、LINQ to MySQL、LINQ to Amazon、LINQ to Flickr和LINQ to SharePoint。无论使用什么数据源,都可以通过LINQ使用相同的API进行操作。
1.怎样区分LINQ操作时,使用的是哪个LINQ提供程序?
LINQ提供程序的实现方案是:根据命名空间和第一个参数的类型来选择的。实现扩展方法的类的命名空间必须是打开的,否则扩展类就不在作用域内。Eg:在LINQ to Objects中定义的 Where() 方法参数和在 LINQ to Entities中定义的 Where() 方法实现是不同。
// LINQ to Objects: public static class Enumerable { public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate); } // LINQ to Entities public static class Queryable { public static IQueryable<TSource> Where<TSource>( this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate); }
2.LINQ查询提供几种操作语法?
LINQ查询时有两种语法可供选择:查询表达式(Query Expression)和方法语法(Fluent Syntax)。
.NET公共语言运行库(CLR)并不具有查询表达式的概念。所以,编译器会在程序编译时把查询表达式转换为方法语法,即对扩展方法的调用。所以使用方法语法会让我们更加接近和了解LINQ的实现和本质,并且一些查询只能表示为方法调用。但另一方面,查询表达式通常会比较简单和易读。不管怎样,这两种语法是互相补充和兼容的,我们可以在一个查询中混合使用查询表达式和方法语法。
以下扩展方法存在对应的查询表达式关键字:Where、Select、SelectMany、OrderBy、ThenBy、OrderByDescending、ThenByDescending、GroupBy、Join、GroupJoin。
LINQ查询表达式
书写模版如下: