Freigeben über


Arbeiten mit LINQ (Language-Integrated Query)

Einleitung

In diesem Lernprogramm lernen Sie Features in .NET und der C#-Sprache kennen. Hier erfahren Sie, wie Sie:

  • Generieren Sie Sequenzen mit LINQ.
  • Schreiben Sie Methoden, die Sie ganz einfach in LINQ-Abfragen verwenden können.
  • Unterscheiden Sie zwischen eifriger und fauler Bewertung.

Sie lernen diese Techniken, indem Sie eine Anwendung erstellen, die eine der grundlegenden Fähigkeiten jedes Zauberers veranschaulicht: der Faro-Shuffle. Ein Faro-Shuffle ist eine Technik, bei der Sie einen Kartenstapel genau in der Mitte teilen und dann jede Karte von jeder Hälfte so ineinanderfügen, dass der ursprüngliche Stapel wiederhergestellt wird.

Zauberer verwenden diese Technik, da sich jede Karte nach jedem Shuffle an einer bekannten Stelle befindet und die Reihenfolge ein wiederholtes Muster ist.

Dieses Tutorial bietet einen lockeren Einblick in die Manipulation von Datensequenzen. Die Anwendung erstellt einen Kartensatz, führt eine Sequenz von Shuffles aus und schreibt die Sequenz jedes Mal aus. Außerdem wird die aktualisierte Bestellung mit der ursprünglichen Bestellung verglichen.

Dieses Lernprogramm enthält mehrere Schritte. Nach jedem Schritt können Sie die Anwendung ausführen und den Fortschritt anzeigen. Sie können sich auch das abgeschlossene Beispiel in unserem Repository „dotnet/samples“ auf GitHub ansehen. Anweisungen zum Herunterladen finden Sie unter Beispiele und Lernprogramme.

Voraussetzungen

Erstellen der Anwendung

Eine neue Anwendung erstellen. Öffnen Sie eine Eingabeaufforderung, und erstellen Sie ein neues Verzeichnis für Ihre Anwendung. Legen Sie das Verzeichnis als aktuelles Verzeichnis fest. Geben Sie den Befehl dotnet new console -o LinqFaroShuffle an der Eingabeaufforderung ein. Mit diesem Befehl werden die Startdateien für eine einfache "Hello World"-Anwendung erstellt.

Wenn Sie C# noch nie verwendet haben, dieses Lernprogramm die Struktur eines C#-Programms erläutert. Sie können das lesen und dann hier zurückkehren, um mehr über LINQ zu erfahren.

Erstellen des Datasets

Tipp

In diesem Lernprogramm können Sie Ihren Code in einem Namespace organisieren, der LinqFaroShuffle genannt wird, um dem Beispielcode zu entsprechen, oder Sie können den standardmäßigen globalen Namespace verwenden. Wenn Sie einen Namespace verwenden, stellen Sie sicher, dass alle Klassen und Methoden konsistent innerhalb desselben Namespaces sind, oder fügen Sie bei Bedarf entsprechende using Anweisungen hinzu.

Überlegen Sie, was eine Kartensammlung darstellt. Ein Deck mit Spielkarten hat vier Anzüge, und jeder Anzug hat 13 Werte. Normalerweise würden Sie möglicherweise eine Card Klasse erstellen und eine Sammlung von Card Objekten manuell auffüllen. Mit LINQ können Sie präziser sein als die übliche Methode zum Erstellen eines Kartendecks. Anstatt eine Card Klasse zu erstellen, erstellen Sie zwei Sequenzen, um Anzüge und Rangfolgen darzustellen. Erstellen Sie ein Paar iteratormethoden , die die Rangfolgen und Anzüge als IEnumerable<T>Zeichenfolgen generieren:

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 Methoden unter der Console.WriteLine-Anweisung in Ihrer Program.cs-Datei. Diese beiden Methoden verwenden beide die yield return Syntax, um während der Ausführung eine Sequenz zu erzeugen. Der Compiler erstellt ein Objekt, das IEnumerable<T> implementiert und die Sequenz von Zeichenfolgen generiert, sobald sie angefordert wird.

Verwenden Sie nun diese Iteratormethoden, um den Kartensatz zu erstellen. Platzieren Sie die LINQ-Abfrage am Anfang der Program.cs Datei. So sieht es aus:

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

// Display each card that's generated and placed in startingDeck
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. Die Reihenfolge ist für dieses Beispiel wichtig. Das erste Element in der ersten Quellsequenz (Farben) wird mit jedem Element in der zweiten Sequenz (Ränge) kombiniert. Dieser Prozess erzeugt alle 13 Karten des ersten Anzugs. Dieser Vorgang wird mit jedem Element in der ersten Sequenz (Farben) wiederholt. Das Ergebnis ist ein Kartenstapel, der erst nach Farben und innerhalb der Farben nach Werten sortiert ist.

Denken Sie daran, dass sie unabhängig davon, ob Sie LINQ in die im vorherigen Beispiel verwendete Abfragesyntax schreiben oder stattdessen Methodensyntax verwenden, immer von einer Syntax zur anderen wechseln können. Die in der Abfragesyntax geschriebene vorhergehende Abfrage kann in der Methodensyntax geschrieben werden:

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

Der Compiler übersetzt LINQ-Anweisungen, die mit abfragesyntax geschrieben wurden, in die entsprechende Methodenaufrufsyntax. Daher erzeugen die beiden Versionen der Abfrage unabhängig von ihrer Syntax dasselbe Ergebnis. Wählen Sie die Syntax aus, die für Ihre Situation am besten geeignet ist. Wenn Sie beispielsweise in einem Team arbeiten, in dem einige Mitglieder Schwierigkeiten mit der Methodensyntax haben, versuchen Sie, die Verwendung der Abfragesyntax zu bevorzugen.

Führen Sie das Beispiel aus, das Sie zu diesem Zeitpunkt erstellt haben. Es zeigt alle 52 Karten im Deck an. Möglicherweise ist es hilfreich, dieses Beispiel unter einem Debugger auszuführen, um zu beobachten, wie die Suits() Und Ranks() Methoden ausgeführt werden. Sie können deutlich erkennen, dass jede Zeichenfolge in jeder Sequenz nur bei Bedarf generiert wird.

Ein Konsolenfenster, in dem die App 52 Karten ausgeschrieben hat.

Ändern der Reihenfolge

Konzentrieren Sie sich als Nächstes darauf, wie Sie die Karten im Deck mischen. Der erste Schritt bei jedem guten Mischen ist das Aufteilen des Kartenstapels in zwei Hälften. Die Take Methoden und Skip Methoden, die Teil der LINQ-APIs sind, stellen dieses Feature bereit. Platzieren Sie sie nach der foreach Schleife:

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

Es gibt jedoch keine Shuffle-Methode in der Standardbibliothek, sodass Sie Ihre eigene schreiben müssen. Die shuffle-Methode, die Sie erstellen, veranschaulicht verschiedene Techniken, die Sie mit LINQ-basierten Programmen verwenden, sodass jeder Teil dieses Prozesses in schritten erläutert wird.

Wenn Sie Funktionen zur Interaktion mit den IEnumerable<T> Ergebnissen von LINQ-Abfragen hinzufügen möchten, schreiben Sie einige spezielle Methodenarten, die als Erweiterungsmethoden bezeichnet werden. Eine Erweiterungsmethode ist eine spezielle statische Methode , die einem bereits vorhandenen Typ neue Funktionen hinzufügt, ohne den ursprünglichen Typ ändern zu müssen, dem Sie Funktionen hinzufügen möchten.

Verleihen Sie Ihren Erweiterungsmethoden ein neues Zuhause, indem Sie ihrem Programm eine neue statische Klassendatei hinzufügen, die Extensions.csgenannt wird, und beginnen Sie dann mit dem Erstellen der ersten Erweiterungsmethode:

public static class CardExtensions
{
    extension<T>(IEnumerable<T> sequence)
    {
        public IEnumerable<T> InterleaveSequenceWith(IEnumerable<T> second)
        {
            // Your implementation goes here
            return default;
        }
    }
}

Hinweis

Wenn Sie einen anderen Editor als Visual Studio (z. B. Visual Studio Code) verwenden, müssen Sie möglicherweise am Anfang der using LinqFaroShuffle; Datei hinzufügen, damit auf die Erweiterungsmethoden zugegriffen werden kann. Visual Studio fügt diese using-Anweisung automatisch hinzu, andere Editoren möglicherweise jedoch nicht.

Der extension Container gibt den Erweiterten Typ an. Der extension Knoten deklariert den Typ und den Namen des Empfängerparameters für alle Member innerhalb des extension Containers. In diesem Beispiel erweitern Sie IEnumerable<T>, und der Parameter heißt sequence.

Erweiterungsmemberdeklarationen werden so angezeigt, als seien sie Mitglieder des Empfängertyps:

public IEnumerable<T> InterleaveSequenceWith(IEnumerable<T> second)

Sie rufen die Methode so auf, als wäre sie eine Membermethode des erweiterten Typs. Diese Methodendeklaration folgt auch einem Standardidiom, bei dem die Eingabe- und Ausgabetypen IEnumerable<T>sind. In dieser Praxis können LINQ-Methoden verkettet werden, um komplexere Abfragen auszuführen.

Da Sie den Stapel in Hälften aufgeteilt haben, müssen Sie diese Hälften wieder zusammenfügen. Im Code bedeutet dies, dass Sie beide Sequenzen auflisten, die Sie durch Take und Skip auf einmal erworben haben, die Elemente interleavieren und eine Sequenz erstellen: ihre jetzt neu gemischten Kartensammlungen. Das Schreiben einer LINQ-Methode, die mit zwei Sequenzen funktioniert, erfordert, dass Sie verstehen, wie IEnumerable<T> funktioniert.

Die IEnumerable<T>-Schnittstelle hat eine Methode: GetEnumerator. Das zurückgegebene GetEnumerator Objekt verfügt über eine Methode, um zum nächsten Element zu wechseln, und eine Eigenschaft, die das aktuelle Element in der Sequenz abruft. Sie verwenden diese beiden Member, um die Auflistung aufzählen und die Elemente zurückzugeben. Diese Interleave-Methode ist eine Iteratormethode. Statt eine Auflistung zu erstellen und die Auflistung zurückzugeben, verwenden Sie die yield return im vorherigen Code gezeigte Syntax.

Dies ist die Implementierung dieser Methode:

public IEnumerable<T> InterleaveSequenceWith(IEnumerable<T> second)
{
    var firstIter = sequence.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 zur Main Methode zurück, und mischen Sie das Deck einmal.

var shuffledDeck = top.InterleaveSequenceWith(bottom);

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

Vergleiche

Bestimmen Sie, wie viele Shuffles es benötigt, um den Deck wieder auf die ursprüngliche Reihenfolge festzulegen. Um herauszufinden, schreiben Sie eine Methode, die bestimmt, ob zwei Sequenzen gleich sind. Nachdem Sie diese Methode haben, platzieren Sie den Code, der den Kartenstapel in einer Schleife mischt, und überprüfen Sie, wann der Kartenstapel wieder in die richtige Reihenfolge zurückkehrt.

Das Schreiben einer Methode, um festzustellen, ob die beiden Sequenzen gleich sind, sollte einfach sein. Die Methode hat die gleiche Struktur wie die Methode, die Sie zum Mischen des Kartenstapels geschrieben haben. Dieses Mal vergleichen Sie jedoch anstelle der Verwendung yield return für jedes Element die übereinstimmenden Elemente jeder Sequenz. Wenn die gesamte Sequenz aufgezählt wird, wenn jedes Element übereinstimmt, sind die Sequenzen identisch:

public bool SequenceEquals(IEnumerable<T> second)
{
    var firstIter = sequence.GetEnumerator();
    var secondIter = second.GetEnumerator();

    while ((firstIter?.MoveNext() == true) && secondIter.MoveNext())
    {
        if ((firstIter.Current is not null) && !firstIter.Current.Equals(secondIter.Current))
        {
            return false;
        }
    }

    return true;
}

Diese Methode zeigt ein zweites LINQ-Idiom: Terminalmethoden. Sie nehmen eine Sequenz als Eingabe (oder in diesem Fall zwei Sequenzen) und geben einen einzelnen skalaren Wert zurück. Wenn Sie Terminalmethoden verwenden, sind sie immer die endgültige Methode in einer Kette von Methoden für eine LINQ-Abfrage.

Sie können dies in der Praxis sehen, wenn Sie es verwenden, um zu bestimmen, wann sich das Kartendeck wieder in der ursprünglichen Reihenfolge befindet. Platzieren Sie den Shuffle-Code in einer Schleife, und beenden Sie den Vorgang, wenn die Sequenz wieder in der ursprünglichen Reihenfolge liegt, indem Sie die SequenceEquals()-Methode anwenden. Sie können sehen, dass es immer die letzte Methode in jeder Abfrage ist, da sie einen einzelnen Wert anstelle einer Sequenz zurückgibt:

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

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

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

var shuffledDeck = top.InterleaveSequenceWith(bottom);

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

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

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

Console.WriteLine(times);

Führen Sie den code aus, den Sie bisher erstellt haben, und beachten Sie, wie sich die Decks auf jedem Shuffle neu anordnen. 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.

Optimierungen

Das beispiel, das Sie bisher erstellt haben, führt eine out shuffle aus, wobei die oberen und unteren Karten bei jeder Ausführung gleich bleiben. Lassen Sie uns eine Änderung vornehmen: Verwenden Sie stattdessen einen in shuffle, bei dem alle 52 Karten ihre Position verändern. Hierbei werden die Hälften so ineinander gefächert, dass die erste Karte der unteren Hälfte zur ersten Karte des Kartenstapels wird. Das bedeutet, dass die letzte Karte in der oberen Hälfte zur unteren Karte wird. Für diese Änderung ist eine Codezeile erforderlich. Aktualisieren Sie die aktuelle Shuffle-Abfrage, indem Sie die Positionen von Take und Skipwechseln. Diese Änderung wechselt die Reihenfolge der oberen und unteren Hälften des Decks:

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

Führen Sie das Programm erneut aus, und Sie sehen, dass es 52 Iterationen dauert, bis sich das Deck neu anordnen kann. Sie bemerken auch eine schwerwiegende Leistungsbeeinträchtigung, während das Programm weiterhin ausgeführt wird.

Es gibt mehrere Gründe für diesen Leistungsabbruch. Sie können eine der Hauptursachen angehen: ineffiziente Verwendung der faulen Auswertung.

Lazy evaluation gibt an, dass die Auswertung einer Anweisung erst ausgeführt wird, wenn ihr Wert erforderlich ist. LINQ-Abfragen sind verzögert ausgewertete Anweisungen. Die Sequenzen werden nur generiert, wenn die Elemente angefordert werden. In der Regel ist das ein großer Vorteil von LINQ. In einem Programm wie diesem führt die faule Auswertung jedoch zu exponentiellem Wachstum in der Ausführungszeit.

Denken Sie daran, dass Sie den ursprünglichen Deck mithilfe einer LINQ-Abfrage generiert haben. Jede Shuffle wird durch Ausführen von drei LINQ-Abfragen auf dem vorherigen Deck generiert. Alle diese Abfragen werden lazily ausgeführt. Das bedeutet auch, dass sie jedes Mal erneut ausgeführt werden, wenn die Sequenz angefordert wird. Wenn Sie zur 52. Iteration gelangen, regenerieren Sie das ursprüngliche Deck mehrmals neu. Schreiben Sie ein Protokoll, um dieses Verhalten zu veranschaulichen. Nachdem Sie Daten gesammelt haben, können Sie die Leistung verbessern.

Geben Sie in Der Extensions.cs Datei die Methode im folgenden Codebeispiel ein, oder kopieren Sie sie. Diese Erweiterungsmethode erstellt eine neue Datei namens debug.log in Ihrem Projektverzeichnis und zeichnet auf, welche Abfrage derzeit in der Protokolldatei ausgeführt wird. Fügen Sie diese Erweiterungsmethode an eine beliebige Abfrage an, um die ausgeführte Abfrage zu markieren.

public IEnumerable<T> LogQuery(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;
}

Instrumentieren Sie als Nächstes die Definition jeder Abfrage mit einer Protokollmeldung:

var startingDeck = (from s in Suits().LogQuery("Suit Generation")
                    from r in Ranks().LogQuery("Rank Generation")
                    select (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 Sie nicht jedes Mal protokollieren, wenn Sie auf eine Abfrage zugreifen. Sie protokollieren nur, wenn Sie die ursprüngliche Abfrage erstellen. Das Programm dauert immer noch lange, aber jetzt können Sie sehen, warum. 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. Sie sehen immer noch die faulen Auswertungseffekte. In einer Ausführung werden 2.592 Abfragen ausgeführt, einschließlich des Werts und der Anzugsgenerierung.

Sie können die Leistung des Codes verbessern, um die Anzahl der ausgeführten Ausführungen zu verringern. Eine einfache Lösung besteht darin, die Ergebnisse der ursprünglichen LINQ-Abfrage zwischenzuspeichern , die den Kartensatz erstellt. Derzeit führen Sie die Abfragen jedes Mal aus, wenn die Do-While-Schleife eine Iteration durchläuft, rekonstruieren das Kartendeck und mischen es jedes Mal neu. Um den Kartensatz zwischenzuspeichern, wenden Sie die LINQ-Methoden ToArray und ToList an. Wenn Sie sie an die Abfragen anfügen, führen sie dieselben Aktionen aus, denen Sie mitgeteilt haben, aber jetzt speichern sie die Ergebnisse in einem Array oder einer Liste, je nachdem, welche Methode Sie aufrufen möchten. Fügen Sie die LINQ-Methode ToArray an beide Abfragen an, und führen Sie das Programm erneut aus:

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. Führen Sie es erneut mit dem Shuffle aus, und es werden ähnliche Verbesserungen erzielt: Es führt jetzt 162 Abfragen aus.

In diesem Beispiel werden die Anwendungsfälle hervorgehoben, in denen eine faule Auswertung zu Leistungsproblemen führen kann. Obwohl es wichtig ist, zu sehen, wo sich die faule Auswertung auf die Codeleistung auswirken kann, ist es ebenso wichtig zu verstehen, dass nicht alle Abfragen eifrig ausgeführt werden sollten. Die Leistungsbeeinträchtigung, die auftritt, ohne ToArray zu verwenden, entsteht dadurch, dass jede neue Anordnung des Kartendecks aus der vorherigen Anordnung erstellt wird. Die Verwendung von Lazy Evaluation bedeutet, dass jede neue Deckkonfiguration aus dem ursprünglichen Deck erstellt wird und sogar den Code ausführt, der die startingDeckgeneriert hat. Dies führt zu einer großen Menge zusätzlicher Arbeit.

In der Praxis laufen einige Algorithmen gut mit eifriger Auswertung, und andere laufen gut mit fauler Auswertung. Für die tägliche Nutzung ist Lazy Evaluation in der Regel die bessere Wahl, wenn die Datenquelle ein separater Prozess ist, z. B. eine Datenbank-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. LINQ ist flexibel, unabhängig davon, ob Sie faule oder begierige Bewertungen verwenden möchten. Messen Sie also Ihre Prozesse, und wählen Sie aus, welche Bewertung Ihnen die beste Leistung bietet.

Schlussfolgerung

In diesem Projekt haben Sie Folgendes behandelt:

  • Verwenden von LINQ-Abfragen zum Aggregieren von Daten in einer sinnvollen Sequenz.
  • Schreiben von Erweiterungsmethoden zum Hinzufügen benutzerdefinierter Funktionen zu LINQ-Abfragen.
  • Ermitteln von Bereichen im Code, in denen LINQ-Abfragen möglicherweise Leistungsprobleme wie beeinträchtigte Geschwindigkeit haben.
  • Faule und begierige Auswertung in LINQ-Abfragen und die Auswirkungen, die sie möglicherweise auf die Abfrageleistung haben.

Abgesehen von LINQ haben Sie über eine Technik gelernt, die Zauberer für Kartentricks verwenden. Zauberer verwenden den Färöer shuffle, da sie steuern können, wo sich jede Karte im Deck bewegt. Jetzt, da Sie es wissen, verraten Sie es nicht allen anderen!

Weitere Informationen zu LINQ finden Sie unter: