Where 子句的用法

我们除了可以如下方式书写带Where子句的LINQ外:

from p in products where p.UnitsInStock > 0 && p.UnitPrice > 3.00M select p;

还可以对数组(所有实现了IEnumerable接口的对象都可以)的实体使用 Where 扩展方法。

 

把一个查询语句写成多个扩展函数的方式,这其实是编译器处理查询语句的方法,比如下面的查询语句:

int[] arr = new int[] { 8, 5, 89, 3, 56, 4, 1, 58 };

var m = from n in arr where n < 5 orderby n select n;

编译器在编译后,替我们产生的代码等价于如下的代码:

IOrderedSequence<int> m = arr.Where<int>(delegate (int n) {

    return (n < 5);

}).OrderBy<int, int>(delegate (int n) {

    return n;

});

 

下面我们来看一个使用Where扩展方法的例子:

我们有一个字符串数组,一次是0到9的英文单词,我们查询出这10个字符的长度比它所在数组的位置 这两个数字比较小的英文单词.

这个查询可能有些绕口,你可以先看下面这些代码:

public static void LinqDemo01()

{

    string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };

    var shortDigits = digits.Where((dd, aa) => dd.Length < aa);

    Console.WriteLine("Short digits:");

    foreach (var d in shortDigits)

        Console.WriteLine("The ​​word​​ {0} is shorter than its value.", d);

}

输出结果:

Short digits:

The ​​word​​ five is shorter than its value.

The ​​word​​ six is shorter than its value.

The ​​word​​ seven is shorter than its value.

The ​​word​​ eight is shorter than its value.

The ​​word​​ nine is shorter than its value.

 

下面我们就来分析上述代码中最核心的代码:

digits.Where((dd, aa) => dd.Length < aa);

这行代码都赶了些什么?

 

1、Where子句其实是用扩展方法来实现的

如果你对扩展方法不熟悉,请先看我之前的几篇博客:

​C#3.0 中的扩展方法 (Extension Methods)​

​C#3.0 中使用扩展方法来扩展接口​

​Orcas Beta1 对多个同名扩展方法的处理逻辑​

微软替我们实现的 Where 子句对应的扩展函数实际是如下的定义:

namespace System.Linq

{

    public delegate TResult Func<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1);

    public static class Enumerable

    {

        public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

        public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);

    }

}

其中红色字体的那个扩展函数,就是我们上面代码实际使用的扩展函数。

我们这个扩展函数参数:Func<TSource, int, bool> predicate 的定义看上面代码的绿色delegate 代码。

 

2、Where 子句参数书写的是Lambda 表达式

如果你不清楚什么是Lambda 表达式,你可以参看我之前的博客:

​C# 3.0 的Lambda表达式(Lambda Expressions)​

(dd, aa) => dd.Length < aa 就相当于 ​​c#​​ 2.0 的匿名函数。

LINQ中所有关键字比如 Select,SelectMany, Count, All 等等其实都是用扩展方法来实现的。上面的用法同样也适用于这些关键字子句。

 

3、这个Where子句中Lambda 表达式第二个参数是数组索引,我们可以在Lambda 表达式内部使用数组索引。来做一些复杂的判断。

具有数组索引的LINQ关键字除了Where还以下几个Select,SelectMany, Count, All

我们下面就来依次举例

 

Select 子句使用数组索引的例子

下面代码有一个整数数组,我们找出这个数字是否跟他在这个数组的位置一样

public static void LinqDemo01()

{

    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

    var numsInPlace = numbers.Select((num, index) => new { Num = num, InPlace = (num == index) });

    Console.WriteLine("Number: In-place?");

    foreach (var n in numsInPlace)

        Console.WriteLine("{0}: {1}", n.Num, n.InPlace);

}

输出结果:

Number: In-place?

5: False

4: False

1: False

3: True

9: False

8: False

6: True

7: True

2: False

0: False

其中我们用到的这个Select子句对应的扩展函数定义,以及其中Func<TSource, int, TResult>委托定义如下:

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector);

public delegate TResult Func<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1);

 

SelectMany 子句使用数组索引的例子

几个句子组成的数组,我们希望把这几个句子拆分成单词,并显示每个单词在那个句子中。查询语句如下:

public static void Demo01()

{

    string[] text = { "Albert was here",

          "Burke slept late",

          "Connor is happy" };

    var tt = text.SelectMany((s, index) => from ss in s.Split(' ') select new { ​​word​​ = ss, Index = index });

    foreach (var n in tt)

        Console.WriteLine("{0}:{1}", n.Word,n.Index);

}

结果:

Albert:0

was:0

here:0

Burke:1

slept:1

late:1

Connor:2

is:2

happy:2

 

SkipWhile 子句使用数组索引的例子

SkipWhile 意思是一直跳过数据,一直到满足表达式的项时,才开始返回数据,而不管之后的项是否仍然满足表达式,需要注意他跟Where是不一样的,Where是满足条件的记录才返回,SkipWhile 是找到一个满足条件的,然后后面的数据全部返回。

下面例子返回一个整数数组中,这个整数比他自身在这个数组的位置大于等于的第一个位置以及之后的数据。

public static void Linq27()

{

    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

    var laterNumbers = numbers.SkipWhile((n, index) => n >= index);

    Console.WriteLine("All elements starting from first element less than its position:");

    foreach (var n in laterNumbers)

        Console.WriteLine(n);

}

输出结果:

All elements starting from first element less than its position:

1

3

9

8

6

7

2

0

 

First 、FirstOrDefault、Any、All、Count 子句

注意:

​101 LINQ Samples​​ 中 First - Indexed、FirstOrDefault - Indexed、 Any - Indexed、All - Indexed、Count - Indexed 这五个例子在 Orcas Beta1中已经不在可用,即下面代码是错误的。

​public void Linq60() {
    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
    int evenNum = numbers.First((num, index) => (num % 2 == 0) && (index % 2 == 0));
    Console.WriteLine("{0} is an even number at an even position within the list.", evenNum);
}​

​public void Linq63() {
    double?[] doubles = { 1.7, 2.3, 4.1, 1.9, 2.9 };
    double? num = doubles.FirstOrDefault((n, index) => (n >= index - 0.5 && n <= index + 0.5));
    if (num != null)
        Console.WriteLine("The value {1} is within 0.5 of its index position.", num);
    else
        Console.WriteLine("There is no number within 0.5 of its index position.", num);
}​

​public void Linq68() {
   int[] numbers = { -9, -4, -8, -3, -5, -2, -1, -6, -7 };
   bool negativeMatch = numbers.Any((n, index) => n == -index);
   Console.WriteLine("There is a number that is the negative of its index: {0}", negativeMatch);
}​

​public void Linq71() {
   int[] lowNumbers = { 1, 11, 3, 19, 41, 65, 19 };
   int[] highNumbers = { 7, 19, 42, 22, 45, 79, 24 };
   bool allLower = lowNumbers.All((num, index) => num < highNumbers[index]);
   Console.WriteLine("Each number in the first list is lower than its counterpart in the second list: {0}", allLower);
}​

​public void Linq75() {
   int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
   int ddEvenMatches = numbers.Count((n, index) => n % 2 == index % 2);
   Console.WriteLine("There are {0} numbers in the list whose odd/even status " +
        "matches that of their position.", oddEvenMatches);
} ​

要实现这个功能,可以用Where 子句,如下:

public static void Linq60()

{

    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

    int evenNum = numbers.Where((num,index) =>( num % 2 == 0 && index %2 == 0) ).First();

    Console.WriteLine("{0} is an even number at an even position within the list.", evenNum);

}

public static void Linq63()

{

    double?[] doubles = { 1.7, 2.3, 4.1, 1.9, 2.9 };

    double? num = doubles.Where((n, index) => (n >= index - 0.5 && n <= index + 0.5)).FirstOrDefault();

    if (num != null)

        Console.WriteLine("The value {1} is within 0.5 of its index position.", num);

    else

        Console.WriteLine("There is no number within 0.5 of its index position.", num);

}

public static void Linq68()

{

    int[] numbers = { -9, -4, -8, -3, -5, -2, -1, -6, -7 };

    bool negativeMatch = numbers.Where((n, index) => n == -index).Any();

    Console.WriteLine("There is a number that is the negative of its index: {0}", negativeMatch);

}

public static void Linq71()

{

    int[] lowNumbers = { 1, 11, 3, 19, 41, 65, 19 };

    int[] highNumbers = { 7, 19, 42, 22, 45, 79, 24 };

    bool allLower = lowNumbers.Where((num, index) => num < highNumbers[index]).All(n => true);

    Console.WriteLine("Each number in the first list is lower than its counterpart in the second list: {0}", allLower);

}

public static void Linq75()

{

    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

    int ddEvenMatches = numbers.Where((n, index) => n % 2 == index % 2).Count();

    Console.WriteLine("There are {0} numbers in the list whose odd/even status " +

         "matches that of their position.", oddEvenMatches);

} ​

作者:​​菩提树下的杨过​

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。