Praca z technologią LINQWorking with LINQ

WprowadzenieIntroduction

W tym samouczku przedstawiono funkcje platformy .NET Core i C# języka.This tutorial teaches you features in .NET Core and the C# language. Dowiesz się:You’ll learn:

  • Jak generować sekwencje przy użyciu LINQ.How to generate sequences with LINQ.
  • Jak pisać metody, które mogą być łatwo używane w zapytaniach LINQ.How to write methods that can be easily used in LINQ queries.
  • Sposób rozróżniania między eager i oceną z opóźnieniem.How to distinguish between eager and lazy evaluation.

Poznasz te techniki, tworząc aplikację, która pokazuje jedną z podstawowych umiejętności dowolnego czarodziej: Faro losowo.You'll learn these techniques by building an application that demonstrates one of the basic skills of any magician: the faro shuffle. Krótko Faro losowo to technika, w której można podzielić talię kart dokładnie na połowę, a następnie losowo przeplata każdą kartę z każdej połowy w celu odbudowania oryginalnego talii.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.

Magicians użyć tej techniki, ponieważ każda karta znajduje się w znanej lokalizacji po każdym rozłożeniu losowym, a kolejność jest powtarzalnym wzorcem.Magicians use this technique because every card is in a known location after each shuffle, and the order is a repeating pattern.

Na potrzeby Twoich celów jest jasne spojrzenie na manipulowanie sekwencjami danych.For your purposes, it is a light hearted look at manipulating sequences of data. Aplikacja, którą utworzysz, utworzy talię kart, a następnie wykona sekwencję losowych, co oznacza, że sekwencję można napisać za każdym razem.The application you'll build will construct a card deck, and then perform a sequence of shuffles, writing the sequence out each time. Porównano również zaktualizowaną kolejność do oryginalnej kolejności.You'll also compare the updated order to the original order.

Ten samouczek zawiera wiele kroków.This tutorial has multiple steps. Po każdym kroku można uruchomić aplikację i postępować według postępu.After each step, you can run the application and see the progress. Możesz również zobaczyć ukończony przykład w repozytorium GitHub/Samples.You can also see the completed sample in the dotnet/samples GitHub repository. Aby uzyskać instrukcje dotyczące pobierania, zobacz przykłady i samouczki.For download instructions, see Samples and Tutorials.

Wymagania wstępnePrerequisites

Musisz skonfigurować maszynę do uruchamiania programu .NET Core.You’ll need to setup your machine to run .NET core. Instrukcje instalacji można znaleźć na stronie pobierania programu .NET Core .You can find the installation instructions on the .NET Core Download page. Możesz uruchomić tę aplikację w systemie Windows, Ubuntu Linux, OS X lub w kontenerze platformy Docker.You can run this application on Windows, Ubuntu Linux, OS X or in a Docker container. Musisz zainstalować swój ulubiony Edytor kodu.You’ll need to install your favorite code editor. Poniższe opisy wykorzystują Visual Studio Code , czyli edytor Międzyplatformowy.The descriptions below use Visual Studio Code which is an open source, cross platform editor. Można jednak korzystać z dowolnych narzędzi, z którymi masz doświadczenie.However, you can use whatever tools you are comfortable with.

Tworzenie aplikacjiCreate the Application

Pierwszym krokiem jest utworzenie nowej aplikacji.The first step is to create a new application. Otwórz wiersz polecenia i Utwórz nowy katalog dla aplikacji.Open a command prompt and create a new directory for your application. Upewnij się, że bieżący katalog.Make that the current directory. Wpisz dotnet new console polecenia w wierszu polecenia.Type the command dotnet new console at the command prompt. Spowoduje to utworzenie plików początkowych dla podstawowej aplikacji "Hello world".This creates the starter files for a basic "Hello World" application.

Jeśli wcześniej nie korzystano C# z tego samouczka, ten samouczek wyjaśnia strukturę C# programu.If you've never used C# before, this tutorial explains the structure of a C# program. Możesz przeczytać ten element, a następnie wrócić tutaj, aby dowiedzieć się więcej na temat LINQ.You can read that and then return here to learn more about LINQ.

Tworzenie zestawu danychCreating the Data Set

Przed rozpoczęciem upewnij się, że następujące wiersze znajdują się na początku pliku Program.cs wygenerowanego przez 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;

Jeśli te trzy wiersze (instrukcjeusing) nie znajdują się na początku pliku, nasz program nie zostanie skompilowany.If these three lines (using statements) aren't at the top of the file, our program will not compile.

Teraz, gdy masz wszystkie odwołania, których potrzebujesz, weź pod uwagę co to jest talia kart.Now that you have all of the references that you'll need, consider what constitutes a deck of cards. Często talia kart gry ma cztery kolory, a każdy z nich ma trzynastie wartości.Commonly, a deck of playing cards has four suits, and each suit has thirteen values. Zwykle warto rozważyć utworzenie klasy Card po prawej stronie bat i wypełnianie kolekcji obiektów Card.Normally, you might consider creating a Card class right off the bat and populating a collection of Card objects by hand. Dzięki LINQ można być bardziej zwięzłe niż zwykły sposób tworzenia talii kart.With LINQ, you can be more concise than the usual way of dealing with creating a deck of cards. Zamiast tworzyć klasy Card, można utworzyć dwie sekwencje, aby reprezentować odpowiednio kolory i Range.Instead of creating a Card class, you can create two sequences to represent suits and ranks, respectively. Utworzysz bardzo prostą parę metod iteratorów , które będą generować Range i ubrania jako IEnumerable<T>s ciągów: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";
}

Umieść je poniżej metody Main w pliku Program.cs.Place these underneath the Main method in your Program.cs file. Te dwie metody wykorzystują składnię yield return do tworzenia sekwencji podczas jej uruchamiania.These two methods both utilize the yield return syntax to produce a sequence as they run. Kompilator kompiluje obiekt, który implementuje IEnumerable<T> i generuje sekwencję ciągów w miarę ich żądania.The compiler builds an object that implements IEnumerable<T> and generates the sequence of strings as they are requested.

Teraz Użyj tych metod iteratora do utworzenia talii kart.Now, use these iterator methods to create the deck of cards. Kwerenda LINQ zostanie umieszczona w naszej metodzie Main.You'll place the LINQ query in our Main method. Oto jak wygląda: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);
    }
}

Wielofromowe klauzule tworzą SelectMany, który tworzy jedną sekwencję od łączenia każdego elementu w pierwszej sekwencji z każdym elementem w drugiej sekwencji.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. Kolejność jest ważna dla naszych celów.The order is important for our purposes. Pierwszy element w pierwszej sekwencji źródłowej (kolory) jest połączony z każdym elementem w drugiej sekwencji (rangi).The first element in the first source sequence (Suits) is combined with every element in the second sequence (Ranks). Spowoduje to wygenerowanie wszystkich trzynastu kart pierwszego koloru.This produces all thirteen cards of first suit. Ten proces jest powtarzany przy każdym elemencie w pierwszej sekwencji (kolory).That process is repeated with each element in the first sequence (Suits). Wynik końcowy to talia kart uporządkowanych według kolorów, a następnie wartości.The end result is a deck of cards ordered by suits, followed by values.

Należy pamiętać, że niezależnie od tego, czy użytkownik zdecyduje się pisać składnik LINQ we wskazanej powyżej składni zapytania lub użyć składni metody zamiast tego, zawsze możliwe jest przechodzenie z jednej formy składni.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. Powyższe zapytanie zapisywane w składni zapytania można napisać w składni metody jako: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 }));

Kompilator tłumaczy instrukcje LINQ zapisywane z składnią zapytania na równoważną składnię wywołania metody.The compiler translates LINQ statements written with query syntax into the equivalent method call syntax. W związku z tym, niezależnie od wyboru składni, dwie wersje zapytania dają ten sam wynik.Therefore, regardless of your syntax choice, the two versions of the query produce the same result. Wybierz, która składnia najlepiej sprawdza się w danej sytuacji: na przykład jeśli pracujesz w zespole, w którym niektórzy członkowie mają problemy ze składnią metody, spróbuj użyć składni zapytania.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.

Zacznij korzystać z przykładu skompilowanego w tym punkcie.Go ahead and run the sample you've built at this point. Spowoduje to wyświetlenie wszystkich kart 52 na pokładzie.It will display all 52 cards in the deck. Przydatne może być uruchomienie tego przykładu w debugerze, aby obserwować sposób wykonywania Suits() i Ranks() metod.You may find it very helpful to run this sample under a debugger to observe how the Suits() and Ranks() methods execute. Można jasno zobaczyć, że każdy ciąg w każdej sekwencji jest generowany tylko w razie konieczności.You can clearly see that each string in each sequence is generated only as it is needed.

Okno konsoli z zapisaniem aplikacji z kartami 52.

Manipulowanie kolejnościąManipulating the Order

Następnie należy skoncentrować się na tym, w jaki sposób mają być losowo używane karty na pokładzie.Next, focus on how you're going to shuffle the cards in the deck. Pierwszym krokiem w każdym dobrym rozbiciem jest podzielenie talii na dwa.The first step in any good shuffle is to split the deck in two. Metody Take i Skip, które są częścią interfejsów API LINQ, udostępniają tę funkcję.The Take and Skip methods that are part of the LINQ APIs provide that feature for you. Umieść je poniżej pętli 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);
}

Jednak nie ma metody losowej, aby skorzystać z funkcji w bibliotece standardowej, więc musisz napisać własny.However, there's no shuffle method to take advantage of in the standard library, so you'll have to write your own. Metoda Losowa, która zostanie utworzona, ilustruje kilka technik, które będą używane z programami LINQ, więc każda część tego procesu zostanie omówiona w krokach.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.

Aby dodać niektóre funkcje do korzystania z IEnumerable<T> można wrócić z zapytań LINQ, należy napisać specjalne rodzaje metod nazywanych metodami rozszerzenia.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. Krótko Metoda rozszerzenia to metoda statyczna specjalnego przeznaczenia, która dodaje nową funkcję do już istniejącego typu bez konieczności modyfikowania oryginalnego typu, do którego ma zostać dodana funkcja.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.

Nadaj rozszerzeniom nowe metody, dodając nowy plik statycznej klasy do programu o nazwie Extensions.cs, a następnie rozpocznij tworzenie pierwszej metody rozszerzenia: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
        }
    }
}

Zapoznaj się z chwilą podpis metody, a w tym parametry:Look at the method signature for a moment, specifically the parameters:

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

Możesz zobaczyć dodanie modyfikatora this pierwszego argumentu do metody.You can see the addition of the this modifier on the first argument to the method. Oznacza to, że wywoływana jest metoda, tak jakby była to metoda członkowska typu pierwszego argumentu.That means you call the method as though it were a member method of the type of the first argument. Ta deklaracja metody jest również zgodna ze standardową idiom, w którym typy wejściowe i wyjściowe są IEnumerable<T>.This method declaration also follows a standard idiom where the input and output types are IEnumerable<T>. To rozwiązanie umożliwia łączenie metod LINQ ze sobą w celu wykonywania bardziej złożonych zapytań.That practice enables LINQ methods to be chained together to perform more complex queries.

Naturalnie, ze względu na rozdzielenie talii na połowy, należy dołączyć te połówki razem.Naturally, since you split the deck into halves, you'll need to join those halves together. W kodzie oznacza to, że zostaną wyliczone obie sekwencje nabyte za pośrednictwem Take i Skip na raz, interleaving elementów i utworzenie jednej sekwencji: teraz rozjmowana talia kart.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. Pisanie metody LINQ, która współpracuje z dwoma sekwencjami, wymaga zrozumienia sposobu działania IEnumerable<T>.Writing a LINQ method that works with two sequences requires that you understand how IEnumerable<T> works.

Interfejs IEnumerable<T> ma jedną metodę: GetEnumerator.The IEnumerable<T> interface has one method: GetEnumerator. Obiekt zwrócony przez GetEnumerator ma metodę do przejścia do następnego elementu i właściwość, która pobiera bieżący element w sekwencji.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. Te dwa elementy członkowskie będą używane do wyliczenia kolekcji i zwrócenia elementów.You will use those two members to enumerate the collection and return the elements. Ta metoda przeplotu będzie metodą iteratora, dlatego zamiast kompilowania kolekcji i zwracania kolekcji, użyj składni yield return pokazanej powyżej.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.

Oto implementacja tej metody: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;
    }
}

Teraz, po zapisaniu tej metody, Wróć do metody Main i przetwórz losowo talię: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);
    }
}

PorównaniaComparisons

Ile trwa losowo, aby ustawić pokład z powrotem do oryginalnej kolejności?How many shuffles it takes to set the deck back to its original order? Aby dowiedzieć się, należy napisać metodę, która określa, czy dwie sekwencje są równe.To find out, you'll need to write a method that determines if two sequences are equal. Po zastosowaniu tej metody należy umieścić kod, który wystawia talię w pętli, i sprawdzić, czy talia jest odwrócona.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.

Pisanie metody w celu ustalenia, czy dwie sekwencje są równe, powinny być bezpośrednie.Writing a method to determine if the two sequences are equal should be straightforward. Jest to podobna struktura do metody, która została zapisana w celu rozdzielenia talii.It's a similar structure to the method you wrote to shuffle the deck. Tylko ten czas, a nie yield returnWykorzystaj każdego elementu, porównano pasujące elementy każdej sekwencji.Only this time, instead of yield returning each element, you'll compare the matching elements of each sequence. Gdy cała sekwencja została wyliczona, jeśli każdy element jest zgodny, sekwencje są takie same: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;
}

Przedstawiono w nim drugą LINQ idiom: metody terminalowe.This shows a second LINQ idiom: terminal methods. Przyjmuje sekwencję jako dane wejściowe (lub w tym przypadku dwie sekwencje) i zwracają pojedynczą wartość skalarną.They take a sequence as input (or in this case, two sequences), and return a single scalar value. W przypadku korzystania z metod terminalu są zawsze końcową metodą w łańcuchu metod dla zapytania LINQ, w związku z czym nazwa "Terminal".When using terminal methods, they are always the final method in a chain of methods for a LINQ query, hence the name "terminal".

Ten element można zobaczyć w działaniu, gdy jest on używany do określenia, kiedy talia zostanie przywrócona w oryginalnej kolejności.You can see this in action when you use it to determine when the deck is back in its original order. Umieść kod losowy wewnątrz pętli i Zatrzymaj, gdy sekwencja zostanie przywrócona w oryginalnej kolejności, stosując metodę SequenceEquals().Put the shuffle code inside a loop, and stop when the sequence is back in its original order by applying the SequenceEquals() method. Można zobaczyć, że zawsze będzie to Ostatnia metoda w dowolnych zapytania, ponieważ zwraca jedną wartość zamiast sekwencji: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);
}

Uruchom kod, który został już osiągnięty, i zanotuj, jak talia jest zmieniana na każdym losowo.Run the code you've got so far and take note of how the deck rearranges on each shuffle. Po 8 losowo (iteracji pętli "do-while") na pokładzie zostanie przywrócona oryginalna konfiguracja, która znajdowała się podczas pierwszego tworzenia go na podstawie uruchomienia zapytania LINQ.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.

OptymalizacjeOptimizations

Utworzona przez Ciebie przykład jest wykonywana losowo, gdy karty górne i dolne pozostają takie same w każdym przebiegu.The sample you've built so far executes an out shuffle, where the top and bottom cards stay the same on each run. Wprowadźmy jedną zmianę: zamiast tego będziemy używać w tym miejscu, gdzie wszystkie karty 52 zmieniają pozycję.Let's make one change: we'll use an in shuffle instead, where all 52 cards change position. W przypadku losowego odtwarzania talii należy pozostawać, aby pierwsza karta w dolnej połowie stała się pierwszą kartą na pokładzie.For an in shuffle, you interleave the deck so that the first card in the bottom half becomes the first card in the deck. Oznacza to, że ostatnia karta w górnej połowie zmieni się na najniższą kartę.That means the last card in the top half becomes the bottom card. Jest to prosta zmiana w pojedynczej linii kodu.This is a simple change to a singular line of code. Zaktualizuj bieżące losowe zapytanie, przełączając pozycje Take i Skip.Update the current shuffle query by switching the positions of Take and Skip. Spowoduje to zmianę kolejności górnej i dolnej części talii:This will change the order of the top and bottom halves of the deck:

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

Uruchom program ponownie, a zobaczysz, że dla talii zostanie wykonanych 52 iteracji na potrzeby zmiany kolejności.Run the program again, and you'll see that it takes 52 iterations for the deck to reorder itself. Należy również pamiętać o poważnym obniżeniu wydajności, gdy program będzie kontynuował pracę.You'll also start to notice some serious performance degradations as the program continues to run.

Istnieje kilka przyczyn tego działania.There are a number of reasons for this. Możesz skorzystać z jednej z głównych przyczyn tego spadku wydajności: niewydajne użycie oceny z opóźnieniem.You can tackle one of the major causes of this performance drop: inefficient use of lazy evaluation.

Krótko, z opóźnieniem, że Ocena instrukcji nie jest wykonywana, dopóki jej wartość nie jest wymagana.Briefly, lazy evaluation states that the evaluation of a statement is not performed until its value is needed. Zapytania LINQ to instrukcje, które są oceniane opóźnieniem.LINQ queries are statements that are evaluated lazily. Sekwencje są generowane tylko w przypadku, gdy są żądane elementy.The sequences are generated only as the elements are requested. Zwykle jest to główna korzyść dla LINQ.Usually, that's a major benefit of LINQ. Jednak w przypadku użycia takiego jak ten program powoduje wzrost wykładniczy w czasie wykonywania.However, in a use such as this program, this causes exponential growth in execution time.

Należy pamiętać, że wygenerowałeś oryginalne talie przy użyciu zapytania LINQ.Remember that we generated the original deck using a LINQ query. Każda losowo jest generowana przez wykonywanie trzech zapytań LINQ na poprzednim talii.Each shuffle is generated by performing three LINQ queries on the previous deck. Wszystkie te są wykonywane opóźnieniem.All these are performed lazily. Oznacza to również, że są wykonywane ponownie przy każdym żądaniu sekwencji.That also means they are performed again each time the sequence is requested. Po otrzymaniu do iteracji 52nd można ponownie wygenerować oryginalny tali wiele razy.By the time you get to the 52nd iteration, you're regenerating the original deck many, many times. Napiszmy dziennik, aby zademonstrować to zachowanie.Let's write a log to demonstrate this behavior. Następnie należy rozwiązać ten problem.Then, you'll fix it.

W pliku Extensions.cs wpisz lub skopiuj metodę poniżej.In your Extensions.cs file, type in or copy the method below. Ta metoda rozszerzenia tworzy nowy plik o nazwie debug.log w katalogu projektu i rejestruje, jakie zapytanie jest aktualnie wykonywane w pliku dziennika.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. Tę metodę rozszerzenia można dołączyć do dowolnego zapytania, aby oznaczyć, że zapytanie zostało wykonane.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;
}

Zostanie wyświetlona czerwona zygzakowata File, co oznacza, że nie istnieje.You will see a red squiggle under File, meaning it doesn't exist. Nie kompiluje się, ponieważ kompilator nie wie, co File.It won't compile, since the compiler doesn't know what File is. Aby rozwiązać ten problem, pamiętaj, aby dodać następujący wiersz kodu poniżej pierwszego wiersza w 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;

Powinno to rozwiązać problem, a czerwony błąd znika.This should solve the issue and the red error disappears.

Następnie instrument definicji każdego zapytania z komunikatem dziennika: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);
}

Należy zauważyć, że nie rejestruje się za każdym razem, gdy uzyskujesz dostęp do zapytania.Notice that you don't log every time you access a query. Rejestruje się tylko podczas tworzenia oryginalnego zapytania.You log only when you create the original query. Uruchomienie programu nadal trwa długo, ale teraz można zobaczyć dlaczego.The program still takes a long time to run, but now you can see why. W przypadku wypróbowania korzystania z trybu losowania z włączonym rejestrowaniem, przełącz się z powrotem na konfigurację losową.If you run out of patience running the in shuffle with logging turned on, switch back to the out shuffle. Nadal zobaczysz efekty oceny z opóźnieniem.You'll still see the lazy evaluation effects. W jednym przebiegu są wykonywane zapytania 2592, w tym cała wartość i generowanie koloru.In one run, it executes 2592 queries, including all the value and suit generation.

W tym miejscu można poprawić wydajność kodu, aby zmniejszyć liczbę wykonanych wykonań.You can improve the performance of the code here to reduce the number of executions you make. Prosta poprawka, którą można wprowadzić, to buforowanie wyników oryginalnego zapytania LINQ, które konstruuje talię kart.A simple fix you can make is to cache the results of the original LINQ query that constructs the deck of cards. Obecnie wykonujesz zapytania ponownie i ponownie za każdym razem, gdy pętla do-while przechodzi przez iterację, należy ponownie skonstruować talię kart i reshuffling je za każdym razem.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. Aby buforować talię kart, można wykorzystać metody LINQ ToArray i ToList; Po dołączeniu ich do zapytań będą one wykonywały te same akcje, które zostały przez Ciebie zapamiętane, ale teraz przechowują wyniki w tablicy lub liście, w zależności od wybranej metody do wywołania.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. Dołącz metodę LINQ ToArray do obu zapytań i ponownie uruchom program: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);
}

Teraz wychodząca wartość jest wyłączana do 30 zapytań.Now the out shuffle is down to 30 queries. Uruchom ponownie z użyciem funkcji losowej i zobaczysz podobne udoskonalenia: teraz wykonuje zapytania 162.Run again with the in shuffle and you'll see similar improvements: it now executes 162 queries.

Należy pamiętać, że ten przykład został zaprojektowany w celu wyróżnienia przypadków użycia, gdy Ocena z opóźnieniem może spowodować problemy z wydajnością.Please note that this example is designed to highlight the use cases where lazy evaluation can cause performance difficulties. Chociaż ważne jest, aby sprawdzić, gdzie Ocena z opóźnieniem może wpłynąć na wydajność kodu, należy jednak pamiętać, że nie wszystkie zapytania powinny uruchamiać eagerly.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. Trafienie wydajności, które pozostało bez użycia ToArray to ponieważ każde nowe rozmieszczenie kart jest zbudowane z poprzedniego rozmieszczenia.The performance hit you incur without using ToArray is because each new arrangement of the deck of cards is built from the previous arrangement. Użycie oceny z opóźnieniem oznacza, że każda nowa konfiguracja jest tworzona na podstawie oryginalnego pokładu, nawet w przypadku wykonywania kodu, który został skompilowany startingDeck.Using lazy evaluation means each new deck configuration is built from the original deck, even executing the code that built the startingDeck. Powoduje to znaczną ilość dodatkowego nakładu pracy.That causes a large amount of extra work.

W przypadku niektórych algorytmów działa dobrze przy użyciu oceny eager, a inne działają dobrze przy użyciu oceny z opóźnieniem.In practice, some algorithms run well using eager evaluation, and others run well using lazy evaluation. W przypadku codziennego użycia Ocena z opóźnieniem jest zazwyczaj lepszym wyborem, gdy źródło danych jest osobnym procesem, takim jak aparat bazy danych.For daily usage, lazy evaluation is usually a better choice when the data source is a separate process, like a database engine. W przypadku baz danych Ocena z opóźnieniem umożliwia bardziej skomplikowane zapytania wykonywanie tylko jednej rundy w procesie bazy danych i powrót do reszty kodu.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. LINQ jest elastyczne, niezależnie od tego, czy zdecydujesz się na użycie oceny z opóźnieniem, czy eager, więc Zmierz procesy i wybieraj niezależny rodzaj oceny zapewnia najlepszą wydajność.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.

WniosekConclusion

W tym projekcie omówiono następujące zagadnienia:In this project, you covered:

  • Używanie zapytań LINQ do agregowania danych w zrozumiałej sekwencjiusing LINQ queries to aggregate data into a meaningful sequence
  • Pisanie metod rozszerzających, aby dodać własną funkcję niestandardową do zapytań LINQwriting Extension methods to add our own custom functionality to LINQ queries
  • Lokalizowanie obszarów w naszym kodzie, w których nasze zapytania LINQ mogą działać w przypadku problemów z wydajnością, takich jak obniżona szybkośćlocating areas in our code where our LINQ queries might run into performance issues like degraded speed
  • obliczenia opóźnione i eager w odniesieniu do zapytań LINQ oraz implikacje, które mogą mieć na wydajność zapytańlazy and eager evaluation in regards to LINQ queries and the implications they might have on query performance

Oprócz LINQ wyuczysz się, jak korzystać z techniki Magicians na wskazówki dotyczące kart.Aside from LINQ, you learned a bit about a technique magicians use for card tricks. Magicians używają Faro losowo, ponieważ mogą one kontrolować, gdzie każda karta przenosi na talię.Magicians use the Faro shuffle because they can control where every card moves in the deck. Teraz, gdy już wiesz, nie ryzykuj go dla innych osób!Now that you know, don't spoil it for everyone else!

Aby uzyskać więcej informacji na temat LINQ, zobacz:For more information on LINQ, see: