查询表达式与循环控制

系统 1573 0

    For,do… while,while ,foreach是大多数编程语言中常用的循环控制语句,在C#中查询表达式也能实现同样的效果。

    查询表达式使得编程风格从”命令式”变得更加的”声明式”。查询表达式定义想要的结果以及要达到该结果需要执行的方法,而不是具体的如何实现。这里重点是查询表达式,通过扩展方法,查询表达式使得能够比命令式循环构造更能够清楚的表达意图。

    下面的语句显示了一个命令式风格的填充一个数组并打印到控制台上:

      
        
          int
        
        [] foo = 
        
          new int
        
        [100];


        
          for 
        
        (
        
          int 
        
        num = 0; num < foo.Length; num++)

{

   foo[num] = num * num;

}


        
          foreach 
        
        (
        
          int 
        
        i 
        
          in 
        
        foo)

{

   
        
          Console
        
        .WriteLine(i.ToString());

}  
      
    
即使实现这么一小点功能,依然注重的是如何实现而不是怎样实现,将这段代码改为查询表达式能够使得代码更易读和使用:
      
        
          int
        
        [] foo = (
        
          from 
        
        n 
        
          in 
        
        
          Enumerable
        
        .Range(0, 100) 
        
          select 
        
        n * n).ToArray();
      
    
第二个循环可以利用扩展方法改写为:
      
        foo.ForAll(n => 
        
          Console
        
        .WriteLine(n));
      
    

或者更为简洁的:

      foo.ForAll(
      
        Console
      
      .WriteLine); 
    

在.NET BCL中ForEach扩展方法有针对List<T>的实现方法。我们可以添加一个针对IEnumerable<T>的ForAll扩展方法实现如下:

      
        
          public static class 
        
      
      
        
          Extensions 
        
        {

    
        
          public static void 
        
        ForAll<T>(
        
          this 
        
        
          IEnumerable
        
        <T> sequence, 
        
          Action
        
        <T> action)

    {

        
        
          foreach 
        
        (T item 
        
          in 
        
        sequence)

        {

            action(item);

        }

    }

}
      
    

  可能看起来不起眼,只不过是一个扩展方法而已,但是这能够使得代码能够更多的重用,任何时候要对一个序列元素执行操作,ForAll就能够派上用场。

    以上问题可能看上去比较简单,不足以看出使用查询语法所带来的好处,下面来看个复杂一点的。

很多操作需要嵌套的循环操作,假设需要产生一个从0到99的(X,Y)对,通常的做法是:

      
        
          public static 
        
        
          IEnumerable
        
        <
        
          Tuple
        
        <
        
          Int32
        
        , 
        
          Int32
        
        >> ProduceIndices()

{

   
        
          for 
        
        (
        
          int 
        
        x = 0; x < 100; x++)

   {

      
        
          for 
        
        (
        
          int 
        
        y = 0; y < 100; y++)

      {

          
        
          yield return 
        
        
          Tuple
        
        .Create(x, y);

      }

   }

}
      
    

或者改为查询表达式:

      
        
          public static 
        
        
          IEnumerable
        
        <
        
          Tuple
        
        <
        
          Int32
        
        , 
        
          Int32
        
        >> ProduceIndices()

{

    
        
          return from 
        
        x 
        
          in 
        
        
          Enumerable
        
        .Range(1, 100)

           
        
          from 
        
        y 
        
          in 
        
        
          Enumerable
        
        .Range(1, 100)

           
        
          select 
        
        
          Tuple
        
        .Create(x, y);

}
      
    

两者看起来差不多,但是随着问题的复杂,查询表达式仍然能够保持简洁。现在将问题改为:只产生x和y相加小于100的点对,比较两者的实现:

      
        
          public static 
        
        
          IEnumerable
        
        <
        
          Tuple
        
        <
        
          Int32
        
        , 
        
          Int32
        
        >> ProduceIndices2()

{

    
        
          for 
        
        (
        
          int 
        
        x = 0; x < 100; x++)

    {

        
        
          for 
        
        (
        
          int 
        
        y = 0; y < 100; y++)

        {

            
        
          if 
        
        (x+y<100)

                
        
          yield return 
        
        
          Tuple
        
        .Create(x, y);

        }

    }

}




        
          public static 
        
        
          IEnumerable
        
        <
        
          Tuple
        
        <
        
          Int32
        
        , 
        
          Int32
        
        >> ProduceIndices2()

{

    
        
          return from 
        
        x 
        
          in 
        
        
          Enumerable
        
        .Range(1, 100)

           
        
          from 
        
        y 
        
          in 
        
        
          Enumerable
        
        .Range(1, 100)

           
        
          where 
        
        x+y<100

           
        
          select 
        
        
          Tuple
        
        .Create(x, y);

}
      
    

 

差距仍不明显,但是命令式语句开始隐藏我们想要的意图。再将问题变得复杂一些。现在,我们需要将返回的点按照距离原点位置的距离降序排列。

      
        
          public static 
        
        
          IEnumerable
        
        <
        
          Tuple
        
        <
        
          Int32
        
        , 
        
          Int32
        
        >> ProduceIndices3()

{

    
        
          var 
        
        storage = 
        
          new 
        
        
          List
        
        <
        
          Tuple
        
        <
        
          int
        
        , 
        
          int
        
        >>();

    
        
          for 
        
        (
        
          int 
        
        x = 0; x < 100; x++)

    {

        
        
          for 
        
        (
        
          int 
        
        y = 0; y < 100; y++)

        {

            
        
          if 
        
        (x+y<100)

            storage.Add(
        
          Tuple
        
        .Create(x, y));

        }

    }

    storage.Sort((point1,point2)=>

                (point2.Item1*point2.Item1+point2.Item2*point2.Item2).CompareTo

                (point1.Item1 * point1.Item1 + point1.Item2 * point1.Item2)

                );

    
        
          return 
        
        storage;

}




        
          public static 
        
        
          IEnumerable
        
        <
        
          Tuple
        
        <
        
          Int32
        
        , 
        
          Int32
        
        >> ProduceIndices3()

{

    
        
          return from 
        
        x 
        
          in 
        
        
          Enumerable
        
        .Range(1, 100)

           
        
          from 
        
        y 
        
          in 
        
        
          Enumerable
        
        .Range(1, 100)

           
        
          where 
        
        x + y < 100

           
        
          orderby 
        
        (x * x + y * y) 
      
      
        
          descending select 
        
        
          Tuple
        
        .Create(x, y);

}
      
    

   现在,差距开始出现了,命令式的风格时的代码难以理解,如果不认真的看,很难一下子明白比较函数后面一堆东西返回的是什么。如果没有注释的话,这段命令式代码比较难懂。命令式风格的代码过多的强调了代码执行的过程,很容易在这复杂的过程中迷失我们最初想要达到的目的。

   查询表达式比循环控制结构更好的一点是:查询表示式能够更好的组合,他能够将算法组织在一个很小的精简的片段内来执行一系列的操作。查询的惰性执行模型也使得开发者能够在一个循环类执行一系列的操作。而查询表达式则不能做到这一点。你必须存储每一次循环的结果,然后构造新的循环操作来执行上一次的操作结果。

   最后一个例子演示了如何工作。操作组合了过滤(where子句),排序(orderby 子句)以及投影(select语句),所有这些操作在一次遍历操作中完成。而命令式版本的方法需要创建一个零时的存储对象storage,然后对这个对象分别执行一些列操作。

每一个查询表达式有一个对应的方法调用是的表达式,有时候,查询表达式更自然有时候方法调用是的表达式更自然。显然在上面的例子中,查询表达式版本更易读,下面是方法调用的版本:

      
        public static 
      
      
        IEnumerable
      
      <
      
        Tuple
      
      <
      
        Int32
      
      , 
      
        Int32
      
      >> ProduceIndices3()

{

    
      
        return 
      
      Enumerable.Range(1, 100).

            SelectMany(x => Enumerable.Range(1, 100),

            (x, y) => 
      
        Tuple
      
      .Create(x, y)).Where(pt => pt.Item1 + pt.Item2 < 100).

            OrderByDescending(pt => pt.Item1 * pt.Item1 + pt.Item2 * pt.Item2);

}
    

   在这个例子中,查询表达式可能比方法表达式更易读,但其他例子可能不同。有些方法表达式并没有对应的查询表达式。一些方法,如Take,TakeWhile,Skip,SkipWhile,Min,以及Max在一些情况下需要使用方法表达式。

   有些人可能会人会查询表达式在执行速度上比循环执慢,这一点要看具体的情况。有时候清晰的代码可能比速度更重要。在改进程序算法之前,可以考虑LINQ的并行扩展库PLINQ,可以使用.AsParallel()来使的查询操作能够并行执行。

   C#最开始是一种命令式编程语言,你能够使用之前最熟悉的方法去编写代码。但是这些传统的方法可能不是最好的。当你发现需要使用循环结构去执行某种操作的时候,试试能够将它改写为查询表达式的方式,如果查询表达式不起作用,试试方法表达式,在大多数情况下,你会发现使用查询表达式或者方法表达式会比使用传统的循环式的命令式结构能使得代码变得更加简洁和清晰。

查询表达式与循环控制


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论