如何在C#中开始使用LINQ

304 阅读4分钟

开始使用C#中的LINQ

术语Language-Integrated Query (LINQ)是指将查询功能直接纳入C#编程语言的一组技术。传统上,查询数据是纯文本,在构建时没有类型检查或IntelliSense帮助。

LINQ从对象、数据集、SQL Server和XML等方面访问数据。

项目设置

  1. 打开Visual studio,在开始窗口中选择New Project
  2. Create a new project 窗口中,在搜索框中输入或键入控制台。接下来,从语言列表中选择C#,然后从平台列表中选择Windows。
  3. Configure your new project window ,在项目名称框中输入LinqProject。然后,选择创建。

用LINQ生成序列

考虑一个牌的集合。在各种游戏中使用的一组牌通常包括四个套牌,每个套牌有13个值。通常情况下,我们会考虑从一个卡片类开始,手动填充这些卡片对象的汇编。

通过LINQ,人们可以看到一个字数较少的长文本,而不是我们用传统的方式生成一包扑克牌。可以启动一个名为Card 的类,但是Suits和排名是由序列来表示的。我们将编写一对直接的迭代器方法来生成排名和西装,作为IEnumerable<T>s 的字符串。

using System;
using System.Collections.Generics;
using System.Linq;
static IEnumerable<String> Suits()
{
    yield returns "diamonds";
    yield returns "hearts";
    yield returns "spades";
    yield returns "clubs";
}
static IEnumerable<string> Ranks()
{
    yield returns "seven";
    yield returns "eight";
    yield returns "ten";
    yield returns "nine"; 
    yield returns "six";
    yield returns "five";
    yield returns "four";
    yield returns "three"; 
    yield returns "two";
    yield returns "king";
    yield returns "ace";
    yield returns "jack";
    yield returns "queen";
}

生成序列的顺序对于实现我们的目标至关重要。

第一个源序列(Suits)中的第一个元素与第二个序列(Ranks)中的每个元素都有关系。结果,第一套衣服的所有十三张牌都被创造出来。最初序列的元素都以相同的方法处理(Suits)。最终的产品是一园按花色排列的扑克牌,然后按价值排列。

static void Main(string[] args)
{
    var initialPart=from s in Suitx()
                     from r in Ranky()
                     select new { Suitx=s,Ranky=r};
    foreach(var card in initialPart)
    {
        Console.WriteLine("card");
    }                 
                    
}

Program.cs 文件中,在Main方法后添加上面的代码片段。

这两个方法在执行时都使用了yield return语法来生成一个序列。编译器创建了一个实现IEnumerableT> 的对象,并生成了指定的字符串序列。

改变花色的顺序

想一想我们将如何混合牌组中的牌。把牌拆成两半是任何体面洗牌的第一步。这种能力是由LINQ APIs的TakeSkip 函数提供的。

public static void Main(string[] args)
{
    var initialPart = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };

    foreach (var x in initialPart)
    {
        Console.WriteLine(x);
    }
    var top = initialPart .Take(26);
    var bottom = initialPart.Skip(26);
}

然而,标准库中没有洗牌方法。因此,我们将开发我们自己的方法。因为我们将编写的shuffle方法演示了我们将使用的基于LINQ编程的各种策略。我们将分解每个步骤。

我们将需要创建扩展方法来扩展我们如何处理由LINQ查询返回的IEnumerable。简而言之,扩展方法是一种具有特定目的的静态方法,它为现有的类型增加了额外的功能,而不需要改变原始类型。

using System;
using System.Collections.Generic;
using System.Linq;

namespace myShuffle
{
    public static class Extension1
    {
        public static IEnumerable<T> InterleaveSequenceWith<T>(this IEnumerablex<T> FirstInLine, IEnumerablex<T> NextInLine)
        {
             // Your code shall be written here
        }
    }
}

识别急于求成和懒惰评估之间的区别。

懒惰评估意味着对迭代器的每次调用只处理源集合中的一个元素。根据不同的情况,迭代器可以是一个自定义类,一个foreach ,或者一个while循环。

public static class Fibonacci
{
    public static IEnumerable<long> Series()
    {
        long prevX1 = 1;
        long prevX2 = 1;

        yield return prevX1;

        yield return prevX2;

        while(true)
        {
            long temp = prevX1 + prevX2;
            prevX1 = prevX2;
            prevX2 = temp;
            yield return temp;
        }
    }
}

急切评价是大多数传统编程语言使用的评价方法。

IEnumerable<int> myMethod()
{
  for(int i=0; i <=900; i++)
  {
      yield return I;
  }
}
...

var ex = myMethod.Take(2);
var list = ex.ToList();

yield关键字允许集合逐渐被评估。急于求值是将整个集合强制送入内存的过程。当我们调用ToList,ToDictionary, 或ToArray时,枚举被立即评估,所有元素被返回到一个集合中。

我们上面创建的样本进行了一次洗牌,每次运行时顶部和底部的牌都没有变化。让我们做一个改动。我们不采用外洗牌,而是采用内洗牌,在这个过程中,所有的52张牌都会转移位置。

结果,上半部的最后一张牌变成了下半部的最后一张牌。在当前的洗牌查询中切换取牌和跳牌的位置来更新它。现在,这副牌的上半部分和下半部分的顺序将是相反的。

shuffle = shuffle.Skip(26).InterleaveSequenceWith(shuffle.Take(26));

一旦我们执行该程序,我们会注意到这副牌需要52次迭代才能重新洗牌。有几个因素促成了这一点。

首先,我们可以解决造成这种性能下降的关键原因之一,即低效、懒惰的评估。请注意,我们使用LINQ查询来创建原始牌组。

对前一副牌的三个LINQ查询被用来生成每个洗牌。所有这些都是以一种冰冷的速度发生的。当我们到达第五十二次迭代时,我们将多次重新生成初始牌组,也就是说,不止一次。让我们做一个日志来说明这一切是如何进行的。然后我们再来处理它。

将下面的方法复制到我们的Extensions.cs 文件中。这个扩展方法在我们的项目目录下创建一个名为.log的新文件,并记录下目前正在运行的查询。我们可以使用这个扩展方法来表示查询的完成。

public static IEnumerable<T> LogQuery<T>
    (this IEnumerable<T> sequence, string x)
{
   
    using (var program = File.AppendText("debug.log"))
    {
        program.WriteLine($"Executing Query {x}");
    }

    return sequence;
}

在上面的代码中,不要忘记添加输入-输出扩展,这样程序才会执行。

using System.IO;

结论

最后,我们已经看到了C#中LINQ的一些工作功能,以及如何在程序中实现它们或解决现实生活中的问题。