本系列的前两篇文章主要讲了C#3.0引入的新特性,也正是这些新特性让Linq成为可能。我先总结一下前面的知识点,我简单的列举出来:
■ Implicitly typed local variables:隐式类型局部变量
■ Object initializers:对象初始化器
■ Lambda expressions:Lambda表达式
■ Extension methods:扩展方法
■ Anonymous types:匿名类型
我简单的画了一张图来描述他们在Linq中扮演的角色:
这里我为什么又拿出来呢,因为在接下来的篇幅中,我们总会用到上面这些知识点,因为他们太重要了。好了,我们进入今天的主题:
这篇文章主要讲三个点:
- sequences
- query operators
- query expressions
这里用英文原句来介绍,因为翻译成中文,味道就变了。
好,开始我们今天的第一个知识点:
一:Sequences
首先我们要搞清楚,什么类型的sequence才能被Linq查询,通过我们之前的例子来说事:
var processes = Process.GetProcesses() .Where(process => process.WorkingSet64 >= 1024 * 1024 * 10) .OrderByDescending(process => process.WorkingSet64) .Select(process => new { process.Id, Name = process.ProcessName, Memory = process.WorkingSet64 });
这段代码中,GetProcesses()方法返回的是一组Process对象的数组。在.net中,array实现了IEnumerable<T>的泛型接口,而GetProcesses方法是定义在System.Diagnostics.Process的类中,Process类实现了IEnumerable<Process>接口,所以我们暂时得到的结论是,若想使用linq query,那么我们的对象就必须实现IEnumerable<T>接口。IEnumerable<T>接口非常重要,上例中我们用到的Where、OrderByDescending、Select等扩展方法都是使用Process类型的对象作为参数。
另外补充一点,Linq还有一个重要的特性就是Deferred query execution(延时执行),也就是说我们通过一些linq query expression或linq query operation操作获得的sequence,并没有立即到objects或数据库中把这些对象拿出来,而是等我们真正用到的时候才取出来,有个最典型的应用就是“按需加载”,对我们有利用价值的东西我们取出来,没用到的就不取,这样会降低我们程序的压力,提高性能。这里我通过一个简单的例子来说明一下延时加载:
首先,我们不用linq查询来实现输出一个整型数组里每个元素的平方:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace DeferredQueryExecution 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 int[] nums = { 1, 2, 3 }; 13 14 double[] query = new double[3]; 15 16 //给query赋值 17 for (int i = 0, len = nums.Length; i < len; i++) 18 { 19 query[i] = Square(nums[i]); 20 } 21 22 foreach (var n in query) 23 { 24 Console.WriteLine(n); 25 } 26 27 Console.ReadKey(); 28 } 29 30 /// <summary> 31 /// 计算num平方的方法 32 /// </summary> 33 /// <param name="num"></param> 34 /// <returns></returns> 35 static double Square(double num) 36 { 37 Console.WriteLine("计算平方值( " + num + " )..."); 38 return Math.Pow(num, 2); 39 } 40 } 41 }
输出结果:
再来看看用Linq查询的示例:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace DeferredQueryExecution 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 int[] nums = { 1, 2, 3 }; 13 14 var query = 15 from num in nums 16 select Square(num); 17 18 foreach (var n in query) 19 { 20 Console.WriteLine(n); 21 } 22 23 Console.ReadKey(); 24 } 25 26 /// <summary> 27 /// 计算num平方的方法 28 /// </summary> 29 /// <param name="num"></param> 30 /// <returns></returns> 31 static double Square(double num) 32 { 33 Console.WriteLine("计算平方值( " + num + " )..."); 34 return Math.Pow(num, 2); 35 } 36 } 37 }
输出结果:
这里,我们可以很清楚的看出,使用linq查询并没有立即执行,而是当我们遍历query结果的时候才会真正的执行,这就是延时执行。
好,进入今天的第二个重点:query operations
二:Query Operations
什么是query operations?其实query operations就是IEnummerable<T>中给我定义的一些对于类型T的扩展方法,我列举一下常用的query operations。
Filtering | OfType,Where |
Projection | Select,SelectMany |
Partioning | Skip,SkipWhile,Take,TakeWhie |
Join | GroupJoin,Join |
Concatenation | Concate |
Ordering | OrderBy,OrderByDescending,Reverse,ThenBy,ThenByDescending |
Grouping | GroupBy,ToLookup |
Set | Distinct,Except,Intersect,Union |
Conversion | AsEnumerable,AsQueryable,Cast,ToArray,ToDictionary,ToList |
Equality | SequenceEqual |
Element | ElementAt,ElementAtOrDefault,First,FirstOrDefault,Last,LastOrDefault,Single,SingleOrDefault |
Generation | DefaultIfEmpty,Empty,Range,Repeat |
Quantifiers | All,Any,Contains |
Aggregation | Aggregate,Average,Count,LongCount,Max,Min,Sum |
通过这些扩展方法,我们很容易对sequences进行一系列的操作,这篇文章中不会一一对这些方法进行介绍,但是后续文章中或多或少都会涉及到这些方法。其实上文中我们已经用到了一些query operation,例如Where、Select和OrderByDescending,根据这些方法的名字我们大概都能知道他们的功能。Where其实起到一个过滤的作用,把满足条件的对象筛选出来,OrderBy和OrderByDescending方法是对Select取出来的sequence按照传进去的参数进行排序。我们可以把这些方法看作是工厂中的一系列的流水线,将original的sequences输进去进行加工,然后得到我们需要的sequences,这里我贴出一张图方便大家理解:
三:Query expressions
Linq还为我们提供了第二种查询方法,就是query expression的方式,在某些情况下可能query operation并不是显得那么清楚,对于熟悉T-SQL的朋友们来说,query expression可能更容易理解,本人也偏向于query expression这种方式,但是有些时候,有些query operation也不能被很好的转换为query expression,好了,这些概念型的东西我就不说了,我也说不好,我们直接看代码吧,还是上文中的例子,下面我用query expression的方法实现:
var processes = from process in Process.GetProcesses() where process.WorkingSet64 >= 1024 * 1024 * 10 orderby process.WorkingSet64 descending select new { process.Id, Name = process.ProcessName, Memory = process.WorkingSet64 };
哈哈,这样看起来是不是更简单呢,更偏向于我们的思维呢?