LINQ之延迟执行标准查询操作符(上)
标准查询操作符(Standard Query Operator)和查询表达式(Query Expression)是实现LINQ查询2种方式。
通过查看IL代码,我们会发现查询表达式编译后也是转换成标准查询操作符的,并且有些查询时无法用查询表达式来操作的,
因此标准查询操作符显得格外重要,我们将分几次介绍他们。
大多数的标准查询操作符静态类:System.Linq.Enumerable的扩展方法,并且将IEnumerable作为其第一个参数。
标准查询操作符包括两种类型:延迟执行和立即执行。我们将分别介绍他们。先介绍延迟执行吧。
操作符:Where
描述:用于包含/过滤数据
类型:延迟执行
原型:2种
第一种原型:
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
这种原型有2个输入参数,但是由于这是一个扩展方法,因此事实上我们不用传如序列作为第一个参数,
第二个参数是委托,委托的输入参数的类型跟枚举的数据项的类型是一样的,在这个例子中为TSource,
我们可以使用lambda表达式,举个例子:
int[] nums = new int[] { 4, 6, 7, 1, 0, 23, 5, 9, 12 }; var evenNums = nums.Where(item => item%2 == 0); foreach (var evenNum in evenNums) { Console.WriteLine(evenNum); }
对比之前的where语句,是不是变得更简单了呢?
第二种原型:
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate )
第二种原型跟第一种原型的唯一区别是,委托方法多了一个int类型的输入参数,该参数是一个索引,代表数据源的索引,跟C#中的大部分索引一样,都是基于0开始的索引。
举个例子,还是上面的数据源,找出奇数位置上的数字:
int[] nums = new int[] { 4, 6, 7, 1, 0, 23, 5, 9, 12 }; var evenNums = nums.Where((p,i) => (i & 1) == 0); foreach (var evenNum in evenNums) { Console.WriteLine(evenNum); }
在这个例子中,我们没有用到元素p本身。
操作符:Select
描述:也称作投射,用于产生选择后的元素或者从原有序列中产生出新的元素序列,新的序列类型可能跟原有数据源的类型不一致
类型:延迟执行
原型:2种
第一种原型:
public static IEnumerable<TResult> Select<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector )
这种原型接受一个输入数据源和选择器方法委托作为输入参数,同时返回一个新的,可能跟输入数据源元素类型不一样的对象,即TSource和TResult可能是不一样的。
e.g.
string[] allBrands = new string[] { "Exuviance", "Avene", "Baby Quasar", "Ecoya", "Alterna", "Ecru New York" }; var brandsLength = allBrands.Select(b => b.Length); foreach (var length in brandsLength) { Console.WriteLine(length); }
在这个例子中,数据源是一个字符串数组,查询结果返回的是数组中每个字符串的长度。
再举个例子:
string[] allBrands = new string[] { "Exuviance", "Avene", "Baby Quasar", "Ecoya", "Alterna", "Ecru New York" }; var namedBrands = allBrands.Select(b => new {b,b.Length}); foreach (var namedBrand in namedBrands) { Console.WriteLine("{0} length:{1}",namedBrand.b, namedBrand.Length); }
这个例子中,返回的是一个新构建的对象。
第二种原型:
public static IEnumerable<TResult> Select<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, int, TResult> selector )
跟Where操作符的第二种原型类似,委托方法的第二个参数依旧为数据源序列的0基索引。
e.g.
string[] allBrands = new string[] { "Exuviance", "Avene", "Baby Quasar", "Ecoya", "Alterna", "Ecru New York" }; var namedBrands = allBrands.Select((b,i) => new { Index=i+1, b }); foreach (var namedBrand in namedBrands) { Console.WriteLine("{0}.{1}", namedBrand.Index, namedBrand.b); }
借助索引,我们很容易知道各个品牌的位置。
操作符:SelectMany
描述:将数据源中的每个元素投射成一个IEnumerable(Of T)并将结果序列合并成一个序列
类型:延迟执行
原型:官方提供了4种,这里主要介绍2种
第一种原型:
public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector )
这里我们借用msdn的例子来说明:
class PetOwner { public string Name { get; set; } public List<String> Pets { get; set; } }
PetOwner[] petOwners = { new PetOwner { Name="Higa, Sidney", Pets = new List<string>{ "Scruffy", "Sam" } }, new PetOwner { Name="Ashkenazi, Ronen", Pets = new List<string>{ "Walker", "Sugar" } }, new PetOwner { Name="Price, Vernette", Pets = new List<string>{ "Scratches", "Diesel" } } }; // Query using SelectMany(). IEnumerable<string> query1 = petOwners.SelectMany(petOwner => petOwner.Pets); // Only one foreach loop is required to iterate // through the results since it is a // one-dimensional collection. foreach (string pet in query1) { Console.WriteLine(pet); }
相比之下,如果我们用Select方法:
IEnumerable<List<String>> query2 = petOwners.Select(petOwner => petOwner.Pets); // Notice that two foreach loops are required to // iterate through the results // because the query returns a collection of arrays. foreach (List<String> petList in query2) { foreach (string pet in petList) { Console.WriteLine(pet); } Console.WriteLine(); }
首先,返回的结果集是不一样的,我们执行查询的时候,步骤也是不一样的。
第二种原型:
public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TResult>> selector )
跟之前的第二种原型类似,不再举例。
操作符:Take
描述:从序列中返回开始的临近几个元素
类型:延迟执行
原型:一种
public static IEnumerable<TSource> Take<TSource>( this IEnumerable<TSource> source, int count )
e.g.
int[] nums = new int[] { 4, 6, 7, 1, 0, 23, 5, 9, 12 }; var evenNums = nums.Take(3);
操作符:TakeWhile
描述:返回一个序列中的元素直到某个条件成立
类型:延迟执行
原型:2种
第一种原型:
public static IEnumerable<TSource> TakeWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
举个例,我们要从一个int类型的数组中找出所有的元素,直到我们第一次遇到0
int[] nums = new int[] { 4, 6, 7, 1, 0, 23, 5, 9, 12 }; var evenNums = nums.TakeWhile(item => item != 0); foreach (var evenNum in evenNums) { Console.WriteLine(evenNum); }
输出结果为:
4
6
7
1
显然,当遇到0时,查询结束
第二种原型增加一个索引参数,不再赘述。
操作符:Skip
描述:从一个序列中跳过指定数目的元素,注意,是从头开始跳过的,因此使用此方法与排序有关。
类型:延迟执行
原型:一种
public static IEnumerable<TSource> Skip<TSource>( this IEnumerable<TSource> source, int count )
e.g.
int[] nums = new int[] { 4, 6, 7, 1, 0, 23}; var evenNums = nums.Skip(3); foreach (var evenNum in evenNums) { Console.WriteLine(evenNum); }
输出结果为:
1
0
23
即,跳过了刚开始的三个数字
操作符:SkipWhile
描述:跳过所有的元素直到某个指定的条件不再成立,并返回剩下的元素
类型:延迟执行
原型:2种
原型一:
public static IEnumerable<TSource> SkipWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
e.g.
int[] nums = new int[] { 4, 6, 7, 1, 0, 23, 5, 9, 12 }; var evenNums = nums.SkipWhile(item => item<10); foreach (var evenNum in evenNums) { Console.WriteLine(evenNum); }
该例子返回的结果为:
23
5
9
12
因为,当遇到23时,item<10已经不再成立,因此Skip停止,并返回剩下的所有的元素
原型二与前类似。
参考:《Pro.LINQ.Language.Integrated.Query.in.Csharp.2010》