Linq(一)
Linq是c#设计者们在c#3.0中新添加的语法:查询表达式。使用查询表达式,很多标准查询操作符都能转化成更容易理解的代码,也就是和SQL风格非常接近的代码。
在介绍Linq之前,先介绍下泛型集合IEnumerable<T>,IEnumerable泛型接口可以在指定数据源进行迭代操作,它定义了一些扩展方法,可以对数据源进行操作。在Linq中,数据源实际是实现了对接口IEnumerable<T>的类,通过selsect返回的结果页实际是一个类。
Exposes the enumerator, which supports a simple iteration over a collection of a specified type.
这是msdn上给出的解释,一切foreach都是基于IEnumerable。在实际应用中,查询表达式输出哦几乎总是IEnumerable<T>或者它的派生类型。
下面看一段代码,简单的查询表达式:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LinqBasic { class Program { private static string[] keyWords = { "it", "has", "been", "a", "long", "day", "to", "see", "you", "myfriend" }; public static void ShowWordContains() { IEnumerable<string> selection = from word in keyWords where word.Contains("t") select word; //此处将IEnumerable<string>换成var依旧可以,程序会根据选择的结果自动推断出查询的类型。 ShowWordContains(selection); } public static void ShowWordContains(IEnumerable<string> selection){ foreach (string keyword in selection) Console.WriteLine(keyword); } static void Main(string[] args) { ShowWordContains(); Console.ReadLine(); } } }
输出:it
to
在这个查询中,表达式总是以from子句开头,以select或者group by 结尾。from子句中的标识符word称为范围变量,代表集合中的每一项。这就很像是foreach循环变量代表集合中的没一项。
熟悉sql的开发者会发现,Linq和sql有非常相近的语法。最明显的区别是 c#查询的子句顺序首先是from子句,然后是where子句,最后才是select子句。而对应的sql查询首先是select,然后是from,最后是where。
#查询表达式的顺序其实更接近于各个操作在逻辑上的顺序,对查询进行求值时,首先指定集合from,再筛选出想要的项,(where),最后描述希望的结果(select)。
最后,c#查询表达式的顺序确保变量的作用域规则与局部变量的规则保持一致。例如,子句(通常是from)子句先声明变量,然后才能使用这个变量。
投射:
查询表达式的结果是IEnumerable<T>类型的集合。T的实际类型是从select或者group by子句推到出来的。例如 在上面的代码,编译器知到keywords是string[]类型,能穿花为IEnumerable<string>类型,所以推到出word是string类型,查询以select word结尾,所以查询表达式是字符串集合,所以就算是匿名类型var,编译器也知道实际类型。
其实,输出类型还可以有别于输入类型,关键在于select子句的投射,如下面的代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; namespace LinqBasic { class Program { static void List1(string rootDirectory, string searchExtension) { IEnumerable<string> files = Directory.GetFiles(rootDirectory, searchExtension); IEnumerable<FileInfo> fileInfos = from file in files select new FileInfo(file); foreach (FileInfo fileinfo in fileInfos) { Console.WriteLine("filename:{0}, \tfileLaseWriteTime:{1}", fileinfo.Name, fileinfo.LastAccessTime); } } static void Main() { List1(@"G:\c#\depositaryManagingSystem",@"*.cs"); Console.ReadLine(); } } }
输出:
filename:depositary.cs, fileLaseWriteTime:2015/7/1 17:44:21
filename:depositary.Designer.cs, fileLaseWriteTime:2015/6/25 19:32:42
filename:Form1.cs, fileLaseWriteTime:2015/6/22 23:44:19
filename:Form1.Designer.cs, fileLaseWriteTime:2015/6/22 23:44:19
filename:Program.cs, fileLaseWriteTime:2015/6/17 10:17:38
filename:注册管理员.cs, fileLaseWriteTime:2015/6/22 23:55:15
filename:注册管理员.Designer.cs, fileLaseWriteTime:2015/6/25 19:34:32
这个查询表达式的结果是一个IEnumerable<fileinfo>,而不是Directoy.Getfiles()返回的Ienumerable<string>类型,查询表达式的select子句可以将from子句的表达式所收集到的东西完全投射到完全不同的数据类型中。
注意: 将上面的查询结果强数据类型转化成var匿名类型是完全可以的。
如下面的代码段:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; namespace LinqBasic { class Program { static void List1(string rootDirectory, string searchExtension) { var files = Directory.GetFiles(rootDirectory, searchExtension); var fileresults = from file in files select new { Name = file, LastWriteName = File.GetLastWriteTime(file) }; foreach (var fileresult in fileresults) { Console.WriteLine("filename:{0}, \tfileLaseWriteTime:{1}\n", fileresult.Name, fileresult.LastWriteName); } } static void Main() { List1(@"G:\c#\depositaryManagingSystem",@"*.cs"); Console.ReadLine(); } } }
在这个例子中,我们只投射出了文件名和他最后一次的写入时间。
程序运行结果
可以看出,Directory.Getfiles()这个静态方法可以得到文件的全名。而FileInfos.Name这个属性只能得到文件名。
关于文件的知识,将在不久后讲解。
初学者主题:查询表达式的推迟执行
我们现在来考虑我的第一个代码段,对selection赋值的时候,创建查询和向变量赋值不会执行查询,而只是生成代表查询的对象。换言之,查询对象创建时不会调用word.contains()方法。查询表达式只是存储了一个条件(查询标准)。以后再遍历有selection变量所标识的集合时会用到这个条件。
下面我们在来看一段代码来检测这个发生。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; namespace LinqBasic { class Program { private static string[] keyWords = { "it", "has", "been", "a", "long", "day", "to", "see", "you", "myfriend" }; public static void ShowKeywords() { IEnumerable<string> selections = from word in keyWords where IsKeyWord(word) select word; Console.WriteLine("Query created"); foreach (string str in selections) Console.WriteLine(str); } private static bool IsKeyWord(string word) { Console.WriteLine("~~~"); if (word.Contains("t")) return true; return false; } static void Main() { ShowKeywords(); Console.ReadLine(); } } }
程序输出:
可以看到,在query created 之前并没有任何输出,在foreach对集合的输出是,才调用方法IsKeyWord();
总之 虽然selection是集合(毕竟他的类型是IEnumerable<T>),但在赋值时,from子句之后的一切都构成了选择条件。遍历selection时才会真正应用这些条件。
现在来考虑第二个例子:
今天七夕要早睡,剩下的明天继续吧。