Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
En meetup behöver namnskyltsetiketter i namnordning. Varje deltagare begär en eller flera kopior och varje etikett innehåller ett meddelande för deltagarens roll. I den här självstudien kombinerar du små C#-tekniker för att skapa etiketterna utan en separat samling formaterade strängar.
Den här självstudien är avsedd för utvecklare som känner till grundläggande C#-metoder, loopar och samlingar. Du väljer var korta åtgärder hör hemma, gör hjälpberoenden explicita och bestämmer när programmet genererar resultat. Bakgrunden till de här valen finns i Skapa programvara.
Prerequisites
- .NET 10 SDK.
- En textredigerare och en terminal.
Börja med en etikett per deltagare
Skapa en mapp med namnet
BadgeLabelsoch en fil med namnetProgram.csinuti den.Kopiera hela källtexten i följande avsnitt till
Program.cs. Använd samma fil för varje steg och 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);Överst i filen kommenterar du ut de fyra aktiva
#defineraderna. Lämna även de tre experimentraderna utkommenterade.Kör den filbaserade appen från den mappen:
dotnet run --file Program.cs
Programmet skriver:
Ben | Host
Ada | Guest
Filen innehåller alla faser. Från och med nu, ändra bara raderna högst upp #define, inte metodkroppar eller deklarationer. Ett #if direktiv innehåller endast kod när dess symbol är definierad. En #else gren tillhandahåller alternativet. Se Villkorsstyrd kompilering.
Avkommentera ytterligare en fasrad i varje steg och behåll de föregående fasraderna aktiverade:
| Scenen | Aktiva stegsymboler |
|---|---|
| En etikett per deltagare | None |
| Sortera deltagare | BADGE_SORT |
| Framställ kopior |
BADGE_SORT, BADGE_COPIES |
| Formatera meddelanden |
BADGE_SORT, BADGE_COPIES, BADGE_FORMAT |
| Kontrollera antalet |
BADGE_SORT, BADGE_COPIES, BADGE_FORMAT, BADGE_COUNT |
Den nedladdade källkoden aktiverar alla fyra stegsymbolerna, så den kör det slutliga programmet som standard. De andra tre symbolerna styr valfria experiment och förblir avstängda under normal progression. Dessa symboler använder ett BADGE_ prefix för att undvika SDK-definierade symboler, till exempel TRACE.
Följande utdrag lyfter fram kod som redan finns i filen. Kopiera dem inte över till hela programmet.
Attendee[] attendees =
[
new("Ben", "Host", 1),
new("Ada", "Guest", 2)
];
Deklarationen Attendee definierar en record, en typ som grupperar relaterade data och tillhandahåller värdebaserad likhet. Varje deltagare har ett Name, ett Role och det begärda antalet Copies. En post passar den här lilla datamodellen eftersom programmet skickar deltagardata mellan operationer utan att ändra den. Mer information om den här typen finns i Records.
De fixerade indata använder en array och ett samlingsuttryck. I varje new(...) anrop härleder C# Attendee från arrayelementtypen. Se Måltypsbestämd new.
Med satser på toppnivå kan du köra exemplet utan att skriva en Main-metod.
Loopen foreachloop läser varje deltagare och en interpolerad sträng infogar deras värden i utdata. Slingan ignorerar Copies för närvarande.
Behåll denna lilla indata när du utvecklar programmet: Ben begär en kopia och Ada begär två. Endast Guest och Host är de enda roller som stöds, och antalet kopior måste vara positivt.
Sortera med en lambda
Om du vill placera deltagarna i namnordning avkommenterar du #define BADGE_SORT. Lämna de andra symbolerna kommenterade. Sorteringsgrenen definierar den här frågan:
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
Med diagnostik avstängd levererar grenen #else den korta lambdan. Slingan skriver ut de deltagare som sorterats:
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
}
Kör samma kommando. Utdata placerar nu Ada först:
Ada | Guest
Ben | Host
attendee => attendee.Name är ett lambda-uttryck, en namnlös funktion som tillhandahålls där koden behöver beteende. Här väljer lambda det namn som ska sorteras efter. Lambda förblir inline, direkt i anropet OrderBy, eftersom den är kort och dess syfte är tydligt där. Du kan extrahera urvalet till en namngiven hjälpmetod, men ett metodnamn skulle inte förklara det här valet med en egenskap bättre. Se Lambda-uttryck.
OrderByär en del av LINQ (Language Integrated Query), en uppsättning C#-funktioner och .NET API:er för frågor om data. Se LINQ-frågor.
I det här exemplet används metodsyntax, som anropar LINQ-metoder direkt. Däremot använder frågesyntaxen C#-satser som from, orderbyoch select:
var orderedAttendees =
from attendee in attendees
orderby attendee.Name
select attendee;
Kompilatorn översätter frågesyntaxen till metodanrop. Frågesyntax kan göra en fråga med flera satser enklare att läsa. metodsyntaxen håller den här enkla sorteringen kort och låter dig skicka en jämförelse till OrderBy. I exemplet med frågesyntax används standardjämförare för strängar, så den garanterar inte samma ordning mellan olika kulturer. Behåll versionen med metodsyntax för det här programmet. Se LINQ-metoden och frågesyntaxen.
För att hålla utdata oberoende av datorns kultur använder exemplet Ordinal för namnjämförelser. För de korta namnen här är den ordningen alfabetisk. Se Sortera data med LINQ.
var deklarationen låter kompilatorn härleda frågevariabelns typ. Variabeln orderedAttendees beskriver en query, inte en slutförd sorterad samling. Att räkna upp en sekvens innebär att läsa dess element en i taget.
OrderBy läser och sorterar alla deltagare när foreach först begär ett element.
Observera körningsordningen
Fördröjd exekvering innebär att arbetet startar när koden itererar över en sekvens i stället för när koden definierar frågan. Se Köra en sökning. Använd diagnostikutdata för att se skillnaden:
Behåll endast
BADGE_SORTaktiverat bland stegsymbolerna.Avkommentera
#define BADGE_DIAGNOSTICSöverst i samma fil. Diagnostikgrenarna omger frågedefinitionen med meddelanden och använder en lambda som skriver ut varje sorteringsnyckel:#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; #endifKör
dotnet run --file Program.csigen. Innan du läser utdata kan du förutsäga omSort key:visas före eller efterAfter query definition..
Resultatet är:
Before query definition.
After query definition.
Sort key: Ben
Sort key: Ada
Ada | Guest
Ben | Host
Inga sorteringsnycklar visas mellan de två frågedefinitionsmeddelandena. Lambda körs endast när foreach begär det första resultatet. Båda nyckelmeddelandena visas före den första etiketten eftersom OrderBy behöver varje nyckel innan den kan avgöra vilken deltagare som kommer först. Nycklarna följer indataordningen, Ben och sedan Ada. resultaten följer den sorterade ordningen, Ada och sedan Ben.
Den diagnostiska lambdan behöver både ett meddelande och en returnerad nyckel, så den använder en blocksatskropp med klammerparenteser och return, i stället för den ursprungliga kroppen med ett enda uttryck.
Med diagnostik aktiverad, prova de två experimentväxlarna i samma fil. Inga ändringar i looparna eller frågan behövs:
Avkommentera
#define BADGE_REPEAToch kör igen: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 | HostDet andra passet upprepar sorteringen. Frågevariabeln cachar inte resultat.
Behåll
BADGE_REPEATaktiverat, avkommentera#define BADGE_SNAPSHOToch kör igen:Before query definition. After query definition. Sort key: Ben Sort key: Ada Ada | Guest Ben | Host Ada | Guest Ben | HostDe två nyckelmeddelandena visas nu bara en gång, under
ToList, innan någon av körningarna skriver ut etiketter. Båda genomgångarna läser de lagrade deltagarna.
Dessa alternativ väljer den sekvens som ska enumereras och antalet genomgångar:
#if BADGE_SNAPSHOT
var attendeesToPrint = orderedAttendees.ToList();
#else
var attendeesToPrint = orderedAttendees;
#endif
int passes = 1;
#if BADGE_REPEAT
passes = 2;
#endif
Omedelbar exekvering innebär att en åtgärd körs omedelbart i stället för att vänta på senare iterering.
ToList kör frågan omedelbart och lagrar resultaten i en lista. Båda passen läser nu de lagrade deltagarna utan att köra sorteringen igen. Däremot skjuter det fortfarande upp sorteringen att definiera OrderBy-fråga: den läser och buffrar endast alla indata när uppräkningen börjar. Buffring innebär inte att frågan körs när du definierar den. Använd en snapshot när du behöver återanvända resultat utan att beräkna om dem; behåll den fördröjda frågan när du vill att varje uppräkning ska läsa de aktuella indata. Se Skapa frågor med cachelagrade resultat.
Innan du fortsätter kommenterar du ut BADGE_DIAGNOSTICS, BADGE_REPEAToch BADGE_SNAPSHOT igen. Behåll BADGE_SORT aktiverat. Kör appen för att bekräfta att endast de två sorterade etiketterna visas.
Skapa de begärda kopiorna med en iterator
Avkommentera #define BADGE_COPIES tillsammans med BADGE_SORT för att expandera varje deltagare till det begärda antalet etiketter. Loopen väljer nu den tidigare visade BADGE_COPIES-grenen och anropar iteratorn som redan finns i filen:
#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
Kör appen igen:
Ada | Guest [1/2]
Ada | Guest [2/2]
Ben | Host [1/1]
CreateLabels är en iterator, en funktion som genererar sekvenselement en i taget. Returtypen IEnumerable<T>, skriven som IEnumerable<string>, representerar en sekvens med strängar. Varje yield return levererar en etikett till den inre foreach loopen, som skriver ut den innan nästa etikett begärs. Se iteratorer.
slinganfor räknar kopior från 1 till och med attendee.Copies. En deltagare skapar så många strängar som Copies begär. En samling fungerar också, men anroparen behöver bara varje etikett en gång. Iteratorn undviker en separat etikettsamling och håller ihop copy-number-loopen. De yttre och inre foreach looparna separerar deltagarordningen från etikettproduktionen. Anroparen ansvarar fortfarande för utskrift.
Att anropa CreateLabels(attendee) kör inte funktionskroppen. Precis som LINQ-frågan använder iteratorn fördröjd körning. Det inre foreach startar det arbetet, inklusive kontrollen av positiv kopia. En ny iterering kör iteratorn igen. Till skillnad från OrderBy, som läser och sorterar alla deltagare före det första resultatet, tillhandahåller iteratorn en etikett utan att skapa de återstående etiketterna.
Om du vill observera iteratorns körningsordning, avkommentera #define BADGE_DIAGNOSTICS igen. Håll BADGE_REPEAT och BADGE_SNAPSHOT avstängda. Diagnostiken visar nu både sorteringen och iteratorn:
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 varje deltagare visas det sekvensskapade meddelandet före det iteratorstartade meddelandet och etiketterna. När du skapar sekvensen körs inte iteratorns kropp; att begära dess första element gör det. Kommentera ut BADGE_DIAGNOSTICS igen innan du fortsätter.
Samma experimentkontroller är fortfarande tillgängliga i de senare stegen.
BADGE_REPEAT räknar upp deltagarna två gånger och skapar varje deltagares etikettsekvens igen.
BADGE_SNAPSHOT cachelagrar endast de sorterade deltagarna, inte etikettsträngar, så det förhindrar en annan sortering men hindrar inte iteratorn från att köras igen vid varje pass.
Kontrollen ThrowIfNegativeOrZero rapporterar ogiltiga indata i stället för att utan att skapa några etiketter. Ett undantag rapporterar ett fel som avbryter normal körning. se Skapa och generera undantag. Här stoppar ett ogiltigt kopieringsantal programmet i stället för att tyst hoppa över den deltagaren.
Beräkna ett värde och namnge formateringssteget
Varje roll behöver ett annat meddelande. Ta bort kommentaren #define BADGE_FORMAToch lämna BADGE_SORT och BADGE_COPIES aktiverade. Samma iterator innehåller nu meddelandeberäkningen:
#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
Den väljer även grenen yield return FormatLabel(...) och innehåller den här lokala hjälpfunktionen:
#if BADGE_FORMAT
static string FormatLabel(
string name, string role, string message, int copy, int total) =>
$"{name} | {role} | {message} [{copy}/{total}]";
#endif
Kör appen igen:
Ada | Guest | Welcome! [1/2]
Ada | Guest | Welcome! [2/2]
Ben | Host | Ask me for help [1/1]
switchuttrycket beräknar meddelandet en gång per deltagare, före kopieringsloopen. Ett uttryck passar eftersom varje roll väljer ett värde att tilldela till message. En if/else kedja eller en switch -instruktion skulle fungera, men skulle upprepa tilldelningen i varje gren.
Mönstermatchning testar ett uttryck mot ett angivet mönster. Här är attendee.Role indatauttrycket, och de konstanta mönstren"Guest" och "Host" används för att testa dess utvärderade värde för att välja det matchande meddelandet.
discard-mönstret _ matchar alla andra roller och utlöser ett undantag, så roller som inte stöds får inget vilseledande standardmeddelande. Se Mönstermatchning.
nameof operatorn tillhandahåller parameternamnet attendee i undantaget. Till skillnad från en hårdkodad sträng förblir namnet korrekt om du byter namn på parametern med ett refaktorverktyg.
FormatLabel är en lokal funktion, en namngiven funktion som deklareras i en annan funktion. Eftersom endast CreateLabels behöver formateringsåtgärden begränsar placeringen av den i CreateLabels hjälpfunktionen till den åtgärd som använder den. En privat medlemsmetod i en klass skulle göra hjälpfunktionen tillgänglig för andra medlemmar i den klassen. Välj det bredare omfånget om flera åtgärder behöver samma formateringsåtgärd. Se Lokala funktioner.
Med namnet FormatLabel kan du följa kopieringsloopen utan att läsa formateringsinformationen. Att behålla den interpolerade strängen direkt i yield return skulle också fungera, som i föregående steg. Namnet blir mer användbart nu när en etikett innehåller ett rollspecifikt meddelande. Hjälpmetodens =>uttryckskropp returnerar den formaterade strängen.
Alla formateringsindata kommer via parametrar. En icke-statisk lokal funktion kan fånga eller använda variabler från dess omgivande omfång. Modifieraren static förhindrar den infångningen, så dess beroenden förblir explicita. Se Parametrar och fångat tillstånd.
Eftersom CreateLabels fortfarande är en iterator körs rollkontrollen endast när uppräkningen startar. För den här kontrollerade indatan hör dessa kontroller till etikettproduktionen. Programmet lovar inte att verifiera alla deltagare innan det skriver ut några etiketter.
Verifiera totalsumman och kör det fullständiga programmet
Om du vill jämföra den faktiska etikettproduktionen med det begärda antalet, avkommentera #define BADGE_COUNT. Alla fyra stegsymbolerna är nu aktiverade. Lämna de tre experimentsymbolerna bortkommenterade för de normala slutliga utdata.
Antalskonfigurationen körs före deltagarloopen:
#if BADGE_COUNT
int expected = attendees.Sum(attendee => attendee.Copies);
int printed = 0;
#endif
Sum använder omedelbar exekvering för att lägga till kopieringsantalen från indatamatrisen. Till skillnad från OrderByreturnerar den ett tal, inte en sekvens som ska räknas upp senare. Dess lambda-uttryck, attendee => attendee.Copies, väljer antalet kopior direkt i raden. Precis som med namnvalet skulle en separat hjälpmetod inte förtydliga det här korta uttrycket. En slinga med en ackumulator skulle också fungera. Sum anger direkt att du vill ha en summa. Se Vanliga LINQ-metoder.
printed++ increment i etikettloopen ökar bara räknaren när loopen tar emot en etikett. Efter den loopen jämför -instruktionenif det antalet med den begärda summan och genererar ett undantag om de skiljer sig åt:
#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
Du har nått standardkonfigurationen för hela programmet som du kopierade i början. Inga metoder eller deklarationer behöver ändras:
#define BADGE_SORT
#define BADGE_COPIES
#define BADGE_FORMAT
#define BADGE_COUNT
// #define BADGE_DIAGNOSTICS
// #define BADGE_REPEAT
// #define BADGE_SNAPSHOT
Kör den med dotnet run --file Program.cs. Resultatet är:
Ada | Guest | Welcome! [1/2]
Ada | Guest | Welcome! [2/2]
Ben | Host | Ask me for help [1/1]
Printed 3 of 3 requested labels.
Indatamatrisen finns redan i minnet.
Sum läser den direkt, OrderBy sorterar den när deltagarloopen börjar och CreateLabels skapar strängar när den inre loopen avancerar. Programmet bygger aldrig upp en separat samling etiketter. Mer omfattande vägledning om frågor finns i LINQ och samlingar.
Om du vill gå tillbaka till diagnostiken med det slutliga programmet aktiverar du BADGE_DIAGNOSTICS ensamt bland experimentsymbolerna. Resultatet är:
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.
Om du också aktiverar BADGE_REPEATkontrollerar varje pass och rapporterar sitt eget antal. Med BADGE_SNAPSHOT aktiverad visas sorteringsnycklarna bara en gång, men iteratormeddelandena, etiketterna och antalrapporten visas fortfarande vid varje pass. Kommentera ut experimentsymbolerna för att återställa normal slututdata.
Summary
Varje teknik fyller en annan funktion i samma uppgift:
- Lambdas och LINQ väljer namn och kopieantal direkt för sorteringen och summan.
- En iterator möjliggör anpassad produktion från en till många och lämnar hanteringen av etiketter till anroparen.
-
Ett
switchuttryck beräknar ett rollspecifikt värde och gör roller som inte stöds explicita. - En lokal funktion namnger formateringssteget, begränsar dess omfång och anger dess indata.
Välj dessa former när deras omfång, beroenden och körningsordning passar operationen. En direktloop, en delad metod eller en slutförd samling kan passa andra anropande funktioner bättre.