Trabalhando com LINQWorking with LINQ

IntroduçãoIntroduction

Este tutorial ensina os recursos no .NET Core e da linguagem C#.This tutorial teaches you features in .NET Core and the C# language. Você aprenderá:You’ll learn:

  • Como gerar sequências com LINQ.How to generate sequences with LINQ.
  • Como escrever métodos que podem ser facilmente usados em consultas LINQ.How to write methods that can be easily used in LINQ queries.
  • Como distinguir entre uma avaliação lenta e uma detalhada.How to distinguish between eager and lazy evaluation.

Você aprenderá essas técnicas ao compilar um aplicativo que demonstra uma das habilidades básicas de qualquer mágico: o embaralhamento faro.You'll learn these techniques by building an application that demonstrates one of the basic skills of any magician: the faro shuffle. Em resumo, um embaralhamento faro é uma técnica em que você divide um baralho de cartas exatamente na metade, então as cartas de cada metade são colocadas em ordem aleatória até recriar o conjunto original.Briefly, a faro shuffle is a technique where you split a card deck exactly in half, then the shuffle interleaves each one card from each half to rebuild the original deck.

Os mágicos usam essa técnica porque cada carta é fica em um local conhecido após o embaralhamento e a ordem é um padrão de repetição.Magicians use this technique because every card is in a known location after each shuffle, and the order is a repeating pattern.

Para os seus propósitos, vamos examinar rapidamente as sequências de manipulação de dados.For your purposes, it is a light hearted look at manipulating sequences of data. O aplicativo que você criará construirá um baralho de cartas e, em seguida, executará uma sequência de embaralhamento, sempre gravando a sequência de saída.The application you'll build will construct a card deck, and then perform a sequence of shuffles, writing the sequence out each time. Você também comparará a ordem atualizada com a ordem original.You'll also compare the updated order to the original order.

Este tutorial tem várias etapas.This tutorial has multiple steps. Após cada etapa, você poderá executar o aplicativo e ver o progresso.After each step, you can run the application and see the progress. Você também poderá ver o exemplo concluído no repositório dotnet/samples do GitHub.You can also see the completed sample in the dotnet/samples GitHub repository. Para obter instruções de download, consulte Exemplos e tutoriais.For download instructions, see Samples and Tutorials.

Pré-requisitosPrerequisites

Você precisará configurar seu computador para executar o .NET Core.You’ll need to setup your machine to run .NET core. Você pode encontrar as instruções de instalação na página de download do .NET Core .You can find the installation instructions on the .NET Core Download page. Você pode executar esse aplicativo no Windows, Ubuntu Linux, OS X ou em um contêiner do Docker.You can run this application on Windows, Ubuntu Linux, OS X or in a Docker container. Você precisará instalar o editor de código de sua preferência.You’ll need to install your favorite code editor. As descrições a seguir usam o Visual Studio Code, que é uma software livre, no editor de plataforma.The descriptions below use Visual Studio Code which is an open source, cross platform editor. No entanto, você pode usar quaisquer ferramentas que esteja familiarizado.However, you can use whatever tools you are comfortable with.

Criar o aplicativoCreate the Application

A primeira etapa é criar um novo aplicativo.The first step is to create a new application. Abra um prompt de comando e crie um novo diretório para seu aplicativo.Open a command prompt and create a new directory for your application. Torne ele o diretório atual.Make that the current directory. Digite o comando dotnet new console no prompt de comando.Type the command dotnet new console at the command prompt. Isso cria os arquivos iniciais de um aplicativo "Olá, Mundo" básico.This creates the starter files for a basic "Hello World" application.

Se você nunca usou C# antes, este tutorial explicará a estrutura de um programa C#.If you've never used C# before, this tutorial explains the structure of a C# program. Você pode ler e, em seguida, voltar aqui para saber mais sobre o LINQ.You can read that and then return here to learn more about LINQ.

Criando o arquivo de dadosCreating the Data Set

Antes de começar, verifique se as linhas a seguir estão na parte superior do arquivo Program.cs gerado pelo dotnet new console:Before you begin, make sure that the following lines are at the top of the Program.cs file generated by dotnet new console:

// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;

Se essas três linhas (instruções using) não estiverem na parte superior do arquivo, nosso programa não será compilado.If these three lines (using statements) aren't at the top of the file, our program will not compile.

Agora que você tem todas as referências necessárias, considere o que forma um baralho de cartas.Now that you have all of the references that you'll need, consider what constitutes a deck of cards. Um baralho de cartas costuma ter quatro naipes, e cada naipe tem treze valores.Commonly, a deck of playing cards has four suits, and each suit has thirteen values. Normalmente, talvez você pense em criar uma classe Card logo de cara e preencher uma coleção de objetos Card manualmente.Normally, you might consider creating a Card class right off the bat and populating a collection of Card objects by hand. Com o LINQ, dá para ser mais conciso do que a forma comum de criação de um baralho de cartas.With LINQ, you can be more concise than the usual way of dealing with creating a deck of cards. Em vez de criar uma classe Card, você pode criar duas sequências para representar naipes e valores, respectivamente.Instead of creating a Card class, you can create two sequences to represent suits and ranks, respectively. Você vai criar um par muito simples de métodos iteradores que gerará as valores e naipes como IEnumerable<T>s de cadeias de caracteres:You'll create a really simple pair of iterator methods that will generate the ranks and suits as IEnumerable<T>s of strings:

// Program.cs
// The Main() method

static IEnumerable<string> Suits()
{
    yield return "clubs";
    yield return "diamonds";
    yield return "hearts";
    yield return "spades";
}

static IEnumerable<string> Ranks()
{
    yield return "two";
    yield return "three";
    yield return "four";
    yield return "five";
    yield return "six";
    yield return "seven";
    yield return "eight";
    yield return "nine";
    yield return "ten";
    yield return "jack";
    yield return "queen";
    yield return "king";
    yield return "ace";
}

Coloque-as sob o método Main em seu arquivo Program.cs.Place these underneath the Main method in your Program.cs file. Esses dois métodos utilizam a sintaxe yield return para produzir uma sequência à medida que eles são executados.These two methods both utilize the yield return syntax to produce a sequence as they run. O compilador compila um objeto que implementa IEnumerable<T> e gera a sequência de cadeias de caracteres conforme solicitado.The compiler builds an object that implements IEnumerable<T> and generates the sequence of strings as they are requested.

Agora, use esses métodos iteradores para criar o baralho de cartas.Now, use these iterator methods to create the deck of cards. Você colocará a consulta do LINQ em nosso método Main.You'll place the LINQ query in our Main method. Dê uma olhada:Here's a look at it:

// Program.cs
static void Main(string[] args)
{
    var startingDeck = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };

    // Display each card that we've generated and placed in startingDeck in the console
    foreach (var card in startingDeck)
    {
        Console.WriteLine(card);
    }
}

As várias cláusulas from produzem um SelectMany, que cria uma única sequência da combinação entre cada elemento na primeira sequência com cada elemento na segunda sequência.The multiple from clauses produce a SelectMany, which creates a single sequence from combining each element in the first sequence with each element in the second sequence. A ordem é importante para nossos objetivos.The order is important for our purposes. O primeiro elemento na primeira sequência de fonte (Naipes) é combinado com cada elemento na segunda sequência (Valores).The first element in the first source sequence (Suits) is combined with every element in the second sequence (Ranks). Isso produz todas as treze cartas do primeiro naipe.This produces all thirteen cards of first suit. Esse processo é repetido com cada elemento na primeira sequência (naipes).That process is repeated with each element in the first sequence (Suits). O resultado final é um baralho ordenado por naipes, seguido pelos valores.The end result is a deck of cards ordered by suits, followed by values.

É importante lembrar que se você optar por escrever seu LINQ na sintaxe de consulta usada acima, ou se decidir usar a sintaxe de método, sempre será possível alternar entre as formas de sintaxe.It's important to keep in mind that whether you choose to write your LINQ in the query syntax used above or use method syntax instead, it's always possible to go from one form of syntax to the other. A consulta acima escrita em sintaxe de consulta pode ser escrita na sintaxe de método como:The above query written in query syntax can be written in method syntax as:

var startingDeck = Suits().SelectMany(suit => Ranks().Select(rank => new { Suit = suit, Rank = rank }));

O compilador traduz instruções LINQ escritas com a sintaxe de consulta na sintaxe de chamada do método equivalente.The compiler translates LINQ statements written with query syntax into the equivalent method call syntax. Portanto, independentemente de sua escolha de sintaxe, as duas versões da consulta produzem o mesmo resultado.Therefore, regardless of your syntax choice, the two versions of the query produce the same result. Escolha qual sintaxe funciona melhor para a sua situação: por exemplo, se você estiver trabalhando em uma equipe em que alguns dos membros têm dificuldade com a sintaxe de método, prefira usar a sintaxe de consulta.Choose which syntax works best for your situation: for instance, if you're working in a team where some of the members have difficulty with method syntax, try to prefer using query syntax.

Vá em frente e execute o exemplo que você criou neste momento.Go ahead and run the sample you've built at this point. Ele exibirá todas as 52 cartas do baralho.It will display all 52 cards in the deck. Talvez seja muito útil executar esse exemplo em um depurador para observar como os métodos Suits() e Ranks() são executados.You may find it very helpful to run this sample under a debugger to observe how the Suits() and Ranks() methods execute. Você pode ver claramente que cada cadeia de caracteres em cada sequência é gerada apenas conforme o necessário.You can clearly see that each string in each sequence is generated only as it is needed.

Uma janela de console mostrando o aplicativo gravando 52 cartas.

Manipulando a ordemManipulating the Order

Em seguida, concentre-se em como você vai embaralhar as cartas no baralho.Next, focus on how you're going to shuffle the cards in the deck. A primeira etapa de qualquer embaralhada é dividir o baralho em dois.The first step in any good shuffle is to split the deck in two. Os métodos Take e Skip que fazem parte das APIs do LINQ fornecem esse recurso para você.The Take and Skip methods that are part of the LINQ APIs provide that feature for you. Coloque-os sob o loop foreach:Place them underneath the foreach loop:

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

    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }

    // 52 cards in a deck, so 52 / 2 = 26
    var top = startingDeck.Take(26);
    var bottom = startingDeck.Skip(26);
}

No entanto, não há método de embaralhamento na biblioteca padrão, portanto, você precisará escrever o seu.However, there's no shuffle method to take advantage of in the standard library, so you'll have to write your own. O método de embaralhamento que você criará ilustra várias técnicas que você usará com programas baseados em LINQ. Portanto, cada parte desse processo será explicado nas etapas.The shuffle method you'll be creating illustrates several techniques that you'll use with LINQ-based programs, so each part of this process will be explained in steps.

Para adicionar funcionalidade ao seu modo de interação com o IEnumerable<T> recebido de volta das consultas do LINQ, precisará escrever alguns tipos especiais de métodos chamados métodos de extensão.In order to add some functionality to how you interact with the IEnumerable<T> you'll get back from LINQ queries, you'll need to write some special kinds of methods called extension methods. Em resumo, um método de extensão é um método estático de objetivo especial que adiciona novas funcionalidades a um tipo já existentes, sem ter que modificar o tipo original ao qual você deseja adicionar funcionalidade.Briefly, an extension method is a special purpose static method that adds new functionality to an already-existing type without having to modify the original type you want to add functionality to.

Dê aos seus métodos de extensão uma nova casa adicionando um novo arquivo de classe estático ao seu programa chamado Extensions.cs, depois, comece a criar o primeiro método de extensão:Give your extension methods a new home by adding a new static class file to your program called Extensions.cs, and then start building out the first extension method:

// Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqFaroShuffle
{
    public static class Extensions
    {
        public static IEnumerable<T> InterleaveSequenceWith<T>(this IEnumerable<T> first, IEnumerable<T> second)
        {
            // Your implementation will go here soon enough
        }
    }
}

Examine a assinatura do método por um momento, principalmente os parâmetros:Look at the method signature for a moment, specifically the parameters:

public static IEnumerable<T> InterleaveSequenceWith<T> (this IEnumerable<T> first, IEnumerable<T> second)

Você pode ver a adição do modificador this no primeiro argumento para o método.You can see the addition of the this modifier on the first argument to the method. Isso significa que você chama o método como se fosse um método de membro do tipo do primeiro argumento.That means you call the method as though it were a member method of the type of the first argument. Esta declaração de método também segue um idioma padrão no qual os tipos de entrada e saídas são IEnumerable<T>.This method declaration also follows a standard idiom where the input and output types are IEnumerable<T>. Essa prática permite que os métodos LINQ sejam encadeados para executar consultas mais complexas.That practice enables LINQ methods to be chained together to perform more complex queries.

Naturalmente, como você dividiu o baralho em metades, precisará unir essas metades.Naturally, since you split the deck into halves, you'll need to join those halves together. No código, isso significa que você vai enumerar as duas sequências adquiridas por meio de Take e Skip ao mesmo tempo, interleaving os elementos e criará uma sequência: seu baralho não embaralhado.In code, this means you'll be enumerating both of the sequences you acquired through Take and Skip at once, interleaving the elements, and creating one sequence: your now-shuffled deck of cards. Escrever um método LINQ que funciona com duas sequências exige que você compreenda como IEnumerable<T> funciona.Writing a LINQ method that works with two sequences requires that you understand how IEnumerable<T> works.

A interface IEnumerable<T> tem um método: GetEnumerator.The IEnumerable<T> interface has one method: GetEnumerator. O objeto retornado por GetEnumerator tem um método para mover para o próximo elemento e uma propriedade que recupera o elemento atual na sequência.The object returned by GetEnumerator has a method to move to the next element, and a property that retrieves the current element in the sequence. Você usará esses dois membros para enumerar a coleção e retornar os elementos.You will use those two members to enumerate the collection and return the elements. Esse método de Intercalação será um método iterador, portanto, em vez de criar uma coleção e retornar a coleção, você usará a sintaxe yield return mostrada acima.This Interleave method will be an iterator method, so instead of building a collection and returning the collection, you'll use the yield return syntax shown above.

Aqui está a implementação desse método:Here's the implementation of that method:

public static IEnumerable<T> InterleaveSequenceWith<T>
    (this IEnumerable<T> first, IEnumerable<T> second)
{
    var firstIter = first.GetEnumerator();
    var secondIter = second.GetEnumerator();

    while (firstIter.MoveNext() && secondIter.MoveNext())
    {
        yield return firstIter.Current;
        yield return secondIter.Current;
    }
}

Agora que você escreveu esse método, vá até o método Main e embaralhe uma vez:Now that you've written this method, go back to the Main method and shuffle the deck once:

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

    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }

    var top = startingDeck.Take(26);
    var bottom = startingDeck.Skip(26);
    var shuffle = top.InterleaveSequenceWith(bottom);

    foreach (var c in shuffle)
    {
        Console.WriteLine(c);
    }
}

ComparaçõesComparisons

Quantos embaralhamentos são necessários para colocar o baralho em sua ordem original?How many shuffles it takes to set the deck back to its original order? Para descobrir, você precisará escrever um método que determina se duas sequências são iguais.To find out, you'll need to write a method that determines if two sequences are equal. Depois de ter esse método, você precisará colocar o código de embaralhamento em um loop e verificar quando a apresentação estiver na ordem.After you have that method, you'll need to place the code that shuffles the deck in a loop, and check to see when the deck is back in order.

Escrever um método para determinar se as duas sequências são iguais deve ser simples.Writing a method to determine if the two sequences are equal should be straightforward. É uma estrutura semelhante para o método que você escreveu para embaralhar as cartas.It's a similar structure to the method you wrote to shuffle the deck. Somente desta vez, em vez de o yield returnrendimento retornar cada elemento, você comparará os elementos correspondentes de cada sequência.Only this time, instead of yield returning each element, you'll compare the matching elements of each sequence. Quando toda a sequência tiver sido enumerada, se os elementos corresponderem, as sequências serão as mesmas:When the entire sequence has been enumerated, if every element matches, the sequences are the same:

public static bool SequenceEquals<T>
    (this IEnumerable<T> first, IEnumerable<T> second)
{
    var firstIter = first.GetEnumerator();
    var secondIter = second.GetEnumerator();

    while (firstIter.MoveNext() && secondIter.MoveNext())
    {
        if (!firstIter.Current.Equals(secondIter.Current))
        {
            return false;
        }
    }

    return true;
}

Isso mostra uma segunda linguagem LINQ: métodos de terminal.This shows a second LINQ idiom: terminal methods. Eles consideram uma sequência como entrada (ou, neste caso, duas sequências) e retornam um único valor escalar.They take a sequence as input (or in this case, two sequences), and return a single scalar value. Ao usar métodos de terminal, eles são sempre o método final em uma cadeia de métodos para uma consulta LINQ, por isso, o nome "terminal".When using terminal methods, they are always the final method in a chain of methods for a LINQ query, hence the name "terminal".

Você pode ver isso em ação ao usá-lo para determinar quando o baralho está em sua ordem original.You can see this in action when you use it to determine when the deck is back in its original order. Coloque o código de embaralhamento dentro de um loop e pare quando a sequência estiver em sua ordem original, aplicando o método SequenceEquals().Put the shuffle code inside a loop, and stop when the sequence is back in its original order by applying the SequenceEquals() method. Você pode ver que esse sempre será o método final em qualquer consulta, porque ele retorna um valor único em vez de uma sequência:You can see it would always be the final method in any query, because it returns a single value instead of a sequence:

// Program.cs
static void Main(string[] args)
{
    // Query for building the deck

    // Shuffling using InterleaveSequenceWith<T>();

    var times = 0;
    // We can re-use the shuffle variable from earlier, or you can make a new one
    shuffle = startingDeck;
    do
    {
        shuffle = shuffle.Take(26).InterleaveSequenceWith(shuffle.Skip(26));

        foreach (var card in shuffle)
        {
            Console.WriteLine(card);
        }
        Console.WriteLine();
        times++;

    } while (!startingDeck.SequenceEquals(shuffle));

    Console.WriteLine(times);
}

Execute o código que obtivemos até agora e observe como o baralho é reorganizado em cada embaralhamento.Run the code you've got so far and take note of how the deck rearranges on each shuffle. Após 8 embaralhamentos (iterações do loop do-while), o baralho retorna à configuração original que estava quando você o criou pela primeira vez a partir da consulta LINQ inicial.After 8 shuffles (iterations of the do-while loop), the deck returns to the original configuration it was in when you first created it from the starting LINQ query.

OtimizaçõesOptimizations

O exemplo que você compilou até agora executa um embaralhamento externo, no qual as cartas superiores e inferiores permanecem as mesmas em cada execução.The sample you've built so far executes an out shuffle, where the top and bottom cards stay the same on each run. Vamos fazer uma alteração: em vez disso, usaremos um embaralhamento interno, em que todas as 52 cartas trocam de posição.Let's make one change: we'll use an in shuffle instead, where all 52 cards change position. Para um embaralhamento interno, intercale o baralho para que a primeira carta da metade inferior torne-se a primeira carta do baralho.For an in shuffle, you interleave the deck so that the first card in the bottom half becomes the first card in the deck. Isso significa que a última carta na metade superior torna-se a carta inferior.That means the last card in the top half becomes the bottom card. Essa é uma alteração simples em uma única linha de código.This is a simple change to a singular line of code. Atualize a consulta atual de embaralhamento, alternando as posições de Take e Skip.Update the current shuffle query by switching the positions of Take and Skip. Isso alterará a ordem das metades superior e inferior do baralho:This will change the order of the top and bottom halves of the deck:

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

Execute o programa novamente e você verá que leva 52 iterações para o baralho ser reordenado.Run the program again, and you'll see that it takes 52 iterations for the deck to reorder itself. Você também começará a observar algumas degradações de desempenho graves à medida que o programa continuar a ser executado.You'll also start to notice some serious performance degradations as the program continues to run.

Existem muitas razões para isso.There are a number of reasons for this. Você pode abordar uma das principais causas dessa queda de desempenho: uso ineficiente da avaliação lenta.You can tackle one of the major causes of this performance drop: inefficient use of lazy evaluation.

Em resumo, a avaliação lenta informa que a avaliação de uma instrução não será executada até que seu valor seja necessário.Briefly, lazy evaluation states that the evaluation of a statement is not performed until its value is needed. Consultas LINQ são instruções avaliadas lentamente.LINQ queries are statements that are evaluated lazily. As sequências são geradas somente quando os elementos são solicitados.The sequences are generated only as the elements are requested. Geralmente, esse é o principal benefício do LINQ.Usually, that's a major benefit of LINQ. No entanto, em uso como esse programa, isso causa um crescimento exponencial no tempo de execução.However, in a use such as this program, this causes exponential growth in execution time.

Lembre-se de que geramos o baralho original usando uma consulta LINQ.Remember that we generated the original deck using a LINQ query. Cada embaralhamento é gerado executando três consultas LINQ no baralho anterior.Each shuffle is generated by performing three LINQ queries on the previous deck. Todos eles são executados lentamente.All these are performed lazily. Isso também significa que eles são executados novamente sempre que a sequência é solicitada.That also means they are performed again each time the sequence is requested. Ao obter a 52ª iteração, você estará regenerando o baralho original muitas e muitas vezes.By the time you get to the 52nd iteration, you're regenerating the original deck many, many times. Vamos escrever um log para demonstrar esse comportamento.Let's write a log to demonstrate this behavior. Em seguida, você poderá corrigir isso.Then, you'll fix it.

Em seu arquivo Extensions.cs, digite ou copie o método a seguir.In your Extensions.cs file, type in or copy the method below. Esse método de extensão cria um novo arquivo chamado debug.log em seu diretório do projeto, e registra qual consulta está sendo executada atualmente para o arquivo de log.This extension method creates a new file called debug.log within your project directory and records what query is currently being executed to the log file. Este método de extensão pode ser anexado a qualquer consulta para marcar que a consulta foi executada.This extension method can be appended to any query to mark that the query executed.

public static IEnumerable<T> LogQuery<T>
    (this IEnumerable<T> sequence, string tag)
{
    // File.AppendText creates a new file if the file doesn't exist.
    using (var writer = File.AppendText("debug.log"))
    {
        writer.WriteLine($"Executing Query {tag}");
    }

    return sequence;
}

Você verá um rabisco vermelho sob File, que significa que ele não existe.You will see a red squiggle under File, meaning it doesn't exist. Ele não será compilado, pois o compilador não sabe o que é File.It won't compile, since the compiler doesn't know what File is. Para resolver esse problema, é preciso que você adicione a linha de código a seguir abaixo da primeira linha em Extensions.cs:To solve this problem, make sure to add the following line of code under the very first line in Extensions.cs:

using System.IO;

Isso deve resolver o problema e o erro vermelho desaparece.This should solve the issue and the red error disappears.

Em seguida, instrumente a definição de cada consulta com uma mensagem de log:Next, instrument the definition of each query with a log message:

// Program.cs
public static void Main(string[] args)
{
    var startingDeck = (from s in Suits().LogQuery("Suit Generation")
                        from r in Ranks().LogQuery("Rank Generation")
                        select new { Suit = s, Rank = r }).LogQuery("Starting Deck");

    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }

    Console.WriteLine();
    var times = 0;
    var shuffle = startingDeck;

    do
    {
        // Out shuffle
        /*
        shuffle = shuffle.Take(26)
            .LogQuery("Top Half")
            .InterleaveSequenceWith(shuffle.Skip(26)
            .LogQuery("Bottom Half"))
            .LogQuery("Shuffle");
        */

        // In shuffle
        shuffle = shuffle.Skip(26).LogQuery("Bottom Half")
                .InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
                .LogQuery("Shuffle");

        foreach (var c in shuffle)
        {
            Console.WriteLine(c);
        }

        times++;
        Console.WriteLine(times);
    } while (!startingDeck.SequenceEquals(shuffle));

    Console.WriteLine(times);
}

Observe que você não precisa fazer o registro sempre que acessar uma consulta.Notice that you don't log every time you access a query. Você faz o registro ao criar a consulta original.You log only when you create the original query. O programa ainda leva muito tempo para ser executado, mas agora você pode ver o motivo.The program still takes a long time to run, but now you can see why. Se você não tiver paciência para executar o embaralhamento interno com o registro em log ativado, volte para o embaralhamento externo.If you run out of patience running the in shuffle with logging turned on, switch back to the out shuffle. Você ainda verá os efeitos da avaliação lenta.You'll still see the lazy evaluation effects. Em uma execução, ele faz 2592 consultas, incluindo a geração de todos os valores e naipes.In one run, it executes 2592 queries, including all the value and suit generation.

Aqui, você pode melhorar o desempenho do código para reduzir o número de execuções feitas.You can improve the performance of the code here to reduce the number of executions you make. Uma correção simples possível é armazenar em cache os resultados da consulta do LINQ original que constrói o baralho de cartas.A simple fix you can make is to cache the results of the original LINQ query that constructs the deck of cards. Atualmente, você executa as consultas novamente sempre que o loop do-while passa por uma iteração, construindo novamente o baralho de cartas e o embaralhamento de novo todas as vezes.Currently, you're executing the queries again and again every time the do-while loop goes through an iteration, re-constructing the deck of cards and reshuffling it every time. Para armazenar em cache o baralho de cartas, aproveite os métodos LINQ ToArray e ToList; ao anexá-los às consultas, eles executarão as mesmas ações paras quais foram instruídos, mas agora armazenarão os resultados em uma matriz ou lista, dependendo de qual método você optar por chamar.To cache the deck of cards, you can leverage the LINQ methods ToArray and ToList; when you append them to the queries, they'll perform the same actions you've told them to, but now they'll store the results in an array or a list, depending on which method you choose to call. Anexe o método LINQ ToArray às duas consultas e execute o programa novamente:Append the LINQ method ToArray to both queries and run the program again:

public static void Main(string[] args)
{
    var startingDeck = (from s in Suits().LogQuery("Suit Generation")
                        from r in Ranks().LogQuery("Value Generation")
                        select new { Suit = s, Rank = r })
                        .LogQuery("Starting Deck")
                        .ToArray();

    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }

    Console.WriteLine();

    var times = 0;
    var shuffle = startingDeck;

    do
    {
        /*
        shuffle = shuffle.Take(26)
            .LogQuery("Top Half")
            .InterleaveSequenceWith(shuffle.Skip(26).LogQuery("Bottom Half"))
            .LogQuery("Shuffle")
            .ToArray();
        */

        shuffle = shuffle.Skip(26)
            .LogQuery("Bottom Half")
            .InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
            .LogQuery("Shuffle")
            .ToArray();

        foreach (var c in shuffle)
        {
            Console.WriteLine(c);
        }

        times++;
        Console.WriteLine(times);
    } while (!startingDeck.SequenceEquals(shuffle));

    Console.WriteLine(times);
}

Agora, o embaralhamento externo contém 30 consultas.Now the out shuffle is down to 30 queries. Execute novamente com o embaralhamento interno e você verá melhorias semelhantes: agora, executa 162 consultas.Run again with the in shuffle and you'll see similar improvements: it now executes 162 queries.

Observe que esse exemplo é projetado para realçar os casos de uso em que a avaliação lenta pode causar problemas de desempenho.Please note that this example is designed to highlight the use cases where lazy evaluation can cause performance difficulties. Embora seja importante ver onde a avaliação lenta pode afetar o desempenho do código, é igualmente importante entender que nem todas as consultas devem ser executadas avidamente.While it's important to see where lazy evaluation can impact code performance, it's equally important to understand that not all queries should run eagerly. O desempenho incorrido sem usar ToArray ocorre porque cada nova disposição do baralho de cartas é criada com base na disposição anterior.The performance hit you incur without using ToArray is because each new arrangement of the deck of cards is built from the previous arrangement. Usar a avaliação lenta significa que cada nova disposição do baralho é criada do baralho original, até mesmo a execução do código que criou o startingDeck.Using lazy evaluation means each new deck configuration is built from the original deck, even executing the code that built the startingDeck. Isso causa uma grande quantidade de trabalho extra.That causes a large amount of extra work.

Na prática, alguns algoritmos funcionam bem usando a avaliação detalhada, e outros executam funcionam melhor usando a avaliação lenta.In practice, some algorithms run well using eager evaluation, and others run well using lazy evaluation. Para o uso diário, a avaliação lenta é uma opção melhor quando a fonte de dados é um processo separado, como um mecanismo de banco de dados.For daily usage, lazy evaluation is usually a better choice when the data source is a separate process, like a database engine. Para os bancos de dados, a avaliação lenta permite que as consultas mais complexas executem apenas uma viagem de ida e volta para o processo de banco de dados e de volta para o restante do seu código.For databases, lazy evaluation allows more complex queries to execute only one round trip to the database process and back to the rest of your code. O LINQ é flexível, não importa se você optar por utilizar a avaliação lenta ou detalhada, portanto, meça seus processos e escolha o tipo de avaliação que ofereça o melhor desempenho.LINQ is flexible whether you choose to utilize lazy or eager evaluation, so measure your processes and pick whichever kind of evaluation gives you the best performance.

ConclusãoConclusion

Neste projeto, abordamos:In this project, you covered:

  • o uso de consultas LINQ para agregar dados em uma sequência significativausing LINQ queries to aggregate data into a meaningful sequence
  • a produção de métodos de Extensão para adicionar nossa própria funcionalidade personalizada a consultas LINQwriting Extension methods to add our own custom functionality to LINQ queries
  • a localização de áreas em nosso código nas quais nossas consultas LINQ podem enfrentar problemas de desempenho, como diminuição da velocidadelocating areas in our code where our LINQ queries might run into performance issues like degraded speed
  • avaliação lenta e detalhada com relação às consultas LINQ, e as implicações que elas podem ter no desempenho da consultalazy and eager evaluation in regards to LINQ queries and the implications they might have on query performance

Além do LINQ, você aprendeu um pouco sobre uma técnica usada por mágicos para truques de carta.Aside from LINQ, you learned a bit about a technique magicians use for card tricks. Os mágicos usam o embaralhamento Faro porque podem controlar onde cada carta fica no baralho.Magicians use the Faro shuffle because they can control where every card moves in the deck. Agora que você sabe, não conte para os outros!Now that you know, don't spoil it for everyone else!

Para saber mais sobre o LINQ, consulte:For more information on LINQ, see: