Självstudie: Kombinera C#-tekniker för att generera märkesetiketter

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

Börja med en etikett per deltagare

  1. Skapa en mapp med namnet BadgeLabels och en fil med namnet Program.cs inuti den.

  2. 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);
    
  3. Överst i filen kommenterar du ut de fyra aktiva #define raderna. Lämna även de tre experimentraderna utkommenterade.

  4. 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:

  1. Behåll endast BADGE_SORT aktiverat bland stegsymbolerna.

  2. 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;
    #endif
    
  3. Kör dotnet run --file Program.cs igen. Innan du läser utdata kan du förutsäga om Sort key: visas före eller efter After 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:

  1. Avkommentera #define BADGE_REPEAT och 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 | Host
    

    Det andra passet upprepar sorteringen. Frågevariabeln cachar inte resultat.

  2. Behåll BADGE_REPEAT aktiverat, avkommentera #define BADGE_SNAPSHOT och kör igen:

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

    De 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 switch uttryck 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.