Arbeiten mit LINQWorking with LINQ

EinführungIntroduction

In diesem Tutorial lernen Sie Features in .NET Core und der Sprache C# kennen.This tutorial teaches you features in .NET Core and the C# language. Es werden die folgenden Themen abgedeckt:You’ll learn:

  • Generieren von Sequenzen mit LINQHow to generate sequences with LINQ.
  • Schreiben von Methoden, die sich problemlos in LINQ-Abfragen verwenden lassenHow to write methods that can be easily used in LINQ queries.
  • Unterscheiden zwischen strenger und verzögerter AuswertungHow to distinguish between eager and lazy evaluation.

Sie lernen diese Techniken durch Erstellen einer Anwendung, die eine der grundlegenden Fertigkeiten jedes Zauberkünstlers demonstriert: den Faro-Shuffle.You'll learn these techniques by building an application that demonstrates one of the basic skills of any magician: the faro shuffle. Ein Faro-Shuffle ist eine Kartenmischtechnik, bei der zwei Kartenpäckchen exakt so ineinander gefächert werden, dass auf eine Karte des einen Stapels stets eine Karte des anderen Stapels folgt.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.

Zauberer verwenden diese Technik, weil sie damit nach jedem Mischen genau wissen, welche Karte sich wo befindet, und die Reihenfolge ein sich wiederholendes Muster ist.Magicians use this technique because every card is in a known location after each shuffle, and the order is a repeating pattern.

Im vorliegenden Artikel dient die Technik dazu, das Manipulieren von Datensequenzen mit einem anschaulichen Beispiel zu zeigen.For your purposes, it is a light hearted look at manipulating sequences of data. Die von Ihnen erstellte Anwendung stellt einen Kartenstapel zusammen und führt dann eine Sequenz aus Mischvorgängen aus, wobei die Sequenz jedes Mal ausgegeben wird.The application you'll build will construct a card deck, and then perform a sequence of shuffles, writing the sequence out each time. Sie werden auch die aktualisierte mit der ursprünglichen Reihenfolge vergleichen.You'll also compare the updated order to the original order.

Dieses Tutorial besteht aus vielen Schritten.This tutorial has multiple steps. Sie können die Anwendung nach jedem Schritt ausführen und sich den Fortschritt ansehen.After each step, you can run the application and see the progress. Sie können sich auch das abgeschlossene Beispiel in unserem Repository „dotnet/samples“ auf GitHub ansehen.You can also see the completed sample in the dotnet/samples GitHub repository. Anweisungen zum Herunterladen finden Sie unter Beispiele und Lernprogramme.For download instructions, see Samples and Tutorials.

Erforderliche KomponentenPrerequisites

Sie müssen Ihren Computer zur Ausführung von .NET Core einrichten.You’ll need to setup your machine to run .NET core. Die Installationsanweisungen finden Sie auf der Downloadseite für .NET Core.You can find the installation instructions on the .NET Core Download page. Sie können diese Anwendung unter Windows, Ubuntu Linux, OS X oder in einem Docker-Container ausführen.You can run this application on Windows, Ubuntu Linux, OS X or in a Docker container. Sie müssen Ihren bevorzugten Code-Editor installieren.You’ll need to install your favorite code editor. In den folgenden Beschreibungen wird Visual Studio Code verwendet. Hierbei handelt es sich um einen plattformübergreifenden Open Source-Editor.The descriptions below use Visual Studio Code which is an open source, cross platform editor. Sie können jedoch auch ein beliebiges anderes Tool verwenden, mit dem Sie vertraut sind.However, you can use whatever tools you are comfortable with.

Erstellen der AnwendungCreate the Application

Im ersten Schritt wird eine neue Anwendung erstellt.The first step is to create a new application. Öffnen Sie eine Eingabeaufforderung, und erstellen Sie ein neues Verzeichnis für Ihre Anwendung.Open a command prompt and create a new directory for your application. Legen Sie das Verzeichnis als aktuelles Verzeichnis fest.Make that the current directory. Geben Sie an der Eingabeaufforderung den Befehl dotnet new console ein.Type the command dotnet new console at the command prompt. Hierdurch werden die Startdateien für eine einfache „Hello World“-Anwendung erstellt.This creates the starter files for a basic "Hello World" application.

Wenn Sie C# noch nie verwendet haben, erläutert dieses Tutorial die Struktur eines C#-Programms.If you've never used C# before, this tutorial explains the structure of a C# program. Sie können dieses Tutorial lesen und dann zu diesem Artikel zurückkehren, um mehr über LINQ zu erfahren.You can read that and then return here to learn more about LINQ.

Erstellen des DatasetsCreating the Data Set

Bevor Sie beginnen, stellen Sie sicher, dass die folgenden Zeilen am Anfang der Datei Program.cs stehen, die von dotnet new console generiert wurde: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;

Wenn diese drei Zeilen (using-Anweisungen) nicht am Anfang der Datei stehen, wird unser Programm nicht kompiliert.If these three lines (using statements) aren't at the top of the file, our program will not compile.

Jetzt haben Sie alle nötigen Referenzen und können die Struktur eines Spielkartenstapels berücksichtigen.Now that you have all of the references that you'll need, consider what constitutes a deck of cards. In der Regel besteht ein Spielkartenstapel aus vier Farben, und jede hat dreizehn Werte.Commonly, a deck of playing cards has four suits, and each suit has thirteen values. In der Regel würden Sie wohl direkt eine Card-Klasse erstellen und eine Sammlung von Card-Objekten manuell füllen.Normally, you might consider creating a Card class right off the bat and populating a collection of Card objects by hand. Mit LINQ können Sie einen Spielkartenstapel präziser als üblich erstellen.With LINQ, you can be more concise than the usual way of dealing with creating a deck of cards. Statt eine Card-Klasse zu erstellen, können Sie zwei Sequenzen zur Darstellung von Farben und Rängen erstellen.Instead of creating a Card class, you can create two sequences to represent suits and ranks, respectively. Sie erstellen ein einfaches Paar von Iteratormethoden, das die Ränge und Farben als IEnumerable<T>s von Zeichenfolgen generiert: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";
}

Platzieren Sie diese unterhalb der Main-Methode in Ihrer Program.cs-Datei.Place these underneath the Main method in your Program.cs file. Diese Methoden nutzen beide die yield return-Syntax, um während der Ausführung eine Sequenz zu erzeugen.These two methods both utilize the yield return syntax to produce a sequence as they run. Der Compiler erstellt ein Objekt, das IEnumerable<T>implementiert und die Sequenz aus Zeichenfolgen erst dann generiert, wenn diese angefordert werden.The compiler builds an object that implements IEnumerable<T> and generates the sequence of strings as they are requested.

Verwenden Sie nun diese Iteratormethoden, um den Spielkartenstapel zu erstellen.Now, use these iterator methods to create the deck of cards. Sie platzieren die LINQ-Abfrage in unserer Main-Methode.You'll place the LINQ query in our Main method. Sie sieht wie folgt aus: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);
    }
}

Die verschiedenen from-Klauseln erzeugen SelectMany, wodurch aus der Kombination jedes Elements in der ersten Sequenz mit jedem Element in der zweiten Sequenz eine einzige Sequenz erstellt wird.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. Für unsere Zwecke ist die Reihenfolge wichtig.The order is important for our purposes. Das erste Element in der ersten Quellsequenz (Farben) wird mit jedem Element in der zweiten Sequenz (Ränge) kombiniert.The first element in the first source sequence (Suits) is combined with every element in the second sequence (Ranks). Dadurch werden alle dreizehn Karten der ersten Farbe erzeugt.This produces all thirteen cards of first suit. Dieser Vorgang wird mit jedem Element in der ersten Sequenz (Farben) wiederholt.That process is repeated with each element in the first sequence (Suits). Das Ergebnis ist ein Kartenstapel, der erst nach Farben und innerhalb der Farben nach Werten sortiert ist.The end result is a deck of cards ordered by suits, followed by values.

Beachten Sie unbedingt Folgendes: Ob Sie LINQ-Anweisungen in der oben verwendeten Abfragesyntax schreiben oder stattdessen die Methodensyntax verwenden, Sie können immer von einer Form der Syntax zur anderen wechseln.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. Die obige, in der Abfragesyntax geschriebene Abfrage kann in der Methodensyntax geschrieben werden als: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 }));

Der Compiler übersetzt mit der Abfragesyntax geschriebene LINQ-Anweisungen in die entsprechende Methodenaufrufsyntax.The compiler translates LINQ statements written with query syntax into the equivalent method call syntax. Aus diesem Grund erzielen unabhängig von Ihrer Syntaxwahl die beiden Versionen der Abfrage das gleiche Ergebnis.Therefore, regardless of your syntax choice, the two versions of the query produce the same result. Wählen Sie die beste Syntax für Ihre Situation aus: Wenn Sie z.B. in einem Team arbeiten, in dem einige Mitglieder mit der Methodensyntax Schwierigkeiten haben, versuchen Sie, die Abfragesyntax zu bevorzugen.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.

Führen Sie jetzt das erstellte Beispiel aus.Go ahead and run the sample you've built at this point. Es werden alle 52 Karten des Kartenstapels angezeigt.It will display all 52 cards in the deck. Es kann sehr hilfreich sein, dieses Beispiel mit einem Debugger auszuführen, um zu beobachten, wie die Methoden Suits() und Ranks() ausgeführt werden.You may find it very helpful to run this sample under a debugger to observe how the Suits() and Ranks() methods execute. Sie können deutlich erkennen, dass jede Zeichenfolge in jeder Sequenz erst dann erstellt wird, wenn sie benötigt wird.You can clearly see that each string in each sequence is generated only as it is needed.

Ein Konsolenfenster, das die App zeigt, die 52 Karten schreibt.

Ändern der ReihenfolgeManipulating the Order

Konzentrieren Sie sich nun darauf, wie die Karten im Kartenstapel gemischt werden sollen.Next, focus on how you're going to shuffle the cards in the deck. Der erste Schritt bei jedem guten Mischen ist das Aufteilen des Kartenstapels in zwei Hälften.The first step in any good shuffle is to split the deck in two. Die zu den LINQ-APIs gehörenden Methoden Take und Skip stellen Ihnen diese Funktion bereit.The Take and Skip methods that are part of the LINQ APIs provide that feature for you. Platzieren Sie sie unterhalb der foreach-Schleife: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);
}

In der Standardbibliothek ist jedoch keine Mischmethode vorhanden, Sie müssen sie also selbst schreiben.However, there's no shuffle method to take advantage of in the standard library, so you'll have to write your own. Die Mischmethode, die Sie erstellen, veranschaulicht verschiedene Techniken, die Sie mit LINQ-basierten Programmen verwenden, sodass jeder Teil dieses Prozesses in Schritten erläutert wird.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.

Um Funktionalität für Ihre Interaktion mit den IEnumerable<T>-Formen, die Sie von einigen LINQ-Abfragen erhalten, hinzuzufügen, müssen Sie einige besondere Arten von Methoden schreiben, die als Erweiterungsmethoden bezeichnet werden.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. Eine Erweiterungsmethode ist im Wesentlichen eine statische Methode für einen speziellen Zweck, die einem bereits vorhandenen Typ Funktionalität hinzufügt, ohne diesen ursprünglichen Typ ändern zu müssen.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.

Fügen Sie für Ihre Erweiterungsmethoden eine neue statische Klassendatei namens Extensions.cs Ihrem Programm hinzu, und beginnen Sie dann, die erste Erweiterungsmethode zu erstellen: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
        }
    }
}

Beachten Sie für einen Moment die Signatur der Methode, insbesondere die Parameter:Look at the method signature for a moment, specifically the parameters:

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

Sie sehen, dass dem ersten Argument der Methode der this-Modifizierer hinzugefügt wurde.You can see the addition of the this modifier on the first argument to the method. Dies bedeutet, dass Sie die Methode so aufrufen können, als wäre sie eine Membermethode vom Typ des ersten Arguments.That means you call the method as though it were a member method of the type of the first argument. Diese Methodendeklaration folgt auch einem Standardidiom, bei dem die Eingabe- und Ausgabetypen IEnumerable<T> sind.This method declaration also follows a standard idiom where the input and output types are IEnumerable<T>. Auf diese Weise können LINQ-Methoden miteinander verkettet werden, um komplexere Abfragen auszuführen.That practice enables LINQ methods to be chained together to perform more complex queries.

Wenn Sie den Kartenstapel in zwei Hälften geteilt haben, müssen Sie diese Hälften natürlich auch wieder zusammenführen.Naturally, since you split the deck into halves, you'll need to join those halves together. Im Code zählen Sie beide Sequenzen, die Sie durch Take und Skip erhalten haben, gleichzeitig auf, führen ein interleaving der Elemente durch und erstellen eine einzige Sequenz: Ihr jetzt gemischter Kartenstapel.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. Um eine LINQ-Methode schreiben zu können, die mit Sequenzen arbeitet, müssen Sie verstehen, wie IEnumerable<T> funktioniert.Writing a LINQ method that works with two sequences requires that you understand how IEnumerable<T> works.

Die IEnumerable<T>-Schnittstelle weist eine einzige Methode auf: GetEnumerator.The IEnumerable<T> interface has one method: GetEnumerator. Das von GetEnumerator zurückgegebene Objekt verfügt über eine Methode, um zum nächsten Element zu wechseln, und eine Eigenschaft, die das aktuelle Element in der Sequenz abruft.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. Sie verwenden diese beiden Member, um die Auflistung aufzuzählen und die Elemente zurückzugeben.You will use those two members to enumerate the collection and return the elements. Diese Interleavemethode ist eine Iteratormethode, daher verwenden Sie die oben gezeigte yield return-Syntax, anstatt eine Auflistung zu erstellen und die Auflistung zurückzugeben.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.

Hier sehen Sie die Implementierung dieser Methode: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;
    }
}

Nachdem Sie diese Methode geschrieben haben, kehren Sie jetzt zur Main-Methode zurück und mischen den Kartenstapel ein Mal: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);
    }
}

VergleicheComparisons

Wie viele Mischvorgänge sind nötig, damit der Kartenstapel wieder in seiner ursprünglichen Reihenfolge vorliegt?How many shuffles it takes to set the deck back to its original order? Um dies herauszufinden, müssen Sie eine Methode schreiben, die ermittelt, ob zwei Sequenzen gleich sind.To find out, you'll need to write a method that determines if two sequences are equal. Nachdem Sie diese Methode erstellt haben, müssen Sie den Code, der den Stapel mischt, in eine Schleife platzieren und dann prüfen, wann der Stapel wieder die ursprüngliche Reihenfolge aufweist.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.

Das Schreiben einer Methode, mit der ermittelt wird, ob die beiden Sequenzen gleich sind, sollte kein Problem sein.Writing a method to determine if the two sequences are equal should be straightforward. Die Methode hat die gleiche Struktur wie die Methode, die Sie zum Mischen des Kartenstapels geschrieben haben.It's a similar structure to the method you wrote to shuffle the deck. Der Unterschied ist, dass dieses Mal nicht für jedes Element ein yield return ausgeführt wird, sondern dass Sie die übereinstimmenden Elemente jeder Sequenz vergleichen.Only this time, instead of yield returning each element, you'll compare the matching elements of each sequence. Wenn die gesamte Sequenz aufgezählt wurde und alle Elemente übereinstimmen, sind die Sequenzen gleich: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;
}

Dies zeigt ein zweites LINQ-Idiom: Terminalmethoden.This shows a second LINQ idiom: terminal methods. Diese Methoden akzeptieren eine Sequenz als Eingabe (bzw. in diesem Fall zwei Sequenzen) und geben einen einzelnen Skalarwert zurück.They take a sequence as input (or in this case, two sequences), and return a single scalar value. Terminalmethoden sind immer die endgültige Methode in einer Kette von Methoden für eine LINQ-Abfrage, daher der Namen „Terminalmethode“.When using terminal methods, they are always the final method in a chain of methods for a LINQ query, hence the name "terminal".

Sie können diese Methode in Aktion sehen, wenn Sie sie verwenden, um zu ermitteln, wann der Kartenstapel wieder die ursprüngliche Reihenfolge aufweist.You can see this in action when you use it to determine when the deck is back in its original order. Platzieren Sie den Code zum Mischen in einer Schleife, und halten Sie diese an, wenn die Sequenz wieder die ursprüngliche Reihenfolge aufweist. Wenden Sie hierzu die SequenceEquals()-Methode an.Put the shuffle code inside a loop, and stop when the sequence is back in its original order by applying the SequenceEquals() method. Hier sehen Sie, warum diese Methode immer die letzte in einer Abfrage sein muss: sie gibt anstelle einer Sequenz einen einzelnen Wert zurück: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);
}

Führen Sie den bisher erstellten Code aus, und notieren Sie sich, wie der Kartenstapel bei jedem Mischvorgang neu angeordnet wird.Run the code you've got so far and take note of how the deck rearranges on each shuffle. Nach 8 Mischvorgängen (Iterationen der Do-while-Schleife) kehrt der Stapel zur ursprünglichen Konfiguration zurück, die er hatte, als Sie ihn zum ersten Mal durch Starten der LINQ-Abfrage erstellt haben.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.

OptimierungenOptimizations

Das Beispiel, das Sie bisher erstellt haben, führt einen Mischvorgang nach außen aus, bei dem die oberste und unterste Karte bei jedem Durchgang gleich bleiben.The sample you've built so far executes an out shuffle, where the top and bottom cards stay the same on each run. Als Nächstes führen wir stattdessen einen Mischvorgang nach innen aus, bei dem alle 52 Karten ihre Position ändern.Let's make one change: we'll use an in shuffle instead, where all 52 cards change position. Hierbei werden die Hälften so ineinander gefächert, dass die erste Karte der unteren Hälfte zur ersten Karte des Kartenstapels wird.For an in shuffle, you interleave the deck so that the first card in the bottom half becomes the first card in the deck. Das bedeutet, dass die unterste Karte der oberen Hälfte zur untersten Karte des Stapels wird.That means the last card in the top half becomes the bottom card. Dies ist eine einfache Änderung einer einzelnen Codezeile.This is a simple change to a singular line of code. Aktualisieren Sie den derzeitigen Mischvorgang durch Wechseln der Positionen von Take und Skip.Update the current shuffle query by switching the positions of Take and Skip. Dies ändert die Reihenfolge der oberen und unteren Hälfte des Kartenstapels:This will change the order of the top and bottom halves of the deck:

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

Führen Sie das Programm erneut aus, Sie werden feststellen, dass 52 Iterationen nötig sind, um den Kartenstapel wieder in die ursprüngliche Reihenfolge zu bringen.Run the program again, and you'll see that it takes 52 iterations for the deck to reorder itself. Sie werden auch einige erhebliche Leistungsabfälle beim Ausführen des Programms bemerken.You'll also start to notice some serious performance degradations as the program continues to run.

Dafür gibt es eine Anzahl von Gründen.There are a number of reasons for this. Sie können eine der wichtigsten Ursachen dieses Leistungsabfalls bekämpfen: die ineffiziente Nutzung der verzögerten Auswertung.You can tackle one of the major causes of this performance drop: inefficient use of lazy evaluation.

Der wesentliche Aspekt der verzögerten Auswertung ist, dass eine Anweisung erst dann ausgewertet wird, wenn der Wert benötigt wird.Briefly, lazy evaluation states that the evaluation of a statement is not performed until its value is needed. LINQ-Abfragen sind verzögert ausgewertete Anweisungen.LINQ queries are statements that are evaluated lazily. Die Sequenzen werden erst generiert, wenn die Elemente angefordert werden.The sequences are generated only as the elements are requested. Dies ist üblicherweise ein großer Vorteil von LINQ.Usually, that's a major benefit of LINQ. In Programmen wie diesem verursacht diese Art der Auswertung jedoch ein exponentielles Wachstum der Ausführungszeit.However, in a use such as this program, this causes exponential growth in execution time.

Denken Sie daran, dass wir den ursprünglichen Stapel mithilfe einer LINQ-Abfrage generiert haben.Remember that we generated the original deck using a LINQ query. Jede Mischung wird durch Ausführung dreier LINQ-Abfragen im vorherigen Kartenstapel generiert.Each shuffle is generated by performing three LINQ queries on the previous deck. All diese werden verzögert ausgeführt.All these are performed lazily. Das bedeutet auch, dass sie bei jeder Anforderung der Sequenz erneut ausgeführt werden.That also means they are performed again each time the sequence is requested. Zum Zeitpunkt der 52. Iteration haben Sie den ursprünglichen Stapel also sehr häufig neu generiert.By the time you get to the 52nd iteration, you're regenerating the original deck many, many times. Nun schreiben wir ein Protokoll, das dieses Verhalten veranschaulicht.Let's write a log to demonstrate this behavior. Danach werden wir das Problem beheben.Then, you'll fix it.

Geben Sie die folgende Methode in Ihre Extensions.cs-Datei ein, bzw. kopieren Sie sie hinein.In your Extensions.cs file, type in or copy the method below. Diese Erweiterungsmethode erstellt eine neue Datei namens debug.log in Ihrem Projektverzeichnis, und zeichnet in der Protokolldatei auf, welche Abfrage derzeit ausgeführt wird.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. Diese Erweiterungsmethode kann jeder Abfrage angefügt werden, um diese Abfrage als „ausgeführt“ zu kennzeichnen.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;
}

Dann werden unter File rote Wellenlinien angezeigt. Das bedeutet, dass dieses Element nicht vorhanden ist.You will see a red squiggle under File, meaning it doesn't exist. Es erfolgt keine Kompilierung, da der Compiler nicht weiß, worum es sich bei File handelt.It won't compile, since the compiler doesn't know what File is. Sie können dieses Problem lösen, indem Sie die folgende Codezeile unter die erste Zeile der Datei Extensions.cs einfügen:To solve this problem, make sure to add the following line of code under the very first line in Extensions.cs:

using System.IO;

Dadurch sollten das Problem behoben und die roten Wellenlinien entfernt werden.This should solve the issue and the red error disappears.

Als Nächstes instrumentieren Sie die Definition jeder Abfrage mit einer Protokollmeldung: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);
}

Beachten Sie, dass beim Zugreifen auf eine Abfrage kein Protokolleintrag erstellt wird.Notice that you don't log every time you access a query. Ein Protokolleintrag wird nur erstellt, wenn Sie die ursprüngliche Abfrage erstellen.You log only when you create the original query. Die Ausführung des Programms dauert immer noch lang, aber nun sehen Sie den Grund dafür.The program still takes a long time to run, but now you can see why. Wenn Sie nicht warten möchten, bis das Mischen nach innen mit aktivierter Protokollierung ausgeführt wurde, wechseln Sie zurück zum Mischen nach außen.If you run out of patience running the in shuffle with logging turned on, switch back to the out shuffle. Sie sehen immer noch die Auswirkungen der verzögerten Auswertung.You'll still see the lazy evaluation effects. In einer Ausführung werden 2592 Abfragen ausgeführt, einschließlich der Generierung aller Werte und Farben.In one run, it executes 2592 queries, including all the value and suit generation.

Sie können hier die Leistung des Codes verbessern, um die Anzahl der Ausführungen zu reduzieren, die Sie vornehmen.You can improve the performance of the code here to reduce the number of executions you make. Eine einfache Lösung ist das Zwischenspeichern der Ergebnisse der ursprünglichen LINQ-Abfrage, die den Kartenstapel erstellt.A simple fix you can make is to cache the results of the original LINQ query that constructs the deck of cards. Derzeit führen Sie die Abfragen jedes Mal erneut aus, wenn die Do-while-Schleife eine Iteration durchläuft, wobei Sie jedes Mal den Kartenstapel neu erstellen und mischen.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. Zum Zwischenspeichern des Kartenstapels können Sie die LINQ-Methoden ToArray und ToList nutzen; wenn Sie sie an die Abfragen anfügen, führen sie die gleichen Aktionen aus, für die Sie sie programmiert haben, aber jetzt speichern sie die Ergebnisse in einem Array oder einer Liste, je nachdem, welche Methode Sie auswählen.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. Fügen Sie die LINQ-Methode ToArray an beide Abfragen an, und führen Sie das Programm erneut aus: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);
}

Jetzt ist der Mischvorgang nach außen auf 30 Abfragen reduziert.Now the out shuffle is down to 30 queries. Auch beim Mischen nach innen werden Sie ähnliche Verbesserungen feststellen: Jetzt werden 162 Abfragen ausgeführt.Run again with the in shuffle and you'll see similar improvements: it now executes 162 queries.

Bitte beachten Sie: Dieses Beispiel sollte nur Anwendungsfälle veranschaulichen, bei denen eine verzögerte Auswertung zu Leistungsproblemen führen kann.Please note that this example is designed to highlight the use cases where lazy evaluation can cause performance difficulties. Es ist wichtig, festzustellen, wo die verzögerte Auswertung die Codeleistung beeinträchtigen kann, aber es ist gleichermaßen wichtig, zu verstehen, dass nicht alle Abfragen strikt ausgeführt werden sollten.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. Ursache der Leistungseinbußen, die auftreten, wenn Sie ToArray nicht verwenden, ist, dass jede neue Anordnung des Kartenstapels aus der vorherigen Anordnung erstellt wird.The performance hit you incur without using ToArray is because each new arrangement of the deck of cards is built from the previous arrangement. Bei der verzögerten Auswertung bedeutet dies, dass jede neue Kartenstapelkonfiguration aus dem ursprünglichen Kartenstapel erzeugt wird. Dabei wird sogar der Code ausgeführt, der den startingDeck-Stapel erstellt hat.Using lazy evaluation means each new deck configuration is built from the original deck, even executing the code that built the startingDeck. Das verursacht eine Menge zusätzlichen Aufwands.That causes a large amount of extra work.

In der Praxis werden manche Algorithmen gut mit der strikten Auswertung ausgeführt, für andere eignet sich die verzögerte Auswertung besser.In practice, some algorithms run well using eager evaluation, and others run well using lazy evaluation. Für die routinemäßige Nutzung eignet sich die verzögerte Auswertung in der Regel besser, wenn es sich bei der Datenquelle um einen separaten Prozess handelt, beispielsweise um eine Datenbank-Engine.For daily usage, lazy evaluation is usually a better choice when the data source is a separate process, like a database engine. Bei Datenbanken ermöglicht die verzögerte Auswertung komplexere Abfragen, um nur einen Roundtrip zum Datenbankprozess und zurück zu Ihrem übrigen Code auszuführen.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 ist flexibel, unabhängig davon, ob Sie die verzögerte oder strikte Auswertung verwenden, also wägen Sie Ihre Prozesse ab, und wählen Sie die Art der Auswertung aus, die Ihnen die beste Leistung bietet.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.

SchlussbemerkungConclusion

In diesem Projekt wurde Folgendes behandelt:In this project, you covered:

  • Verwendung von LINQ-Abfragen zum Aggregieren von Daten in einer sinnvollen Abfolgeusing LINQ queries to aggregate data into a meaningful sequence
  • Schreiben von Erweiterungsmethoden zum Hinzufügen eigener benutzerdefinierter Funktionalität zu LINQ-Abfragenwriting Extension methods to add our own custom functionality to LINQ queries
  • Lokalisieren von Bereichen in unserem Code, in denen LINQ-Abfragen zu Leistungsproblemen wie Geschwindigkeitseinbußen führen könnenlocating areas in our code where our LINQ queries might run into performance issues like degraded speed
  • verzögerte und strikte Auswertung im Hinblick auf die LINQ-Abfragen und die möglichen Auswirkungen auf die Abfrageleistunglazy and eager evaluation in regards to LINQ queries and the implications they might have on query performance

Abgesehen von LINQ haben Sie ein wenig über die Verfahren gelernt, die Zauberer für Kartentricks anwenden.Aside from LINQ, you learned a bit about a technique magicians use for card tricks. Zauberer verwenden den Faro-Shuffle, weil sie damit genau steuern können, wann sich welche Karte wo im Stapel befindet.Magicians use the Faro shuffle because they can control where every card moves in the deck. Verderben Sie mit Ihrem Wissen jetzt aber nicht anderen den Spaß!Now that you know, don't spoil it for everyone else!

Weitere Informationen zu LINQ finden Sie unter:For more information on LINQ, see: