Programflow
Et af de vigtigste redskaber i programmering er muligheden for at styre, hvilke instruktioner der skal afvikles på et givet tidspunkt, og C# indeholder naturligvis kommandoer relateret til dette. Set i lyset af vigtigheden af disse kommandoer, er det bemærkelsesværdigt, at der i virkeligheden ikke er så mange af dem.
Information til undervisere
- Der er ikke så meget at understrege - det hele (måske bortset fra
goto
som bør begrænses) er vigtigt - Sørg for at de kigger på opgaverne og skriver noget kode
If-betingelsen
Det vigtigste kodeord i C# er uden tvivl if-kodeordet, som du kan benytte til styre afvikling af instruktioner baseret på et boolsk udtryk (et udtryk der returnerer sandt eller falsk):
I C# ser en if-struktur således ud:
Hvis der kun er én sand-blok, kan else-blokken udelades:
De instruktioner, der skal afvikles, placeres i tuborgklammerne, som samtidigt definerer et virkefelt således, at variabler defineret i virkefeltet kun lever her. Hvis der kun er én instruktion, der skal afvikles, hvis udtryk er sandt, kan tuborgklammer eventuelt undlades:
Men de fleste vælger at benytte tuborgklammer, selv om der kun er én instruktion. Selve udtrykket kan gemmes i en boolsk variabel eller angives direkte i if-strukturen, og det kan være både simpelt og ret komplekst med mange operatorer:
int i = 1, j = 2;
// Simpelt
bool b1 = i == 1;
if (b1)
{
}
// Komplekst
bool b2 = i == 1 || j > 1 && DateTime.Now.DayOfWeek == DayOfWeek.Monday;
if (b2)
{
}
// eller direkte i if
if (i==1)
{
}
if (i == 1 || j > 1 && DateTime.Now.DayOfWeek == DayOfWeek.Monday)
{
}
Hvis der er mange forgreninger, kan du eventuelt tilføje nogle else if-kommandoer. Når først en blok er afviklet, springes resten over:
Den bedste måde at lære forgreninger at kende er at sætte breakpoints, og så steppe igennem koden. Prøv det hvis du er i tvivl.
Husk at bruge snippets i Visual Studio og Visual Studio Code. Du kan skabe en if-struktur med if+tab+tab.
Konditional operator
I C# bruges spørgsmålstegnet (?
) i forbindelse med en if
-sætning til at repræsentere en konditional operator, ofte kendt som den ternære operator. Denne operator tillader en mere kompakt form for en if-else struktur. Den ternære operator består af tre dele: en betingelse, en værdi for ‘true’ og en værdi for ‘false’. Formatet er betingelse ? værdiHvisTrue : værdiHvisFalse
.
Antag, at vi vil tildele en værdi til en variabel baseret på en betingelse. I stedet for at bruge en traditionel if
-sætning, kan vi bruge den ternære operator:
I dette eksempel evalueres betingelsen antal > 5
. Hvis betingelsen er sand (true), bliver besked
sat til “Større end fem”. Hvis betingelsen er falsk (false), bliver besked
sat til “Mindre end eller lig med fem”. Den ternære operator gør koden mere læsbar og koncis, især i simple betingelsesmæssige tildelinger.
Mønstergenkendelse
I nyere C# versioner har du mulighed for at skrive udtryk på en lidt mere logisk måde ved hjælp af mønstergenkendelse (pattern matching), samt brugen af kodeordene is, and og or. Således kan følgende:
int antal = 30;
if (antal > 24 && antal < 40)
Console.WriteLine("Antal er større end 24 og mindre end 40");
også skrives som
int antal = 30;
if(antal is > 24 and < 40)
Console.WriteLine("Antal er større end 24 og mindre end 40");
Bemærk brugen af is og and. Det er mere logisk for os mennesker at tænke: ”hvis antal er større end 24 og mindre end 40” end ”hvis antal er større end 24 og antal er mindre end 40”. De nyere features relateret til mønstergenkendelse gør det muligt.
Led efter ”C# pattern matching” hvis du vil vide mere om avanceret mønstergenkendelse i C#”.
Switch-betingelsen
Som alternativ til if-kommandoen kan du eventuelt benytte switch-kommandoen, som nogle gange kan være lidt nemmere at overskue end en masse else if-kommandoer. Den fungerer ved at kontrollere en konkret værdi, som skal være af typen char, string, int, long eller enum, og så afvikle en given blok-kode. Syntaksen er som følger:
switch([variabel])
{
case [værdi]:
// instruktion(er)
break;
case [værdi]:
// instruktion(er)
break;
// andre case ...
default:
// instruktion(er)
break;
}
Bemærk, at for hver case-blok benyttes break-kodeordet for at bryde helt ud af strukturen, hvorefter afvikling fortsætter med første instruktion efter strukturen. Du kan eventuelt benytte en default-blok som afvikles, hvis ingen af de andre case-blokke afvikles – men den er ikke nødvendig.
Her er et simpelt eksempel, hvor der kontrolleres en int:
// Tilfældigt tal mellem 1 og 3 (inklusiv begge)
int i = new System.Random().Next(1, 4);
switch (i)
{
case 1:
// instruktioner
break;
case 2:
// instruktioner
break;
case 3:
// instruktioner
break;
}
Du kan eventuelt flytte break-kodeordet for at kontrollere på flere værdier:
int i = DateTime.Now.Month;
switch (i)
{
case 1:
case 2:
case 3:
Console.WriteLine("vinter");
break;
case 4:
case 5:
case 6:
Console.WriteLine("forår");
break;
case 7:
case 8:
case 9:
Console.WriteLine("sommer");
break;
case 10:
case 11:
case 12:
Console.WriteLine("efterår");
break;
default:
Console.WriteLine("Forkert værdi!!");
break;
}
Strukturen kan også benyttes mere avanceret ved at benytte et when-kodeord, men det kræver, at der angives en ny variabel af en konkret type, der så kontrolleres:
int i = DateTime.Now.Month;
switch (i)
{
case int x when x >= 1 && x <= 3:
Console.WriteLine("vinter");
break;
case int x when x >= 4 && x <= 6:
Console.WriteLine("forår");
break;
case int x when x >= 7 && x <= 9:
Console.WriteLine("sommer");
break;
case int x when x >= 10 && x <= 12:
Console.WriteLine("efterår");
break;
default:
Console.WriteLine("Forkert værdi!!");
break;
}
Det kan se lidt komplekst ud, men kan være ret brugbart i mere avanceret (og objektorienteret) kode, fordi der nu ikke blot kontrolleres en værdi, men også en type.
Brug switch+tab+tab for at indsætte kode ved hjælp af en snippet.
I nyere C# versioner kan du også benytte mønstergenkendelse (pattern matching) i forbindelse med switch. Som begynder skal du ikke gå så meget op i denne form for switch, for det kræver viden om både lambda-funktioner og delegates, men for en god ordens skyld kan du se følgende eksempel:
int antal = 30;
Console.WriteLine(antal switch
{
< 24 => "Antal er mindre end 24",
> 24 and < 40 => "Antal er større end 24 og mindre end 40",
_ => "Antal er større end 39"
});
// Antal er større end 24 og mindre end 40
Koden giver noget mere mening, når du har læst om lambda (=> operatoren).
For-løkken
En løkke kan bruges til at afvikle en blok-kode et givet antal gange (eller så længe et udtryk returnerer sandt), og C# har flere typer. Den mest benyttede er dog nok for-løkken:
Den består i sin grundlæggende form af tre instruktioner – erklæring af en tællevariabel, kontrol af værdi samt opskrivning eller nedskrivning af tællevariablen.
I sin mest simple syntaks ser det ud som følger:
for ([erklæring og tildeling af tællevariabel];
[kontroludtryk];
[opskrivning/nedskrivning af tællevariabel])
{
// kode
}
Her er eksempelvis en løkke, der tæller fra 0 til 9:
Hvis der blot er en enkelt instruktion, kan tuborgklammer eventuelt udelades:
Da det er helt op til dig, hvordan udtryk og påvirkning af tællevariabel skal ske, kan du skabe en masse forskellige løkker. Her er et par eksempler:
// Hver anden (0, 2, 4, ...)
for (int i = 0; i < 10; i = i + 2)
{
Console.WriteLine(i);
}
// Bagfra (9, 8, 7, 6, ...)
for (int i = 10; i > 0; i--)
{
Console.WriteLine(i);
}
Da tællevariablen erklæres i løkkestrukturen, kan den kun tilgås i selve blokken:
I Visual Studio kan en for-løkke nemt skabes med en snippet. Brug ”for + tab + tab”.
Do- og while-løkken
Nogle gange ved du ikke, hvor meget du skal arbejde i en løkkestruktur. Måske vil du eksempelvis afvikle kode så længe, der er linjer i en fil, eller data i en tabel. Her kan do-strukturen, eller den mere rene while-struktur, være brugbar.
En do-løkkestruktur ser således ud:
I denne struktur afvikles kode i en løkke, så længe et udtryk er sandt, og du er sikker på at komme ind i strukturen mindst én gang.
En while-struktur er vendt om:
Også her afvikles kode i en løkke, så længe udtrykket returnerer sandt, men da udtrykket er placeret i toppen, behøver du ikke komme ind i strukturen overhovedet.
Her er et par eksempler:
// Tæller til tre (0, 1, 2) med en tællevariabel
int i = 0;
do
{
// kode
i++;
} while (i < 3);
DateTime tid = DateTime.Now.AddSeconds(5);
// Løber i 5 sekunder
while (DateTime.Now < tid)
{
// kode
}
I Visual Studio kan du indsætte kode med en snippet til både en do (do + tab + tab) og en while (while + tab + tab).
Brug af continue
I løkkestrukturer kan du eventuelt benytte continue-kodeordet for at fortælle compileren, at du ønsker at starte næste iteration omgående, og dermed springe resten af koden i en løkke over. Det kan eksempelvis benyttes således:
// Tæller 1, 2, 4, 5 fordi 3 springes over
for (int i = 1; i < 6; i++)
{
if (i == 3)
continue;
Console.WriteLine(i);
}
Kodeordet kan også benyttes i do- eller while-løkker.
Brug af break
Nogle gange ønsker du at hoppe helt ud af et loop, og her kan du bruge break-koden (ligesom i en switch-struktur).
Hvis compileren ser et break, vil næste instruktion til afvikling blive første instruktion efter løkken:
// Tæller 1, 2 fordi break hopper helt ud af løkken
for (int i = 1; i < 6; i++)
{
if (i == 3)
break;
Console.WriteLine(i);
}
Løkker i løkker
Du må naturligvis gerne placere løkker inde i andre løkker. Det kan være ret brugbart, hvis du eksempelvis ønsker at gennemløbe en datastruktur med flere dimensioner. Jo flere løkker, du placerer i hinanden, jo sværere bliver det at overskue, hvad der foregår, men her er især debuggeren guld værd. Hvis du sætter et breakpoint og stepper dig igennem koden instruktion for instruktion, er du ikke i tvivl.
En anden måde er at udskrive indeks – eksempelvis:
for (int i = 0; i < 3; i++)
{
for (int x = 0; x < 3; x++)
{
Console.WriteLine($"i: {i} x: {x}");
}
}
/*
i: 0 x: 0
i: 0 x: 1
i: 0 x: 2
i: 1 x: 0
i: 1 x: 1
i: 1 x: 2
i: 2 x: 0
i: 2 x: 1
i: 2 x: 2
*/
Hvis du følger logikken, vil du se, at den ydre løkke tæller en gang, mens den indre tæller tre gange. Du kan kombinere så mange løkker i hinanden, du måtte få brug for, men det kan være svært at holde tungen lige i munden nogle gange.
ForEach-løkken
Den sidste løkke-struktur er ForEach-løkken. Den er lidt speciel i forhold til de andre, fordi den arbejder med arrays og samlinger helt uden tællevariabler. Senere i bogen vil du blive introduceret til arrays og samlinger, og der kommer vi retur til ForEach-løkken.
Goto
Slutteligt kan du styre programpointeren med en goto-instruktion, men det skal du prøve at lade være med. Det skaber en kode, som er svær at gennemskue og vedligeholde, og de fleste C# udviklere går langt uden om goto, hvis de kan. Det er en instruktion, der typisk hører hjemme i de helt gamle iterative sprog og i lavniveau-sprog som assembler (hvor der slet ikke findes løkker, men en masse jump instruktioner). Men der kan være situationer, hvor goto kan være praktisk – eksempelvis hvis du skal hoppe ud af løkker, som er placeret inde i hinanden (break-kodeordet hopper kun ud af den løkke, den er placeret i). En goto-instruktion hænger sammen med en navngivet etiket:
Her er et eksempel på brug af goto:
Console.WriteLine("Start");
for (int i = 1; i < 11; i++)
{
for (int x = 1; x < 11; x++)
{
if (x == 5 && i == 5)
goto slut;
}
}
slut:
Console.WriteLine("Slut");
Her vil goto sørge for at hoppe ud af begge løkke-strukturer og fortsætte afvikling.
Kodeordet goto er en fuldt lovlig instruktion, men prøv at begrænse brugen af det eksempelvis at hoppe ud af dybe løkker – ellers kan det skabe noget frygtelig kode at vedligeholde på et senere tidspunkt.