语言集成查询(Language Integrated Query)是一组技术名称,他可以让我们书写类型安全(type-safe)的查询语句对本地对象或远程数据集合进行查询操作。LINQ使用的前提是你需要查询的对象集合(collection)实现了IEnumerable<T>接口。LINQ包含的技术有本地对象查询(Linq to Object),XML查询(Linq to XML),数据库查询(Linq to Sql)等,我们也可以为自己的程序扩展Linq实现如现在有的Linq to Lucene等。
1.Get Start
LINQ的基本组成元素为squences和elements.squencens事任何一个实现了IEnumerable<T>接口的对象,element则是组成该squences的每单个对象。LINQ有三种写法效果都一样:Lambda表达式写法(暂且这么叫吧),扩展方法写法(extenstion method)和查询表达式写法(Fluent Syntax)。
//Get the name'length over four. string[] names = { "Jack", "Hanmeimei", "Lilei", "Amy" }; IEnumerable<string> filteredNames1 = Enumerable.Where(names, n => n.Length > 4); IEnumerable<string> filteredNames2 = names.Where(n => n.Length > 4); IEnumerable<string> filteredNames3=from name in names where name.Length>4 select name;
第一种写法是是Lambda表达式的写法,也是最原始的写法。他使用类Enumerable(该类定义在System.Linq命名空间下)里面的一系列静态方法(static method)定义的查询操作符(query operater)来进行查询,第二种扩展方法的写法是利用c#3.0的新特性(fetures)来进行的扩展,第三种查询表达式的写法(query expression syntax)则是VS2008提供的“语法糖”(IL代码是1?2?)能够让我们像书写SQL语句那样进行书写查询表达式。
那么为什么需要扩展方法的书写呢?因为我们查询一个集合可能要对其进行不止一样的筛选,而扩展方法提供了便利可以进行链式操作(Chaining querying operators).
//Get the name'length over four the sort then by name'length then upper toUpper . string[] names = { "Jack", "Hanmeimei", "Lilei", "Amy" }; IEnumerable<string> filteredNames1 = Enumerable.Select( Enumerable.OrderBy( Enumerable.Where(names,name=>name.Length>4),name=>name.Length ),name=>name.ToUpper() ); IEnumerable<string> filteredNames2 = names.Where(n => n.Length > 4) .OrderBy(n=>n.Length) .Select(n=>n.ToUpper()); IEnumerable<string> filteredNames3=from name in names where name.Length>4 orderby name.Length select name.ToUpper();
可以看出使用Lambda表达式很繁琐很难理解,而是要扩展方法的查询相对比较简单易于理解,使用查询表达式语法则更接近于SQL的语法。(我们看到Enumerable.Select<(Of <(TSource, TResult>)>)(IEnumerable<(Of <(TSource>)>), Func<(Of <(TSource, TResult>)>)) 的第二个参数是Func<T>(TSource,TResult),这是一个泛型委托,在System.Core.dll里面类似还有Action和Predicate)。Enumerable里面除了Where操作为还有大约40个其他的操作如Group ,Join等。
2.延迟机制(Deferred/Lazy Execution)
LINQ查询的结果不是在创建查询时得到的而是在进行枚举时即进行foreach时得到的。
//lazy execution var numbers= new List<int>(); numbers.Add(1); IEnumerable<int> result = numbers.Select(n => n * 10); numbers.Add(2); foreach (var number in result) { Console.WriteLine(number); } //Print 10,20
如果我们想查询后立即得到结果可使用转换操作符(convertion opertators)如:ToList,ToLookup,ToArray,ToDictionary.
//lazy execution var numbers= new List<int>(); numbers.Add(1); IEnumerable<int> result = numbers.Select(n => n * 10).ToList(); numbers.Add(2); foreach (var number in result) { Console.WriteLine(number); } //Print 10
当然也有例外的情况LINQ查询时返回的单个结果或者是数值的则没有延迟机制的影响如Count,First等。
var names = new List<string>(); names.Add("lili"); names.Add("kated"); names.Add("amy"); names.Add("lilei"); int count = names.Where(n => n.Length >4).Count(); names.Add("jakey"); Console.WriteLine(count); //Print 2 Console.ReadKey();
延迟机制的另一个重要特性就是在进行第二次foreach时要重新进行评估操作(reevaluation)
//lazy execution reevaluation var numbers = new List<int> { 1,2}; var query = numbers.Select(n => n * 10); foreach (var n in query) { Console.Write(n + " | "); //10 | 20 } numbers.Clear(); foreach (var n in query) { Console.Write(n + " | "); //nothing }
如果我们需要第二次的结果不变那么我们就可以用到上面提到的方法在后面加上ToList()操作让其立即返回结果集。
延迟机制也有负面影响之一就是当你在查询表达式中使用了本地变量后在foreach之前改变变量那么结果也会改变。
//lazy execution catured variables var numbers = new List<int> { 1,2}; int factor = 10; var query = numbers.Select(n => n * factor); factor = 20; foreach (var n in query) { Console.Write(n + " | "); //20 | 40 Not 10|20 }
LINQ延迟机制的实现是通过装饰模式(Decorator)来实现的.进行链式操作时就是一个个装饰模式的叠加。
3.子查询(subquery)
LINQ支持子查询。
//subquery string[] names = {"kate","lilei","hanmeimei" }; var query = names.OrderBy(m => m.Split().First()); foreach (var name in query) { Console.WriteLine(name); //hanmeimei,kate,lilei }
m.Split().First()就是子查询。LINQ在执行子查询时是每次外层进行循环(loop)时都需要执行一次子查询。对性能有一定影响所有使用的时候要注意。
子查询与延迟机制:在子查询中使用Count(),ToList()等时不会影响外层查询立即执行,这是因为子查询是一个委托(delegate),他不是直接与外层查询进行交互的。