Arbeiten mit LINQWorking with LINQ

EinführungIntroduction

In diesem Tutorial lernen Sie verschiedene Features in .NET Core und der Sprache C# kennen.This tutorial teaches you a number of 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 our 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/docs“ auf GitHub ansehen.You can also see the completed sample in the dotnet/docs 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 Seite .NET Core.You can find the installation instructions on the .NET Core 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

Beginnen wir damit, einen Kartenstapel zu erstellen.Let's start by creating a deck of cards. Zu diesem Zweck erstellen Sie eine LINQ-Abfrage mit zwei Quellen (eine für die vier Farben, eine für die dreizehn Werte).You'll do this using a LINQ query that has two sources (one for the four suits, one for the thirteen values). Diese Quellen werden Sie zu einem aus 52 Karten bestehenden Kartenstapel kombinieren.You'll combine those sources into a 52 card deck. Ein Console.WriteLine-Auszug innerhalb einer foreach-Schleife zeigt die Karten an.A Console.WriteLine statement inside a foreach loop displays the cards.

Hier ist die Abfrage:Here's the query:

var startingDeck = from s in Suits()
                   from r in Ranks()
                   select new { Suit = s, Rank = r };

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

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 (Werte) kombiniert.The first element in the first source sequence (Suits) is combined with every element in the second sequence (Values). 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.

Als Nächstes müssen Sie die Methoden „Suits()“ und „Ranks()“ erstellen.Next, you'll need to build the Suits() and Ranks() methods. Wir beginnen mit einem einfachen Satz von Iteratormethoden, die die Sequenz als Enumeration von Zeichenfolgen generieren:Let's start with a really simple set of iterator methods that generate the sequence as an enumerable of strings:

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";
}

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.

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 Values() ausgeführt werden.You may find it very helpful to run this sample under a debugger to observe how the Suits() and Values() 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.

Konsolenfenster, das die App zeigt, die 52 Karten schreibt

Ändern der ReihenfolgeManipulating the Order

Als Nächstes erstellen wir eine Hilfsprogrammmethode, die das Mischen ausführen kann.Next, let's build a utility method that can perform the shuffle. Der erste Schritt besteht darin, den Kartenstapel in zwei Hälften zu teilen.The first step is to split the deck in two. Die zu den LINQ-APIs gehörenden Methoden Take() und Skip() stellen uns diese Funktion bereit:The Take() and Skip() methods that are part of the LINQ APIs provide that feature for us:

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

Die Mischmethode ist in der Standardbibliothek nicht vorhanden, Sie müssen sie also selbst schreiben.The shuffle method doesn't exist in the standard library, so you'll have to write your own. Diese neue Methode veranschaulicht verschiedene Techniken, die Sie mit LINQ-basierten Programmen verwenden werden. Daher erläutern wir jeden Teil der Methode in den Schritten.This new method illustrates several techniques that you'll use with LINQ-based programs, so let's explain each part of the method in steps.

Die Signatur für die Methode erstellt eine Erweiterungsmethode:The signature for the method creates an extension method:

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

Bei einer Erweiterungsmethode handelt es sich um eine statische Methode, die einem bestimmten Zweck dient.An extension method is a special purpose static method. 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.

Erweiterungsmethoden können nur innerhalb von static-Klassen deklariert werden. Also erstellen wir hierfür eine neue statische Klasse namens extensions.Extension methods can be declared only inside static classes, so let's create a new static class called extensions for this functionality. Im weiteren Verlauf dieses Tutorials werden Sie weitere Erweiterungsmethoden hinzufügen. Diese werden in der gleichen Klasse platziert.You'll add more extension methods as you continue this tutorial, and those will be placed in the same class.

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.

using System.Collections.Generic;

namespace LinqFaroShuffle
{
    public static class Extensions
    {
        public static IEnumerable<T> InterleaveSequenceWith<T>
            (this IEnumerable<T> first, IEnumerable<T> second)
        {
            // implementation coming.
        }
    }
}

Sie zählen beide Sequenzen gleichzeitig auf, fächern die Elemente ineinander und erstellen ein einziges Objekt.You will be enumerating both sequences at once, interleaving the elements, and creating one object. Um eine LINQ-Methode schreiben zu können, die mit Sequenzen arbeitet, müssen Sie verstehen, wie IEnumerable funktioniert.Writing a LINQ method that works with two sequences requires that you understand how IEnumerable works.

Die IEnumerable-Schnittstelle weist eine einzige Methode auf: GetEnumerator().The IEnumerable 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:

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

Sehen wir uns an, wie viele Mischvorgänge benötigt werden, damit der Kartenstapel wieder in seiner ursprünglichen Reihenfolge vorliegt.Let's see how many shuffles it takes to set the deck back to its original order. Sie müssen eine Methode schreiben, die ermittelt, ob zwei Sequenzen gleich sind.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 jedes Element zurückgegeben werden soll, 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. Wenn diese Methoden verwendet werden, sind sie immer die letzten Methoden einer Abfrage.These methods, when they are used, are always the final method of a query. (Sie beenden – englisch: to terminate – die Abfrage, daher der Name.)(Hence the name).

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:

var times = 0;
var shuffle = startingDeck;

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

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

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

Console.WriteLine(times);

Führen Sie das Beispiel aus, und sehen Sie sich an, wie der Kartenstapel nach jedem Mischvorgang neu sortiert wird und nach 8 Durchgängen wieder seine ursprüngliche Konfiguration aufweist.Run the sample, and see how the deck rearranges on each shuffle, until it returns to its original configuration after 8 iterations.

OptimierungenOptimizations

Das Beispiel, das Sie bisher erstellt haben, führt einen Mischvorgang nach innen aus, bei dem die oberste und unterste Karte bei jedem Durchgang gleich bleiben.The sample you've built so far executes an in shuffle, where the top and bottom cards stay the same on each run. Als Nächstes führen wir einen Mischvorgang nach außen aus, bei dem alle 52 Karten ihre Position ändern.Let's make one change, and run an out shuffle, 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 out 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. Dazu muss nur eine Zeile geändert werden.That's just a one line change. Aktualisieren Sie den Aufruf zum Mischen so, dass die Reihenfolge der oberen und unteren Hälften des Kartenstapels getauscht wird:Update the call to shuffle to 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. Befassen wir uns mit einem der wichtigsten Gründe: der ineffizienten Nutzung der verzögerten Auswertung.Let's tackle one of the major causes: inefficient use of lazy evaluation.

LINQ-Abfragen werden verzögert ausgewertet.LINQ queries 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.

Der ursprüngliche Kartenstapel wurde mithilfe einer LINQ-Abfrage generiert.The original deck was generated 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.

Im Folgenden finden Sie eine Protokollmethode, die an jede Abfrage angefügt werden kann, um diese Abfrage als „ausgeführt“ zu kennzeichnen.Here's a log method that can be appended to any query to mark that the query executed.

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

    return sequence;
}

Als Nächstes instrumentieren Sie die Definition jeder Abfrage mit einer Protokollmeldung:Next, instrument the definition of each query with a log message:

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
    {
        /*
        shuffle = shuffle.Take(26)
            .LogQuery("Top Half")
            .InterleaveSequenceWith(shuffle.Skip(26)
            .LogQuery("Bottom Half"))
            .LogQuery("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 außen mit aktivierter Protokollierung ausgeführt wurde, wechseln Sie zurück zum Mischen nach innen.If you run out of patience running the outer shuffle with logging turned on, switch back to the inner 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.

Es gibt eine einfache Möglichkeit, dieses Programm so zu ändern, dass all diese Ausführungen vermieden werden.There is an easy way to update this program to avoid all those executions. Die LINQ-Methoden ToArray() und ToList() sorgen dafür, dass die Abfrage ausgeführt wird, und speichern die Ergebnisse in einem Array bzw. einer Liste.There are LINQ methods ToArray() and ToList() that cause the query to run, and store the results in an array or a list, respectively. Sie verwenden diese Methoden dazu, die Datenergebnisse einer Abfrage zwischenzuspeichern, anstatt die Quellabfrage erneut auszuführen.You use these methods to cache the data results of a query rather than execute the source query again. Fügen Sie die Abfragen, die die Kartenstapel generieren, an einen Aufruf von ToArray() an, und führen Sie die Abfrage erneut aus:Append the queries that generate the card decks with a call to ToArray() and run the query 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 PlayingCard(s, 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);
}

Führen Sie das Programm erneut aus – das Mischen nach innen wurde auf 30 Abfragen reduziert.Run again, and the inner shuffle is down to 30 queries. Auch beim Mischen nach außen werden Sie ähnliche Verbesserungen feststellen.Run again with the outer shuffle and you'll see similar improvements. (Dabei werden jetzt 162 Abfragen ausgeführt.)(It now executes 162 queries).

Sie dürfen dieses Beispiel aber nicht so interpretieren, dass alle Abfragen mit strikter Auswertung ausgeführt werden sollten.Don't misinterpret this example by thinking that all queries should run eagerly. Dieses Beispiel sollte nur Anwendungsfälle veranschaulichen, bei denen eine verzögerte Auswertung zu Leistungsproblemen führen kann.This example is designed to highlight the use cases where lazy evaluation can cause performance difficulties. Die Ursache des Problems ist, dass jede neue Anordnung des Kartenstapels aus der vorherigen Anordnung erstellt wird.That's 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 besser mit der strikten Auswertung ausgeführt, für andere eignet sich die verzögerte Auswertung besser.In practice, some algorithms run much better using eager evaluation, and others run much better using lazy evaluation. (Im Allgemeinen eignet sich die verzögerte Auswertung wesentlich besser, wenn es sich bei der Datenquelle um einen separaten Prozess handelt, beispielsweise um ein Datenbankmodul.(In general, lazy evaluation is a much better choice when the data source is a separate process, like a database engine. In diesen Fällen ermöglicht die verzögerte Auswertung komplexere Abfragen, um nur einen Roundtrip an den Datenbankprozess auszuführen.) LINQ ermöglicht sowohl eine verzögerte als auch eine strikte Auswertung.In those cases, lazy evaluation enables more complex queries to execute only one round trip to the database process.) LINQ enables both lazy and eager evaluation. Wählen Sie die für Ihr jeweiliges Programm am besten geeignete Variante aus.Measure, and pick the best choice.

Vorbereiten auf neue FunktionenPreparing for New Features

Der Code, den Sie für diesen Artikel geschrieben haben, ist ein Beispiel für die Erstellung eines einfachen Prototyps, der das gewünschte Ergebnis erzielt.The code you've written for this sample is an example of creating a simple prototype that does the job. Er eignet sich hervorragend, um einen Problembereich zu untersuchen, und ist für viele Funktionen möglicherweise die beste dauerhafte Lösung.This is a great way to explore a problem space, and for many features, it may be the best permanent solution. Sie haben anonyme Typen für die Karten verwendet, und jede Karte wird durch Zeichenfolgen dargestellt.You've leveraged anonymous types for the cards, and each card is represented by strings.

Anonyme Typen bieten viele Produktivitätsvorteile.Anonymous Types have many productivity advantages. Sie müssen nicht selbst eine Klasse definieren, um den Speicher darzustellen.You don't need to define a class yourself to represent the storage. Der Compiler generiert den Typ für Sie.The compiler generates the type for you. Der vom Compiler generierte Typ nutzt viele der bewährten Methoden für einfache Datenobjekte.The compiler generated type utilizes many of the best practices for simple data objects. Er ist unveränderlich, d.h., nach dem Erstellen kann keine seiner Eigenschaften geändert werden.It's immutable, meaning that none of its properties can be changed after it has been constructed. Anonyme Typen sind für eine Assembly intern, gehören also nicht zur öffentlichen API für diese Assembly.Anonymous types are internal to an assembly, so they aren't seen as part of the public API for that assembly. Anonyme Typen enthalten auch eine Überschreibung der ToString()-Methode, die eine formatierte Zeichenfolge mit den jeweiligen Werten zurückgibt.Anonymous types also contain an override of the ToString() method that returns a formatted string with each of the values.

Anonyme Typen haben jedoch auch Nachteile.Anonymous types also have disadvantages. Sie verfügen nicht über aufrufbare Namen, können also nicht als Rückgabewerte oder Argumente verwendet werden.They don't have accessible names, so you can't use them as return values or arguments. Sie werden feststellen, dass alle oben genannten Methoden, die diese anonymen Typen verwenden, generische Methoden sind.You'll notice that any methods above that used these anonymous types are generic methods. Die Überschreibung von ToString() ist möglicherweise nicht wünschenswert, wenn der Anwendung weitere Funktionen hinzugefügt werden.The override of ToString() may not be what you want as the application grows more features.

Das Beispiel verwendet auch Zeichenfolgen für die Farbe und den Rang jeder Karte.The sample also uses strings for the suit and the rank of each card. Es ist fast beliebig erweiterbar.That's quite open ended. Mit dem Typsystem von C# lässt sich ein besserer Code erstellen, indem enum-Typen für diese Werte verwendet werden.The C# type system can help us make better code, by leveraging enum types for those values.

Beginnen Sie mit den Farben.Start with the suits. Jetzt ist der perfekte Zeitpunkt, einen enum-Typ zu verwenden:This is a perfect time to use an enum:

public enum Suit
{
    Clubs,
    Diamonds,
    Hearts,
    Spades
}

Die Suits()-Methode ändert auch den Typ und Implementierung:The Suits() method also changes type and implementation:

static IEnumerable<Suit> Suits() => Enum.GetValues(typeof(Suit)) as IEnumerable<Suit>;

Als Nächstes nehmen Sie die gleiche Änderung am Rang der Karten vor:Next, do the same change with the Rank of the cards:

public enum Rank
{
    Two,
    Three,
    Four,
    Five,
    Six,
    Seven,
    Eight,
    Nine,
    Ten,
    Jack,
    Queen,
    King,
    Ace
}

Ändern Sie auch die Methode, die den Rang generiert:And the method that generates them:

static IEnumerable<Rank> Ranks() => Enum.GetValues(typeof(Rank)) as IEnumerable<Rank>;

Und um das Ganze abzuschließen, erstellen wir einen Typ zur Darstellung der Karte, statt einen anonymen Typ zu verwenden.As one final cleanup, let's make a type to represent the card, instead of relying on an anonymous type. Anonyme Typen eignen sich hervorragend für einfache lokale Typen, aber im vorliegenden Beispiel ist die Spielkarte eines der wichtigsten Konzepte.Anonymous types are great for lightweight, local types, but in this example, the playing card is one of the main concepts. Daher sollte sie einen konkreten Typ aufweisen.It should be a concrete type.

public class PlayingCard
{
    public Suit CardSuit { get; }
    public Rank CardRank { get; }
    
    public PlayingCard(Suit s, Rank r)
    {
        CardSuit = s;
        CardRank = r;
    }
    
    public override string ToString()
    {
        return $"{CardRank} of {CardSuit}";
    }
}

Dieser Typ verwendet automatisch implementierte, schreibgeschützte Eigenschaften, die im Konstruktor festgelegt werden und danach nicht mehr geändert werden können.This type uses auto-implemented read-only properties which are set in the constructor, and then cannot be modified. Er verwendet auch die neue Funktion der Zeichenfolgeninterpolation, mit der sich die Zeichenfolgenausgabe einfacher formatieren lässt.It also makes use of the new string interpolation feature that makes it easier to format string output.

Aktualisieren Sie die Abfrage zum Generieren des anfänglichen Kartenstapels so, dass der neue Typ verwendet wird:Update the query that generates the starting deck to use the new type:

var startingDeck = (from s in Suits().LogQuery("Suit Generation")
                    from r in Ranks().LogQuery("Value Generation")
                    select new PlayingCard(s, r))
                    .LogQuery("Starting Deck")
                    .ToArray();

Kompilieren Sie den Code, und führen Sie ihn erneut aus.Compile and run again. Die Ausgabe ist etwas sauberer, und der Code ist klarer und kann einfacher erweitert werden.The output is a little cleaner, and the code is a bit more clear and can be extended more easily.

SchlussfolgerungConclusion

Dieses Beispiel sollte einige der in LINQ verwendeten Methoden veranschaulichen und Ihnen zeigen, wie Sie eigene Methoden erstellen, die sich mit LINQ-aktiviertem Code ganz einfach einsetzen lassen.This sample should you some of the methods used in LINQ, how to create your own methods that will be easily used with LINQ enabled code. Das Beispiel hat auch die Unterschiede zwischen verzögerter und strikter Auswertung aufgezeigt und erläutert, wie sich die Entscheidung für die eine oder andere Variante auf die Leistung auswirken kann.It also showed you the differences between lazy and eager evaluation, and the affect that decision can have on performance.

Sie haben etwas über die Techniken von Zauberkünstlern erfahren.You learned a bit about one magician's technique. Zauberer verwenden den Faro-Shuffle, weil sie damit genau steuern können, wann sich welche Karte wo im Stapel befindet.Magician's use the faro shuffle because they can control where every card moves in the deck. Bei manchen Tricks bittet der Zauberer einen Zuschauer, eine Karte ganz nach oben auf den Stapel zu legen. Dann mischt der Zauberer die Karten einige Male und weiß genau, wo die Karte ist.In some tricks, the magician has an audience member place a card on top of the deck, and shuffles a few times, knowing where that card goes. Für andere Tricks muss der Kartenstapel auf eine ganz bestimmte Art sortiert sein.Other illusions require the deck set a certain way. Der Zauberer bereitet den Stapel vor dem Trick vor.A magician will set the deck prior to performing the trick. Dann mischt er den Stapel 5-mal nach innen.Then she will shuffle the deck 5 times using an inner shuffle. Auf der Bühne zeigt er den Zuschauern dann einen Kartenstapel, der zufällig gemischt aussieht, mischt ihn noch 3-mal und erzielt so genau die gewünschte Kartenreihenfolge.On stage, she can show what looks like a random deck, shuffle it 3 more times, and have the deck set exactly how she wants.