首页 > 代码库 > 什么是LINQ?(下)
什么是LINQ?(下)
先简单学习一下LINQ To Objects。对LINQ To Objects来说,LINQ运算符只不过是一系列定义在IEnumerable<T>上的扩展方法。IEnumerable<T>接口是所有可以遍历的集合的基础接口,由此可见其应用范围广泛。LINQ To Objects的扩展方法定义在 System.Core.dll程序集中,位于System.Linq命名空间下的Enumerable类型下。
这些扩展方法的返回值有:集合类型IEnumerable<TSource>,集合成员类型如ElementAt(),其它集合类型如ToList(),一个标量值如Count。很多扩展方法需要接受一个Func(TSource, TResult> 或者 Func<TSource, bool>委托类型参数,这种情况可以传递一个Lambda表达式进去。对于那些返回值为IEnumerable<T>的方法,我们可以对它进行一种链式调用,如list.Where().Orderby().ToList()。
下面是使用查询表达式,使用链式调用,和混用两种方式来写一条查询语句。可见当使用链式方法调用时,应该传递进去一个Lambda表达式。
ProductCollection collection = Product.GetSampleCollection ();//第一种var query = from x in collection where x._name=="beer" orderby x.code select new { Title = x._name.ToUpper(), code = x.code};//第二种var query = collection.Where (x => x._name == "beer").OrderBy (x=>x.code).Select (x=>new { Title = x._name.ToUpper (), code = x.code});//第三种var query = from x in collection.Where (x=>x._name == "beer") orderby x.code select new { Title = x._name.ToUpper(), code = x.code};foreach (var item in query) { Debug.Log(string.Format("{0},{1}",item.Title ,item.code)); }
并不是所有方法都有对应的查询表达式,而且这些扩展方法一般都有多个重载,某些重载方法不存在查询表达式。我们可以将两种查询方法结合使用,来解决这个问题。我们可以尽量使用某一个方法,在必要的时候才混合使用两种查询方法。
我们可以自己编写一个wherenew扩展方法用来筛选符合条件的元素。
using System.Collections;using System.Collections.Generic;using System;public static class WhereExtension { public static IEnumerable<TSource> WhereNew<TSource>(this IEnumerable<TSource> source, Func<TSource , bool> predicate){ if(source == null) throw new ArgumentNullException(); if(predicate == null) throw new ArgumentNullException(); List<TSource> inner = new List<TSource>; foreach (TSource item in source) { if(predicate(item)) inner.Add(item); } return inner; }}
查询一下,得到预计的输出。
var query2 = collection.WhereNew (x => x._name == "beer");foreach (var item in query2) { Debug.Log(item.ToString());}
但是这里有两个问题。WhereNew()方法不被查询表达式支持,而且也不支持延迟加载。所谓延迟加载就是当编写一个LINQ查询时,实际上并没有立即执行查询操作,而是在每次遍历集合或者在集合上调用Count()、Max()等聚合函数(聚合函数是一组执行运算并返回单一值的函数)时都执行查询操作。
下面简单介绍一些基本的查询方法。
1、where的重载,根据下标index来筛选
var query3 = collection.Where ((x,index) => index >= 0 && index < 3);foreach (var item in query3) { Debug.Log(item.ToString());}
2、ofType() :根据类型筛选。ofType()定义在IEnumerable上,而不是IEnumerable<T>上。它的作用是从非泛型集合上筛选出某一类型,然后返回一个IEnumerable<T>。
ArrayList array = new ArrayList ();array.Add (100);array.Add ("jack");array.Add ("rose");array.Add (DateTime.Now);var query4 = array.OfType<string> ().Where (x => x.StartsWith ("ro"));foreach (var item in query4) { Debug.Log(item);}
3、Cast()也定义在IEnumerable上,它用于将非泛型的序列转换成泛型序列IEnumerable<T>,若无法转换则抛出异常。
var query5 = array.Cast<int> ();//InvalidCastException: Cannot cast from source type to destination type.foreach (var item in query5) { Debug.Log(item.ToString());}
4、OrderBy()和ThenBy() 以及OrderByDescending()和ThenByDescending()。这里要注意ThenBy()用来根据键值再次排序,是在IOrderedEnumerable<T>上定义的扩展方法,因此不能直接在IEnumerable<T>上调用,而只能在OrderBy()、OrderByDescending()ThenBy() ThenByDescending()后面调用。我们也可以通过OrderBy()的重载方法实现再次排序(依据多个键值进行排序,假如A相等,则比较B,以此类推)。
var query6 = collection.OrderBy (x => x.id).ThenByDescending (x => x.code);foreach (var item in query6) { Debug.Log(item.ToString());}
5、Select()是一种投影操作,可以进行对象转换,Select()也有一个重载的方法,包含了序列的序号index。我们也可以把序列投影成JSON的键值对形式。
var query7 = collection.Select ((x,index) => new {Title = x._name, Index = index, Code = x.code});foreach (var item in query7) { Debug.Log(item);}
6、Take()和Skip()。Take()返回前N个元素构成的新序列,Skip()正好相反,返回第N个元素后面的所有元素构成的序列。Take()和Skip()是根据一个序号返回前几个或第几个往后的元素。TakeWhile()与SkipWhile()则根据条件判断来返回元素,只是顺序和Take()、Skip()类似,但是判断条件截然不同。TakeWhile()查找满足条件的元素,并且在遇到第一个不符合条件的元素之后停止查找,返回前面已经找到的元素。SkipWhile()正好相反,一直跳过符合条件的元素,在遇到第一个不符合条件的元素后停止查找,返回该元素之后的所有元素构成的序列。
var query8 = collection.Take (3);var query9 = collection.SkipWhile (x => x.id >= 6);foreach (var item in query8) { Debug.Log(item.ToString());}Debug.Log ("-------------------");foreach (var item in query9) { Debug.Log(item.ToString());}
7、Reverse()用于将元素中的序列逆序排列。
int[] _array = {1,2,3,6,4,5};var query10 = _array.Reverse ();foreach (var item in query10) { Debug.Log(item);}
8、DefaultIfEmpty()。假如对象是空的,就使用元素的默认值或指定元素值。注意结构类型默认为0,引用类型默认为null。
int array = {};var query = array.DefaultIfEmpty(100);//输出100
9、Distinct()用于剔除序列中的重复元素,并返回其余所有元素。注意对于自定义的类型,要去进行判等比较必须要实现IEqualityComparer<T>接口,它包含一个Equals方法,然后使用Distinct的重载方法,传递一个IEqualityComparer<T>类型进去。
int array = {1,2,2,5,6,5};var query = array.Distinct();
10、GroupBy()的结果是一个树形结构,既序列中的元素仍然是一个序列。GroupBy()返回了一个IEnumerable<IGrouping<TKey,TSource>>接口类型。IGrouping<TKey,TSource>也是一个序列,但是多了一个Key属性,并且依据Key进行分组,由原序列的一部分组成。Key就是按照谁进行的分组,内部序列是TSource类型,外部序列是IGrouping<TKey,TSource>类型。我们还可以使用GroupBy()的重载方法来使用自定义序列定义分组后的内部序列。
var query11 = collection.GroupBy (x => x._name);foreach (var item in query11) { Debug.Log("--------------------------------------"); foreach (var _item in item) { Debug.Log(_item.ToString()); }}
11、Intersect()和Except()。Intersect()返回两个序列中相同元素构成的序列。对于自定义类型仍然要使用重载方法并且传递一个实现了判等接口的对象进去。Except()与Intersect()相反,返回第一个序列中有而第二个序列中没有的元素。
int[] array1 = {1,2,3,4};int[] array2 = {2,4,6,8};var query = array1.Intersect(array2);
12、Concat()和Union()。Concat()用于连接两个序列。Union()也用于连接两个序列,但会剔除相同的元素。
var query = array1.Concat(array2);
13、Zip()对两个序列中位置相同的元素进行操作,基于操作结果返回一个新的元素。如果两个序列长度不等,则以短的为准。
int[] array1 = {1,2,3,4,5};string[] array2 = {"星期一","星期二","星期三"};var query = array1.Zip(array2,(x,y)=>String.Format("{0},{1}",x,y));
14、转换成其它类型;ToArray()、ToList()、ToDictionary()、ToLookUp()。在转换时,会进行遍历,因此这些方法没有执行延迟加载。
15、返回序列中的元素。
ElementAt()和ElementAtOrDefault()。ElementAt()以元素的位置作为参数,返回该元素。若不存在则抛出异常。ElementAtOrDefault()在该元素不存在时返回默认值。
First()返回满足条件的第一个元素,若不存在则抛出异常。FirstOrDefault()在元素不存在时返回默认值。
Last()返回满足条件的最后一个元素。LastOrDefault()类似。
Single()要求序列中只有一个满足条件的项,如果有超过一个则会抛出异常。SingleOrDefault()要求有一个或零个满足条件的项,当满足条件的项为零个时,返回默认值。Single()如果不使用参数,则当序列有且仅有一个元素时,返回该元素,否则抛出异常。
16、返回标量。
Count()和LongCount()返回int和long类型的序列元素数目。
Max()、Min()、Average()、Sum()进行一些计算。
Aggregate()用于一些自定义的计算。如:
string sentence = "the quick brown fox jumps over the lazy dog";string[] words = sentence.Split(‘ ‘);string reversed = words.Aggregate((workingSentence, next) => next + " " + workingSentence);Console.WriteLine(reversed);
这个表达式的意思就是next是字符串数组的元素,workingSentence是我们用来运算的变量,workingSentence = next + " "+workingSentence。workingSentence的变化:the => quick the => brown quick the => …… 我们还可以指定该变量的初始值。
Contains()、An()、All()、SequenceEqual()返回布尔类型,用作条件判断。Contains()判断某个元素是否存在。Any()判断序列中是否存在满足表达式的元素,只要有一个满足,就返回TRUE。当调用无参数的重载时,Any()用于判断序列中是否包含元素。All()用于判断序列中是否所有元素都满足表达式。只要有一个元素不满足,则返回FALSE。SequenceEqual()逐项比较两个序列,只有当两个序列的元素数目相同,而且序列中相同位置的元素也相同时,才返回TRUE。
int array = {0,1,2};var query = array.Any(x=>x>=2);var query2 = array.Contains(2);var query3 = array.All(x=>x>0);
17、静态非扩展方法。
有一些方法直接定义在System.Linq.Enumerable上面,而不是扩展方法。比如Empty()用于创建一个空的序列。Range()只能用于int类型,用于生成由连续数字构成的序列。Repeat()用于生成重复数字。
IEnumerable<string> list1 = Enumerable.Empty<string> ();IEnumerable<int> list2 = Enumerable.Range (0, 12);IEnumerable<int> list3 = Enumerable.Repeat (10, 5);Debug.Log (list1.GetType().FullName);Debug.Log (list2.GetType().FullName);Debug.Log (list3.GetType().FullName);foreach (var item in list2) { Debug.Log(item);}
什么是LINQ?(下)