开始使用C#中的LINQ
术语Language-Integrated Query (LINQ)是指将查询功能直接纳入C#编程语言的一组技术。传统上,查询数据是纯文本,在构建时没有类型检查或IntelliSense帮助。
LINQ从对象、数据集、SQL Server和XML等方面访问数据。
项目设置
- 打开Visual studio,在开始窗口中选择
New Project。 - 在
Create a new project窗口中,在搜索框中输入或键入控制台。接下来,从语言列表中选择C#,然后从平台列表中选择Windows。 - 在
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的Take 和Skip 函数提供的。
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的一些工作功能,以及如何在程序中实现它们或解决现实生活中的问题。