Tutorial: Kombinieren von C#-Techniken zum Generieren von Badge-Beschriftungen

Ein Meetup benötigt Namensschilder in Namensreihenfolge. Jeder Teilnehmer fordert eine oder mehrere Kopien an, und jedes Etikett enthält eine Nachricht für die Rolle des Teilnehmers. In diesem Lernprogramm kombinieren Sie kleine C#-Techniken, um die Bezeichnungen ohne eine separate Sammlung von formatierten Zeichenfolgen zu erstellen.

Dieses Lernprogramm richtet sich an Entwickler, die grundlegende C#-Methoden, Schleifen und Auflistungen kennen. Sie wählen aus, wo kurze Vorgänge hingehören, legen Hilfsabhängigkeiten explizit fest und entscheiden, wann das Programm Ergebnisse erzeugt. Die Gründe für diese Auswahl finden Sie unter "Erstellen von Software".

Prerequisites

Mit einer Beschriftung pro Teilnehmer beginnen

  1. Erstellen Sie einen Ordner namens BadgeLabels und darin eine Datei namens Program.cs.

  2. Kopieren Sie den vollständigen Quelltext im folgenden Abschnitt in Program.cs. Verwenden Sie dieselbe Datei für jeden Schritt und jedes Experiment:

    #define BADGE_SORT
    #define BADGE_COPIES
    #define BADGE_FORMAT
    #define BADGE_COUNT
    // #define BADGE_DIAGNOSTICS
    // #define BADGE_REPEAT
    // #define BADGE_SNAPSHOT
    
    Attendee[] attendees =
    [
        new("Ben", "Host", 1),
        new("Ada", "Guest", 2)
    ];
    
    #if BADGE_SORT
    #if BADGE_DIAGNOSTICS
    Console.WriteLine("Before query definition.");
    #endif
    var orderedAttendees = attendees.OrderBy(
    #if BADGE_DIAGNOSTICS
        attendee =>
        {
            Console.WriteLine($"Sort key: {attendee.Name}");
            return attendee.Name;
        }, StringComparer.Ordinal);
    #else
        attendee => attendee.Name, StringComparer.Ordinal);
    #endif
    #if BADGE_DIAGNOSTICS
    Console.WriteLine("After query definition.");
    #endif
    #else
    var orderedAttendees = attendees;
    #endif
    
    #if BADGE_SNAPSHOT
    var attendeesToPrint = orderedAttendees.ToList();
    #else
    var attendeesToPrint = orderedAttendees;
    #endif
    
    int passes = 1;
    #if BADGE_REPEAT
    passes = 2;
    #endif
    
    for (int pass = 0; pass < passes; pass++)
    {
    #if BADGE_COUNT
        int expected = attendees.Sum(attendee => attendee.Copies);
        int printed = 0;
    #endif
    
        foreach (Attendee attendee in attendeesToPrint)
        {
    #if BADGE_COPIES
            var labels = CreateLabels(attendee);
    #if BADGE_DIAGNOSTICS
            Console.WriteLine($"Sequence created: {attendee.Name}");
    #endif
            foreach (string label in labels)
            {
                Console.WriteLine(label);
    #if BADGE_COUNT
                printed++;
    #endif
            }
    #else
            Console.WriteLine($"{attendee.Name} | {attendee.Role}");
    #endif
        }
    
    #if BADGE_COUNT
        if (printed != expected)
        {
            throw new InvalidOperationException("Label count doesn't match the request.");
        }
    
        Console.WriteLine($"Printed {printed} of {expected} requested labels.");
    #endif
    }
    
    #if BADGE_COPIES
    static IEnumerable<string> CreateLabels(Attendee attendee)
    {
    #if BADGE_DIAGNOSTICS
        Console.WriteLine($"Iterator started: {attendee.Name}");
    #endif
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(attendee.Copies);
    
    #if BADGE_FORMAT
        string message = attendee.Role switch
        {
            "Guest" => "Welcome!",
            "Host" => "Ask me for help",
            _ => throw new ArgumentException(
                $"Unsupported role: {attendee.Role}.", nameof(attendee))
        };
    #endif
    
        for (int copy = 1; copy <= attendee.Copies; copy++)
        {
    #if BADGE_FORMAT
            yield return FormatLabel(
                attendee.Name, attendee.Role, message, copy, attendee.Copies);
    #else
            yield return $"{attendee.Name} | {attendee.Role} [{copy}/{attendee.Copies}]";
    #endif
        }
    
    #if BADGE_FORMAT
        static string FormatLabel(
            string name, string role, string message, int copy, int total) =>
            $"{name} | {role} | {message} [{copy}/{total}]";
    #endif
    }
    #endif
    
    record Attendee(string Name, string Role, int Copies);
    
  3. Kommentieren Sie oben in der Datei die vier aktiven #define Zeilen aus. Lassen Sie auch die drei Experimentzeilen auskommentiert.

  4. Führen Sie in diesem Ordner die dateibasierte App aus:

    dotnet run --file Program.cs
    

Das Programm schreibt:

Ben | Host
Ada | Guest

Die Datei enthält jede Phase. Ändern Sie ab jetzt nur die #define Zeilen oben, nicht Methodenkörper oder Deklarationen. Eine #if-Direktive enthält Code nur, wenn sein Symbol definiert ist; eine #else-Verzweigung liefert die Alternative. Siehe Bedingte Kompilierung.

Entfernen Sie in jedem Schritt bei einer weiteren Stage-Zeile die Kommentarzeichen, und lassen Sie die vorherigen Stage-Zeilen aktiviert:

Stage Aktive Stufensymbole
Eine Bezeichnung pro Teilnehmer Kein
Teilnehmer sortieren BADGE_SORT
Kopien erstellen BADGE_SORT, BADGE_COPIES
Nachrichten formatieren BADGE_SORT, BADGE_COPIES, BADGE_FORMAT
Anzahl überprüfen BADGE_SORT, BADGE_COPIES, BADGE_FORMAT, BADGE_COUNT

Der heruntergeladene Quellcode aktiviert alle vier Stufensymbole, sodass das endgültige Programm standardmäßig ausgeführt wird. Die anderen drei Symbole steuern optionale Experimente und bleiben während des normalen Ablaufs aus. Diese Symbole verwenden ein BADGE_ Präfix, um SDK-definierte Symbole wie TRACE zu vermeiden.

In den folgenden Auszügen werden Code hervorgehoben, der bereits in Ihrer Datei enthalten ist. Kopieren Sie sie nicht über das gesamte Programm.

Attendee[] attendees =
[
    new("Ben", "Host", 1),
    new("Ada", "Guest", 2)
];

Die Attendee Deklaration definiert einen Datensatz, einen Typ, der verwandte Daten gruppiert und wertbasierte Gleichheit bereitstellt. Jeder Teilnehmer verfügt über eine Name, eine Roleund eine angeforderte Anzahl von Copies. Ein Datensatz passt zu diesem kleinen Datenmodell, da das Programm Teilnehmerdaten zwischen Vorgängen übergibt, ohne sie zu ändern. Weitere Informationen zu diesem Typ finden Sie unter "Datensätze".

Die feste Eingabe verwendet ein Array und einen Sammlungsausdruck. In jedem new(...)-Aufruf leitet C# Attendee vom Arrayelementtyp ab; siehe zieltypisiertes new. Mit Anweisungen der obersten Ebene können Sie das Beispiel ausführen, ohne eine Main-Methode zu schreiben.

Die foreach-Schleife liest jeden Teilnehmer, und eine interpolierte Zeichenfolge fügt ihre Werte in die Ausgabe ein. Die Schleife ignoriert derzeit Copies.

Halten Sie diese kleine Eingabe bei der Entwicklung des Programms: Ben fordert eine Kopie an, und Ada fordert zwei an. Nur Guest und Host werden als Rollen unterstützt, und die Anzahl der Kopien muss positiv sein.

Sortieren mit einer Lambda-Funktion

Wenn Sie Teilnehmer in die Namensreihenfolge bringen möchten, kommentieren Sie #define BADGE_SORT aus. Lassen Sie die anderen Symbole auskommentiert. Der Sortierzweig definiert diese Abfrage:

var orderedAttendees = attendees.OrderBy(
#if BADGE_DIAGNOSTICS
    attendee =>
    {
        Console.WriteLine($"Sort key: {attendee.Name}");
        return attendee.Name;
    }, StringComparer.Ordinal);
#else
    attendee => attendee.Name, StringComparer.Ordinal);
#endif

Wenn die Diagnose deaktiviert ist, liefert die #else-Verzweigung das kurze Lambda. Die Schleife gibt die sortierten Teilnehmer aus:

    foreach (Attendee attendee in attendeesToPrint)
    {
#if BADGE_COPIES
        var labels = CreateLabels(attendee);
#if BADGE_DIAGNOSTICS
        Console.WriteLine($"Sequence created: {attendee.Name}");
#endif
        foreach (string label in labels)
        {
            Console.WriteLine(label);
#if BADGE_COUNT
            printed++;
#endif
        }
#else
        Console.WriteLine($"{attendee.Name} | {attendee.Role}");
#endif
    }

Führen Sie denselben Befehl aus. Die Ausgabe platziert jetzt Ada zuerst:

Ada | Guest
Ben | Host

attendee => attendee.Name ist ein Lambda-Ausdruck, eine unbenannte Funktion, die dort verwendet wird, wo Code Verhalten benötigt. Hier wählt die Lambda-Funktion den Namen aus, nach dem sortiert werden soll. Die Lambda-Funktion bleibt OrderBy, direkt im Aufruf, da sie kurz ist und der Zweck klar ist. Sie könnten die Auswahl in eine benannte Hilfsmethode extrahieren, aber ein Methodenname würde diese Auswahl mit einer Eigenschaft nicht besser erklären. Siehe Lambda-Ausdrücke.

OrderByist Teil von LANGUAGE Integrated Query (LINQ), einer Reihe von C#-Features und .NET-APIs für Abfragen über Daten. Siehe LINQ-Abfragen.

In diesem Beispiel wird die Methodensyntax verwendet, die LINQ-Methoden direkt aufruft. Im Gegensatz dazu verwendet die Abfragesyntax C#-Klauseln wie from, orderby und select:

var orderedAttendees =
    from attendee in attendees
    orderby attendee.Name
    select attendee;

Der Compiler übersetzt die Abfragesyntax in Methodenaufrufe. Die Abfragesyntax kann eine Abfrage mit mehreren Klauseln einfacher zu lesen machen; die Methodensyntax hält diese einzelne Sortierung kurz und ermöglicht es Ihnen, einen Comparer an OrderBy zu übergeben. Im Abfragesyntaxbeispiel wird der Standard-Zeichenfolgenvergleich verwendet, sodass nicht dieselbe Reihenfolge in allen Kulturen garantiert wird. Behalten Sie die Methodensyntaxversion für dieses Programm bei. Siehe LINQ-Methode und Abfragesyntax.

Um die Ausgabe unabhängig vom Gebietsschema der Maschine zu halten, wird im Beispiel Ordinal für Namensvergleiche verwendet. Für die hier aufgeführten Kurznamen ist diese Reihenfolge alphabetisch. Siehe Sortieren von Daten mit LINQ.

Die var-Deklaration ermöglicht es dem Compiler, den Typ der Abfragevariablen abzuleiten. Die orderedAttendees Variable beschreibt eine Abfrage, keine abgeschlossene sortierte Auflistung. Eine Sequenz zu enumerieren bedeutet, ihre Elemente einzeln zu lesen. OrderBy liest alle Teilnehmer ein und sortiert sie, wenn foreach zuerst ein Element anfordert.

Beachten Sie die Reihenfolge der Ausführung

Verzögerte Ausführung bedeutet, dass die Verarbeitung beginnt, wenn Code eine Sequenz durchläuft, und nicht, wenn Code die Abfrage definiert. Siehe Ausführen einer Abfrage. Verwenden Sie die Diagnoseausgabe, um den Unterschied anzuzeigen:

  1. Lassen Sie nur BADGE_SORT unter den Phasensymbolen aktiviert.

  2. Entkommentieren Sie #define BADGE_DIAGNOSTICS oben in derselben Datei. Die Diagnosezweige umgeben die Abfragedefinition mit Nachrichten und verwenden eine Lambda-Funktion, die jeden Sortierschlüssel druckt:

    #if BADGE_SORT
    #if BADGE_DIAGNOSTICS
    Console.WriteLine("Before query definition.");
    #endif
    var orderedAttendees = attendees.OrderBy(
    #if BADGE_DIAGNOSTICS
        attendee =>
        {
            Console.WriteLine($"Sort key: {attendee.Name}");
            return attendee.Name;
        }, StringComparer.Ordinal);
    #else
        attendee => attendee.Name, StringComparer.Ordinal);
    #endif
    #if BADGE_DIAGNOSTICS
    Console.WriteLine("After query definition.");
    #endif
    #else
    var orderedAttendees = attendees;
    #endif
    
  3. Führen Sie dotnet run --file Program.cs erneut aus. Bevor Sie die Ausgabe lesen, sagen Sie voraus, ob Sort key: vor oder nach After query definition. der Ausgabe angezeigt wird.

Die Ausgabe lautet:

Before query definition.
After query definition.
Sort key: Ben
Sort key: Ada
Ada | Guest
Ben | Host

Zwischen den beiden Abfragedefinitionsmeldungen werden keine Sortierschlüssel angezeigt. Die Lambda-Funktion wird nur ausgeführt, wenn foreach das erste Ergebnis anfordert. Beide Schlüsselnachrichten werden vor der ersten Beschriftung angezeigt, da OrderBy jeden Schlüssel benötigt, bevor es bestimmen kann, welcher Teilnehmer zuerst kommt. Die Schlüssel folgen der Eingabereihenfolge, Ben dann Ada; die Ergebnisse folgen der sortierten Reihenfolge, Ada dann Ben.

Das Diagnose-Lambda benötigt sowohl eine Nachricht als auch einen zurückgegebenen Schlüssel, daher wird ein Blockanweisungstextkörper mit geschweiften Klammern und return anstelle des ursprünglichen Textkörpers mit nur einem Ausdruck verwendet.

Testen Sie bei aktivierter Diagnose die beiden Experimentschaltern in derselben Datei. Es sind keine Änderungen an den Schleifen oder Abfragen erforderlich:

  1. #define BADGE_REPEAT Auskommentierung aufheben und erneut ausführen:

    Before query definition.
    After query definition.
    Sort key: Ben
    Sort key: Ada
    Ada | Guest
    Ben | Host
    Sort key: Ben
    Sort key: Ada
    Ada | Guest
    Ben | Host
    

    Der zweite Durchlauf wiederholt die Sortierung. Die Abfragevariable speichert keine Ergebnisse im Cache.

  2. Lassen Sie BADGE_REPEAT aktiviert, entfernen Sie die Kommentarzeichen bei #define BADGE_SNAPSHOT und führen Sie den Vorgang erneut aus:

    Before query definition.
    After query definition.
    Sort key: Ben
    Sort key: Ada
    Ada | Guest
    Ben | Host
    Ada | Guest
    Ben | Host
    

    Die beiden Hauptmeldungen werden jetzt nur einmal angezeigt (während ToList), bevor einer der beiden Durchläufe Beschriftungen druckt. Beide Durchläufe lesen die gespeicherten Teilnehmer.

Diese Schalter wählen die zu durchlaufende Sequenz und die Anzahl der Durchläufe aus:

#if BADGE_SNAPSHOT
var attendeesToPrint = orderedAttendees.ToList();
#else
var attendeesToPrint = orderedAttendees;
#endif

int passes = 1;
#if BADGE_REPEAT
passes = 2;
#endif

Strikte Ausführung bedeutet, dass ein Vorgang sofort ausgeführt wird, anstatt auf eine spätere Enumeration zu warten. ToList führt die Abfrage sofort aus und speichert die Ergebnisse in einer Liste. Beide Durchläufe lesen nun die gespeicherten Teilnehmer, ohne die Sortierung erneut auszuführen. Im Gegensatz dazu wird die Sortierung beim Definieren der OrderBy-Abfrage weiterhin verschoben: Sie liest und puffert alle Eingaben nur, wenn die Enumeration beginnt. Die Pufferung bedeutet nicht, dass die Abfrage beim Definieren ausgeführt wird. Verwenden Sie eine Momentaufnahme, wenn Sie Ergebnisse wiederverwenden müssen, ohne sie neu zu berechnen; behalten Sie die verzögerte Abfrage bei, wenn jede Enumeration die aktuelle Eingabe lesen soll. Siehe Verfassen von Abfragen mit zwischengespeicherten Ergebnissen.

Bevor Sie fortfahren, kommentieren Sie BADGE_DIAGNOSTICS, BADGE_REPEAT und BADGE_SNAPSHOT wieder aus. BADGE_SORT aktiviert lassen. Führen Sie die App aus, um zu bestätigen, dass nur die beiden sortierten Bezeichnungen angezeigt werden.

Angeforderte Kopien mit einem Iterator erstellen

Um jeden Teilnehmer auf die angeforderte Anzahl von Beschriftungen zu erweitern, kommentieren Sie #define BADGE_COPIES zusammen mit BADGE_SORT aus. Die Schleife wählt nun den zuvor gezeigten BADGE_COPIES-Branch aus und ruft den bereits in der Datei vorhandenen Iterator auf:

#if BADGE_COPIES
static IEnumerable<string> CreateLabels(Attendee attendee)
{
#if BADGE_DIAGNOSTICS
    Console.WriteLine($"Iterator started: {attendee.Name}");
#endif
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(attendee.Copies);

#if BADGE_FORMAT
    string message = attendee.Role switch
    {
        "Guest" => "Welcome!",
        "Host" => "Ask me for help",
        _ => throw new ArgumentException(
            $"Unsupported role: {attendee.Role}.", nameof(attendee))
    };
#endif

    for (int copy = 1; copy <= attendee.Copies; copy++)
    {
#if BADGE_FORMAT
        yield return FormatLabel(
            attendee.Name, attendee.Role, message, copy, attendee.Copies);
#else
        yield return $"{attendee.Name} | {attendee.Role} [{copy}/{attendee.Copies}]";
#endif
    }

#if BADGE_FORMAT
    static string FormatLabel(
        string name, string role, string message, int copy, int total) =>
        $"{name} | {role} | {message} [{copy}/{total}]";
#endif
}
#endif

Führen Sie die App erneut aus:

Ada | Guest [1/2]
Ada | Guest [2/2]
Ben | Host [1/1]

CreateLabels ist ein Iterator, eine Funktion, die Sequenzelemente einzeln erzeugt. Der IEnumerable<T> Rückgabetyp, geschrieben als IEnumerable<string>, stellt eine Abfolge von Zeichenfolgen dar. Jede yield return liefert eine Beschriftung an die innere foreach Schleife, die sie druckt, bevor sie das nächste Etikett anfordert. Siehe Iteratoren.

Die for Schleife zählt Kopien von 1 bis attendee.Copies. Ein Teilnehmer erzeugt so viele Zeichenfolgen, wie von Copies angefordert. Eine Sammlung würde auch funktionieren, aber der Aufrufer benötigt nur einmal jede Bezeichnung. Der Iterator vermeidet eine separate Label-Sammlung und hält die Kopiernummerschleife zusammen. Die äußeren und inneren foreach-Schleifen trennen die Teilnehmerreihenfolge von der Beschriftungsproduktion. Der Aufrufer bleibt für den Druck verantwortlich.

Das Aufrufen von CreateLabels(attendee) führt dessen Textkörper nicht aus. Wie bei der LINQ-Abfrage verwendet der Iterator die verzögerte Ausführung. Die innere foreach startet diese Aufgabe, einschließlich der Überprüfung positiver Kopien. Eine neue Enumeration durchläuft den Iterator erneut. Im Gegensatz zu OrderBy, das alle Teilnehmer vor dem ersten Ergebnis liest und sortiert, liefert dieser Iterator eine Bezeichnung, ohne die verbleibenden Bezeichnungen zu erstellen.

Um die Reihenfolge der Ausführung des Iterators zu beobachten, dekommentieren Sie #define BADGE_DIAGNOSTICS erneut. Lassen Sie BADGE_SNAPSHOT und BADGE_REPEAT deaktiviert. Die Diagnose zeigt jetzt sowohl die Sortierung als auch den Iterator an:

Before query definition.
After query definition.
Sort key: Ben
Sort key: Ada
Sequence created: Ada
Iterator started: Ada
Ada | Guest [1/2]
Ada | Guest [2/2]
Sequence created: Ben
Iterator started: Ben
Ben | Host [1/1]

Für jeden Teilnehmer wird die von der Sequenz erstellte Nachricht vor der vom Iterator gestarteten Nachricht und den Bezeichnungen angezeigt. Das Erstellen der Sequenz wechselt nicht in den Iteratorrumpf. Das Anfordern des ersten Elements tut dies. Kommentieren Sie es BADGE_DIAGNOSTICS erneut aus, bevor Sie fortfahren.

Die gleichen Steuerelemente für Experimente bleiben in den späteren Phasen verfügbar. BADGE_REPEAT listet die Teilnehmer zweimal auf und erstellt die Beschriftungssequenz der einzelnen Teilnehmer erneut. BADGE_SNAPSHOT speichert nur die sortierten Teilnehmer zwischen, keine Label-Strings, sodass eine weitere Sortierung erspart wird, aber nicht verhindert wird, dass der Iterator bei jedem Durchlauf erneut ausgeführt wird.

Die ThrowIfNegativeOrZero Überprüfung meldet ungültige Eingaben, anstatt stillschweigend keine Bezeichnungen zu erzeugen. Eine Ausnahme meldet einen Fehler, der die normale Ausführung unterbricht; siehe Erstellen und Auslösen von Ausnahmen. Hier stoppt eine ungültige Kopieanzahl das Programm, anstatt den Teilnehmer stillschweigend zu überspringen.

Eine Nachricht berechnen und den Formatierungsschritt benennen

Jede Rolle benötigt eine andere Nachricht. Kommentieren Sie #define BADGE_FORMAT aus, und lassen Sie BADGE_SORT und BADGE_COPIES aktiviert. Derselbe Iterator enthält jetzt die Berechnung der Nachricht:

#if BADGE_FORMAT
    string message = attendee.Role switch
    {
        "Guest" => "Welcome!",
        "Host" => "Ask me for help",
        _ => throw new ArgumentException(
            $"Unsupported role: {attendee.Role}.", nameof(attendee))
    };
#endif

Außerdem wählt er die yield return FormatLabel(...)-Verzweigung aus und enthält diesen lokalen Helper:

#if BADGE_FORMAT
    static string FormatLabel(
        string name, string role, string message, int copy, int total) =>
        $"{name} | {role} | {message} [{copy}/{total}]";
#endif

Führen Sie die App erneut aus:

Ada | Guest | Welcome! [1/2]
Ada | Guest | Welcome! [2/2]
Ben | Host | Ask me for help [1/1]

Der switch Ausdruck berechnet die Nachricht einmal pro Teilnehmer vor der Kopierschleife. Ein Ausdruck passt, da jede Rolle einen Wert auswählt, um ihn message zuzuweisen. Eine if/else Kette oder eine switch Anweisung würde funktionieren, aber die Zuordnung in jeder Verzweigung wiederholen.

Musterabgleich testet einen Ausdruck anhand eines angegebenen Musters. Hier ist attendee.Role der Eingabeausdruck, und die konstanten Muster"Guest" und "Host" testen dessen ausgewerteten Wert, um die entsprechende Nachricht auszuwählen. Das Discardmuster _ stimmt mit jeder anderen Rolle überein und löst eine Ausnahme aus, sodass nicht unterstützte Rollen keine irreführende Standardmeldung erhalten. Siehe Mustervergleich.

Der nameof Operator stellt den Parameternamen attendee für die Ausnahme bereit. Im Gegensatz zu einer hartcodierten Zeichenfolge bleibt dieser Name korrekt, wenn Sie den Parameter mit einem Refactoring-Tool umbenennen.

FormatLabel ist eine lokale Funktion, eine benannte Funktion, die in einer anderen Funktion deklariert wird. Da nur CreateLabels den Formatierungsvorgang benötigt, beschränkt seine Platzierung innerhalb von CreateLabels den Helper auf den Vorgang, der ihn verwendet. Eine private Membermethode für eine Klasse würde die Hilfsmethode anderen Mitgliedern dieser Klasse zur Verfügung stellen. Wählen Sie diesen breiteren Umfang aus, wenn mehrere Vorgänge denselben Formatierungsvorgang benötigen. Siehe Lokale Funktionen.

Mit dem Namen FormatLabel können Sie der Kopierschleife folgen, ohne die Formatierungsdetails zu lesen. Die interpolierte Zeichenfolge direkt in yield return zu belassen, würde auch funktionieren, wie im vorherigen Schritt. Der Name wird jetzt nützlicher, da eine Bezeichnung eine rollenspezifische Nachricht enthält. Der =>-Ausdruckstextkörper des Helpers gibt die formatierte Zeichenfolge zurück.

Alle Formatierungseingaben werden über Parameter übergeben. Eine nicht statische lokale Funktion kann Variablen aus dem umgebenden Bereich erfassen oder verwenden. Der static Modifier verhindert diese Erfassung, sodass seine Abhängigkeiten explizit bleiben. Siehe Parameter und erfasster Status.

Da CreateLabels es sich immer noch um einen Iterator handelt, wird die Rollenüberprüfung auch nur ausgeführt, wenn die Enumeration gestartet wird. Für diese gesteuerte Eingabe gehören diese Prüfungen zur Beschriftungsproduktion. Das Programm verspricht nicht, alle Teilnehmer zu überprüfen, bevor es Etiketten druckt.

Überprüfen der Gesamtsumme und Ausführen des vollständigen Programms

Um die tatsächliche Beschriftungsproduktion mit der angeforderten Anzahl zu vergleichen, kommentieren Sie #define BADGE_COUNT aus. Alle vier Stufensymbole sind jetzt aktiviert. Lassen Sie die drei Experimentsymbole für die normale endgültige Ausgabe auskommentiert.

Die Zählinitialisierung wird vor der Teilnehmerschleife ausgeführt:

#if BADGE_COUNT
    int expected = attendees.Sum(attendee => attendee.Copies);
    int printed = 0;
#endif

Sum verwendet die strikte Ausführung, um die Anzahl der Kopien aus dem Eingabearray hinzuzufügen. Im Gegensatz zu OrderBy wird hier eine Zahl zurückgegeben, keine Sequenz, die später durchlaufen werden soll. Die Lambda-Funktion attendee => attendee.Copieswählt die Anzahl der Kopien inline aus. Wie bei der Namensauswahl würde eine separate Hilfsmethode diesen kurzen Ausdruck nicht verdeutlichen. Eine Schleife mit einem Akkumulator würde ebenfalls funktionieren; Sum gibt direkt an, dass Sie eine Summe benötigen. Siehe allgemeine LINQ-Methoden.

Die printed++-Inkrementierung in der Beschriftungsschleife erhöht den Zähler nur, wenn die Schleife eine Beschriftung empfängt. Nach dieser Schleife vergleicht die if Anweisung diese Anzahl mit der angeforderten Summe und löst eine Ausnahme aus, wenn diese unterschiedlich sind:

#if BADGE_COUNT
    if (printed != expected)
    {
        throw new InvalidOperationException("Label count doesn't match the request.");
    }

    Console.WriteLine($"Printed {printed} of {expected} requested labels.");
#endif

Sie haben die Standardkonfiguration des vollständigen Programms erreicht, das Sie am Anfang kopiert haben. Es müssen keine Methoden oder Deklarationen geändert werden:

#define BADGE_SORT
#define BADGE_COPIES
#define BADGE_FORMAT
#define BADGE_COUNT
// #define BADGE_DIAGNOSTICS
// #define BADGE_REPEAT
// #define BADGE_SNAPSHOT

Führen Sie es mit dotnet run --file Program.cs aus. Die Ausgabe lautet:

Ada | Guest | Welcome! [1/2]
Ada | Guest | Welcome! [2/2]
Ben | Host | Ask me for help [1/1]
Printed 3 of 3 requested labels.

Das Eingabearray befindet sich bereits im Arbeitsspeicher. Sum liest sie sofort, OrderBy sortiert sie, wenn die Teilnehmerschleife beginnt, und CreateLabels erzeugt Zeichenfolgen, während die innere Schleife voranschreitet. Das Programm erstellt niemals eine separate Sammlung von Bezeichnungen. Eine umfassendere Abfrageanleitung finden Sie unter LINQ und Auflistungen.

Um die Diagnose mit dem endgültigen Programm erneut zu überprüfen, aktivieren Sie BADGE_DIAGNOSTICS allein unter den Experimentsymbolen. Die Ausgabe lautet:

Before query definition.
After query definition.
Sort key: Ben
Sort key: Ada
Sequence created: Ada
Iterator started: Ada
Ada | Guest | Welcome! [1/2]
Ada | Guest | Welcome! [2/2]
Sequence created: Ben
Iterator started: Ben
Ben | Host | Ask me for help [1/1]
Printed 3 of 3 requested labels.

Wenn Sie BADGE_REPEAT ebenfalls aktivieren, prüft jeder Durchlauf seine eigene Anzahl und meldet sie. Wenn BADGE_SNAPSHOT aktiviert ist, werden die Sortierschlüssel nur einmal angezeigt, aber die Iteratormeldungen, Beschriftungen und Zählungsberichte werden weiterhin in jedem Durchlauf angezeigt. Auskommentieren Sie die Experimentsymbole, um die normale endgültige Ausgabe wiederherzustellen.

Summe

Jede Technik dient einem anderen Teil derselben Aufgabe:

  • Lambdas und LINQ wählen Namen und Kopierzahlen inline für die Sortierung und Summe aus.
  • Ein Iterator beschreibt eine benutzerdefinierte 1:n-Produktion und überlässt die Beschriftungsverarbeitung dem Aufrufer.
  • Ein switch Ausdruck berechnet einen rollenspezifischen Wert und macht nicht unterstützte Rollen explizit.
  • Eine lokale Funktion benennt den Formatierungsschritt, begrenzt seinen Bereich und gibt seine Eingaben an.

Wählen Sie diese Formulare aus, wenn ihr Bereich, ihre Abhängigkeiten und die Reihenfolge der Ausführung dem Vorgang entsprechen. Eine direkte Schleife, eine gemeinsame Methode oder eine abgeschlossene Sammlung ist ggf. besser für andere Aufrufer geeignet.