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》

原文地址:https://www.cnblogs.com/tian2010/p/2399513.html