Gå till innehållet

Kapitel 8 - Felsökning och olika variabeltyper

Introduktion till kapitlet

I detta kapitel ska vi dels undersöka hur man kan använda Visual Studio för att gå igenom sin kod steg-för-steg vilket kan hjälpa när man vill hitta buggar i sina program. En annan fördel med att gå igenom koden steg-för-steg är att det kan bli lättare att förstå kod som kan kännas svår. Kapitlet innehåller också en sammanfattning av de olika variabeltyper som vi har stött på hittills och se hur de kan delas in i två olika varianter, referenstyper och värdetyper.

Olika typer av fel

Man brukar dela in de fel som ett program kan ha i följande tre typer: kompileringsfel, exekveringsfel och logiska fel.

Kompileringsfel

Ett kompileringsfel gör så att ditt program inte kan kompileras, d.v.s. att din kod inte kan omvandlas till ett program. Om din kod har ett kompileringsfel så kommer Visual Studio att rödmarkera det ställe i koden där felet finns och programmet kan inte startas. Kompileringsfel kallas också för syntaxfel, och exempel på sådana som du säkert har stött på är att man kan glömma att skriva ett semikolon i slutet av en rad eller en parentes på ett ställe där det hade behövts.

Exekveringsfel

Ett exekveringsfel får programmet att krascha när det kör, t.ex. genom att programmet försöker omvandla text som inte innehåller tal med hjälp av int.Parse. Andra exempel på exekveringsfel är när programmet försöker komma åt ett index som inte finns i en array eller utföra division med 0. Man kan göra så att programmet inte kraschar vid exekveringsfel med hjälp av två sorters block som heter try och catch, men dessa ingår inte i kursen Programmering 1 och tas därmed inte upp i denna bok.

Logiska fel

Logiska fel är fel som innebär att programmet inte fungerar så som det är tänkt trots att det går att starta och att det inte kraschar. Denna typ av fel är ofta den svåraste typen att lösa. Ett exempel på ett logiskt fel skulle kunna vara att man vill att ett program ska räkna ut medelvärdet av två tal men att det inte beräknar medelvärdet korrekt när medelvärdet ska vara ett decimaltal. Logiska fel beror på att programmeraren helt enkelt har skrivit fel kod för att lösa det problem som hen ville. Även om logiska fel är svåra att upptäcka så kan Visual Studio hjälpa oss att hitta dem. Vi gör det genom att gå igenom vår kod rad för rad för att upptäcka vad som händer med programmets variabler, och du ska nu få lära dig hur man kan stega sig igenom programmet på detta sätt.

Felsökning med debuggern

När du kör dina program i Visual Studio så brukar du antagligen trycka på knappen ”Start” i verktygsraden högst upp eller tangenten F5. Det som egentligen händer när man gör detta är att man startar programmet med hjälp av Visual Studios debugger, ett engelskt ord som skulle kunna översättas till avbuggare eller buggborttagare. Vi ska se hur man kan använda debuggern för att gå igenom sitt program rad för rad.

För att testa debuggern så behöver vi program som vi ska köra igenom steg för steg. För att se de olika sätt som man kan använda debuggern så måste programmet vi ska använda innehålla en funktion som vi skrivit själva. Vi tar därför med en funktion som beräknar och returnerar summan av två heltal.

static void Main(string[] args)
{
    Console.WriteLine("Detta program kan beräkna summan av två tal");
    Console.WriteLine("Summan av 5 + 3 kommer nu beräknas");
    int summa1 = Summa(5, 3);
    Console.WriteLine("Nu kommer summan av 10 och 8 beräknas");
    int summa2 = Summa(10, 8);
    Console.WriteLine($"5+3 = {summa1}");

}

static int Summa(int tal1, int tal2)
{
    int summa = tal1 + tal2;
    return summa;
}

Breakpoints

Det finns olika sätt att pausa programmet med hjälp av debuggern, ett av de vanligaste är att sätta en breakpoint eller brytpunkt på en kodrad. Programmet kommer då att stanna precis innan denna rad ska köras. Du kan sätta en breakpoint genom att placera textmarkören på en rad och trycka på tangenten F9. Det går också bra att placera textmarkören på en rad och sedan välja Debug -> Toggle Breakpoint i Visual Studios meny. Du tar bort en breakpoint med samma knapp som du skapar den.

För att börja undersöka vårt program med debuggern så skapar vi en breakpoint på raden som skriver ut ”Summan av 5 + 3 kommer nu beräknas”. Denna rad kommer att bli rödmarkerad och en rund röd stoppikon visas längst bort till vänster på raden så som bilden visar. Du kan även skapa/ta bort en breakpoint genom att trycka med musen på den plats där stoppikonen visas.

Breakpoints i Visual Studio

När programmet är pausat så ändras verktygsraden lite och ser ut som nedanstående bild visar.

Menyrad för debuggern, 1

Knappen ”Start” har ändrats till att visa ”Continue”. Genom att trycka på denna knapp så fortsätter programmet att köra tills den kommer till någon ny breakpoint, om det inte finns några mer breakpoints i programmet så kommer det att köra klart precis som vanligt. Det finns en röd fyrkantig stoppknapp för att stänga av programmet och precis till höger om den finns en knapp som startar om programmet från början. Ännu längre bort till höger finns tre knappar som används tillsammans med debuggern för att stega sig igenom koden rad för rad, dessa har markerats i bilden nedanför.

Menyrad för debuggern, 2

Dessa tre knappar står för alternativen Step Into, Step Over samt Step out. Du hittar även dessa knappar i menyn Debug i Visual Studios menyrad.

Notera att fönstret Locals visas nere till vänster när du har pausat ditt program med debuggern. Här kan du se värdet av alla lokala variabler som finns i den metod som du kör just nu. Du kan också hålla muspekaren över en variabel i det vanliga kodfönstret för att se vad deras värden är just vid det ögonblick som programmet är pausat.

Undersöka variabelvärden

Det är väldigt användbart att undersöka variabelvärden när programmet har pausat vid en breakpoint eller på något annat sätt. Ett annat sätt att se värdet av en variabel när man testar sina program är att skriva ut det med Console.WriteLine men nu slipper du lägga till och ta bort dessa när du felsöker ett program.

Step Into

Knappen Step Into, som ser ut som en pil som pekar neråt på en cirkel, kör den kodrad som programmet är på just nu och pausar därefter direkt innan nästa rad körs. Vill du gå igenom allt som händer i ditt program så är det Step Into som du ska använda. Testa att gå igenom hela programmet genom att bara trycka på Step Into. Du kan använda tangenten F11 som kortkommando istället för att trycka på knappen med musen.

Step Over

Step Over, som ser ut som en pil som pekar förbi en cirkel, fungerar till viss del på samma sätt som Step Into. Skillnaden är att Step Over inte lämnar den metod som du befinner dig i just nu när du stegar dig igenom koden. Testa att gå igenom hela programmet från början med Step Over istället för Step Into så kan du se vad skillnaden är, du kan använda F10 som kortkommando istället för att trycka på knappen med musen.

När du gick igenom programmet med Step Into så gick programmet in i metoden Summa när denna metod anropades från Main-metoden. Med Step Over så gick programmet istället förbi detta metodanrop och stannade kvar inuti Main-metoden. De rader som inte innehöll anrop till metoden Summa gicks igenom på precis samma sätt som när du använde Step Into.

Step Out

Step Out, som ser ut som en pil som pekar uppåt bort från en cirkel, gör så programmet kör vidare tills det har lämnat den metod som du befinner dig i just nu. Gå igenom programmet med Step Into tills du kommer till den första raden inuti metoden Summa och tryck därefter på Step Out. Du kommer då att lämna metoden Summa och fortsätta kunna stega igenom koden i Main-metoden.

Run to Cursor

Ett alternativ till att skapa en breakpoint för att pausa programmet på ett visst ställe är att högerklicka på raden som du vill pausa innan och välja Run To Cursor. Programmet kommer då att köras till den raden som du markerade eller till eventuella breakpoints som finns tidigare i programmet. När programmet är pausat så kan du välja en ny rad, högerklicka på den och sedan välja Run To Cursor igen för att programmet ska köra vidare till den raden. Testa att ta bort breakpointen i exempelprogrammet och stega igenom det med Run To Cursor.

Du ska nu undersöka följande program i några uppgifter för att träna på att stega igenom ett program.

static void Main(string[] args)
{
    Console.WriteLine("Main 1");
    Console.WriteLine("Main 2");
    SkrivText("Anropa metoden 1");
    Console.WriteLine("Main 3");
    SkrivText("Anropa metoden 2");
    Console.WriteLine("Main 4");
    Console.WriteLine("Main 5");

    Console.ReadKey();
}

static void SkrivText(string text)
{
    Console.WriteLine("SkrivText 1");
    Console.WriteLine(text);
    Console.WriteLine("SkrivText 2");
}

Uppgift 8.1

Gå igenom hela programmet genom att skapa en breakpoint på den första raden inuti Main-metoden och därefter använda Step-knapparna.

Uppgift 8.2

Gå igenom alla kodrader i Main-metoden utan att pausa vid koden inuti SkrivText. Gör detta genom att skapa en breakpoint på den första raden i Main-metoden och därefter använda Step-knapparna.

Uppgift 8.3

Skapa en breakpoint på raden Console.WriteLine("SkrivText 1") och använd därefter Step-knapparna för att pausa programmet vid endast raden Console.WriteLine("Main 3").

Lösningstips 8.3

Använd Step Out.

Uppgift 8.4

Gå igenom programmet med hjälp av enbart breakpoints och Step-knapparna. Programmet ska stanna vid följande rader, men inte vid några andra rader.

  • Console.WriteLine("Main 2");
  • SkrivText("Anropa metoden 1");
  • Console.WriteLine("SkrivText 1");
  • Console.WriteLine("Main 3");
  • Console.WriteLine("Main 5");

Uppgift 8.5

Gå igenom programmet med hjälp av enbart Run To Cursor och Step-knapparna. Programmet ska stanna vid följande rader, men inte vid några andra rader.

  • Console.WriteLine("Main 2");
  • Console.WriteLine("SkrivText 1");
  • Console.WriteLine("Main 3");
  • Console.WriteLine("SkrivText 1");
  • Console.WriteLine(text);
  • Console.WriteLine("SkrivText 2");

Breakpoints med villkor

Ibland vill man bara pausa sitt program vid en breakpoint om en variabel har ett visst värde, och det är möjligt att få en breakpoint att fungera så. Vi ska se hur man kan göra detta i ett väldigt kort program, det viktiga är att du själv lär dig hur man skapar en breakpoint med villkor om du skulle vilja utnyttja det i framtiden. Börja med att skriva av koden i följande exempel.

for (int i = 0; i < 100; i++)
{
    Console.WriteLine($"Körning {i+1}");
}

Console.ReadKey();

Koden i programmets for-loop gör inte speciellet mycket, men i ett större program skulle det kunna vara så att många saker görs inuti en loop. Om vi vill undersöka värdet av programmets variabler efter att loopen har kört t.ex. 80 gånger så blir det väldigt jobbigt för oss att stega oss igenom de första 79 gångerna för att komma fram till den gången som vi tycker är intressant. Skapa en breakpoint på raden Console.WriteLine($"Körning {i+1}"). Högerklicka därefter på den röda stoppikonen som dyker upp på samma rad i Visual Studios vänstra marginal och välj Conditions. Här man kan skriva in villkor som måste stämma för att programmet ska pausa vid denna breakpoint, om vi t.ex. vill att vi bara ska stanna den gång då i är 80 så skriver vi in i == 80 i rutan.

Breakpoints med villkor

Det går att lägga till fler villkor genom att trycka på Add condition om man vill det.

Uppgift 8.6

Gör så att programmet pausar vid breakpointen du skapade i exemplet ovan de sista 5 körningarna av loopen.

Lösningstips 8.6

Lägg till ett villkor vid breakpointen som undersöker om i är större än eller lika med 95.

Olika variabeltyper

För att bättre förstå hur C# fungerar så ska vi studera de olika typer av variabler som finns lite djupare. Vi har hittills använt många olika variabeltyper som t.ex. int, string, double, Random och arrayer. Alla variabeltyper i C# kan sorteras in i en av två olika grupper: värdetyper och referenstyper. De flesta variabeltyper som vi har jobbat med hittills är värdetyper, men arrayer är referenstyper och vi kommer dessutom att stöta på fler referenstyper snart så det är bra att veta vad som skiljer dem åt.

Viktig teori

Detta avsnitt har inga programmeringsuppgifter i slutet men innehåller teori om hur olika variabeltyper fungerar. Det är väldigt viktigt att förstå skillnaden på värdetyper och referenstyper om man ska bli en bra programmerare.

Värdetyper

Variabeltyper som t.ex. int, double och bool är några exempel på värdetyper. Värdetyper är variabeltyper som sparar själva variabelns värde i på variabelns plats i datorminnet. Det kan kanske låta självklart att det är så eller svårt att veta vad som skulle sparas i variabeln annars, men referenstyper fungerar på ett lite annorlunda sätt som du snart kommer få se. Vi ska se hur värdetyper och speciellt kopiering av värdetyper fungerar i följande exempel. Variablerna a och b som är med i programmet skrivs ut flera gånger under programmets körning för att du ska kunna se vilka värden de har.

static void Main(string[] args)
{
    int a = 8;
    int b = a;
    Console.WriteLine($"a: {a}, b: {b}");

    a = 5;
    Console.WriteLine($"a: {a}, b: {b}");

    ÄndraVärde(a);
    Console.WriteLine($"a: {a}, b: {b}");

    Console.ReadKey();
}

static void ÄndraVärde(int tal)
{
    tal = 3;
}

I det första stycket av koden så skapas variablerna a och b. Variabeln b ges samma värde som variabel a, i detta fall 8, och får då en egen kopia av talet 8 sparat i sig. Värdet av båda variablerna skrivs ut av programmet.

Variabelnamn Värde
a 8
b 8

Variabeln a ges ett nytt värde, talet 5. Variablerna a och b är inte ihopkopplade även om vi tidigare skrev att b skulle vara lika med a. Värdet av b blev just då lika med värdet av a vilket gjorde att b fick värdet 8, men när a får ett nytt värde senare i programmet så kommer värdet av b inte att ändras.

Variabelnamn Värde
a 5
b 8

I det sista stycket i programmet så anropas metoden ÄndraVärde med variabeln a som argument. När detta sker så kopieras värdet av argumentet till metodens parameter, det vill säga värdet av variabeln a kopieras till variabeln tal. När metoden körs så ändras värdet av variabeln tal till 3 men detta påverkar inte variabeln a.

Variabelnamn Värde
a 5
b 8

Det finns likheter med hur variabeln a fungerade både när b fick värdet av a och när metoden ÄndraVärde anropades med a som argument. I båda fallen så kopierades värdet av a till en ny variabel, antingen b eller tal. När dessa variabler sedan ändrades så ändrades inte värdet av variabeln a. I exemplet så var variablerna av typen int, men det hade fungerat likadant för alla värdetyper.

Referenstyper

Variabeltyper som inte är värdetyper är istället referenstyper. Sådana typer sparar inte själva värdet av variabeln inuti variabelns plats i datorminnet, istället innehåller variabeln en referens till var i minnet som information finns. Alla arrayer är referenstyper och vi ske se hur det påverkar programmet nedan som liknar exempelprogrammet som handlade om värdetyper.

static void Main(string[] args)
{
    int[] a = { 5, 10, 15 };
    int[] b = a;
    Console.WriteLine($"a[1]: {a[1]}, b[1]: {b[1]}");

    b[1] = 20;
    Console.WriteLine($"a[1]: {a[1]}, b[1]: {b[1]}");

    ÄndraVärde(a);
    Console.WriteLine($"a[1]: {a[1]}, b[1]: {b[1]}");

    Console.ReadKey();
}

static void ÄndraVärde(int[] talArray)
{
    talArray[1] = 25;
}

I början av programmet skapas en array och sparas i variabeln a. Vad som egentligen händer är att på någon plats i datorns minne så skapas själva arrayen av heltal som innehåller värdena 5, 10 och 15, men själva arrayen sparas inte i variabeln a. Det som sparas i variabeln a är istället adressen till den plats i minnet där arrayen finns, vilket också kallas för en referens till den plats där arrayen är. Det går inte att veta vilken plats i minnet som arrayen kommer att skapas på när vi skriver koden, det bestäms först när programmet kör. Om vi antar att arrayen skulle skapas på minnesplats A20 när programmet körs så är värdet av a A20, alltså

Variabelnamn Värde
a A20

På platsen i datorns minne med adressen A20 finns själva arrayen som har följande innehåll.

Index Värde
0 5
1 10
2 15

När variabeln b deklareras och tilldelas ett värde så får den samma värde som variabeln b, nämligen A20. Både a och b har nu samma värde, de har adressen till samma array som finns på minnesplats A20. Man säger också att de refererar till samma array.

Variabelnamn Värde
a A20
b A20

När värdet av a[1] ska skrivas ut med Console.WriteLine så kommer programmet att gå till den array som finns på adressen som är sparad i a, arrayen på minnesplats A20. Den hämtar värdet som finns på index 1 i denna array, värdet 10, och skriver ut det. Detsamma sker när b[1] ska skrivas ut, och eftersom värdet hämtas från samma array båda gångera så skrivs talet 10 ut två gånger.

När koden b[1] = 20 körs så kommer programmet att gå till den array som finns på minnesplats A20 och ändra värdet på index 1 till 20. Värdet av variablerna a och b har inte ändrats någonting, de har fortfarande adressen till samma array som tidigare.

Variabelnamn Värde
a A20
b A20

Däremot så har själva arrayen som finns på minnesplats A20 ändrats, den har nu följande värden.

Index Värde
0 5
1 20
2 15

Eftersom a och b fortfarande refererar till samma adress kommer både a[1] och b[1] vara 20. När metoden ÄndraVärde anropas med variabeln a som argument så kommer värdet av a att kopieras till parametern talArray. Detta innebär att även talArray får värdet A20.

Index Värde
0 5
1 25
2 15

När sedan talArray[1] ändras så är det arrayen på minnesplats A20 som kommer att förändras. När metoden har kört klart och a[1] och b[1] ska skrivas ut en sista gång så kommer deras värden att ha förändrats till 25.

Hur skulle man då ha gjort om man ville att variabeln b skulle referera till en ny array som hade samma innehåll som arrayen a? Man hade i detta fall kunnat göra det med

int[] b = { 5, 10, 15 };

men det är inte alltid så att man vet värdet av alla element i arrayen man vill kopiera. En mer allmän metod hade varit att skapa en array med samma längd som a genom att skriva

int[] b = new int[a.Length];

och sedan gå igenom hela arrayen som a hänvisar till med en loop och kopiera över varje element till motsvarande index i arrayen som b hänvisar till. Det är dock inte vanligt att man vill göra en sådan kopiering av en array så försök att undvika det om det går.

Värdetyp eller referenstyp?

Om du håller musen över en variabeltyp i Visual Studio så får man information om variabeltypen i en ruta som dyker upp. Om variabeltypen är en struct eller enum så är den en värdetyp, är den en class så är den en referenstyp.

En array är alltid en referenstyp oavsett om arrayen innehåller en värdetyp eller referenstyp.

På detta sätt kan man se att t.ex. både int och bool tillhör kategorin struct, alltså är de värdetyper. Variabeltypen Random är en class och är därmed en referenstyp.

Du har nu fått se hur värdetyper och och referenstyper skiljer sig åt med hjälp av två snarlika program. För att verkligen förstå hur dina program fungerar och varför de ibland inte gör som du vill så måste man veta skillnaden mellan värdetyper och referenstyper, och det kan vara bra att i framtiden gå tillbaka till detta avsnitt för att repetera hur de skiljer sig åt. Tills vidare så ska du komma ihåg att alla arrayer är referenstyper, vi kommer att stöta på fler referenstyper senare i boken.

Arrayer som argument

I det förra avsnittet fick du se att man kan använda arrayer som argument när man anropar en metod och vad man behöver tänka på när man använder anropar metoder med en referenstyp. Vi studerar arrayer som argument i ytterligare ett exempel.

static void Main(string[] args)
{
    string[] namn = { "Anna", "Bob", "Clara", "Bob" };
    Console.WriteLine(namn[0]);
    BytFörstaOchSista(namn);
    Console.WriteLine(namn[0]);
    int antalBob = SökEfterSträng("Bob", namn);
    Console.WriteLine($"Antal Bob: {antalBob}");

    Console.ReadKey();
}

static void BytFörstaOchSista(string[] textArray)
{
    string temp = textArray[0];
    textArray[0] = textArray[textArray.Length - 1];
    textArray[textArray.Length - 1] = temp;
}

static int SökEfterSträng(string sökSträng, string[] sökArray)
{
    int antalPassande = 0;
    foreach (string text in sökArray)
    {
        if (text == sökSträng)
        {
            antalPassande++;
        }
    }
    return antalPassande;
}

I exemplet så skapas en array med fyra namn, ett av dessa förekommer två gånger. När Console.WriteLine anropas första gången skrivs namnet Anna ut. Innan Console.WriteLine anropas igen så anropas metoden BytFörstaOchSista med arrayen namn som argument. Denna metod byter plats på det första och sista elementet i arrayen och eftersom alla arrayer är referenstyper så påverkas den array som variabeln namn refererar till. Detta innebär att nästa Console.WriteLine kommer att skriva ut namnet Bob som tidigare låg på sista plats i arrayen. Vill man ändra en array som man skickar som argument till en metod så går det bra, man behöver inte skicka en ny förändrad array som svar vilket man hade behövt göra med en värdetyp.

Metoden SökEfterSträng letar igenom en array med strängar efter en viss söksträng och returnerar sedan hur många gånger som söksträngen fanns i arrayen. När metoden anropas i exemplet så söks arrayen igenom efter namnet Bob och den kommer då att returnera svaret 2. Denna metod visar att man kan använda metoder utan att påverka arrayens innehåll, denna metod skickar istället tillbaka ett svar om arrayen.

Uppgift 8.7

Skapa ett program som innehåller en metod som heter Summa. Metoden ska ha en parameter som är en double-array, den ska returnera summan av alla tal inuti arrayen.

Lösningsförslag 8.7

Uppgift 8.8

Skapa ett program som innehåller en metod som heter Medelvärde. Metoden ska ha en parameter som är en array av heltal, den ska returnera medelvärdet av alla heltal inuti arrayen. Testa att din metod fungerar med en array som endast innehåller talen 1 och 2.

Lösningstips 8.8

Tänk på att beräkningar med enbart heltal ger heltal som svar. Om du låter variabeln som beräknar summan av hela arrayen vara en double istället för en int så kan metoden fungera, alternativt så kan du konvertera ett heltal till en double vid en beräkning för att svaret ska bli en double.

Lösningsförslag 8.8

Uppgift 8.9

Skapa ett program som innehåller en metod som heter Vänd. Metoden ska ha en parameter som är en array av strängar. Metoden ska vända en array så att elementen hamnar i omvänd ordning, det första elementet ska bli sist, det andra näst sist o.s.v.

Lösningstips 8.9

Byt plats på elementet med index 0 och elementet med index (längd - 1), elementet med index 1 ska byta plats med elementet med index (längd - 2) o.s.v. Använd en temp-variabel vid bytet.

Lösningsförslag 8.9

Uppräkningar med enum-variabler

Alla variabeltyper är antingen referenstyper eller värdetyper. Man kan göra även andra, mer specificerade, indelningar av variabeltyper. En sorts variabeltyp som finns i C# är enum-variabler, enum kommer från det engelska ordet enumerable vilket ungefär betyder uppräkningsbar. En enum-variabeltyp innehåller ett visst antal förbestämda värden som är de enda värden en sådan variabel kan ha, det gör alltså att räkna upp alla möjliga värden som finns. Du har redan tidigare i boken använt en variabeltyp som är en enum-variabeltyp, nämligen ConsoleColor som du använder varje gång du vill ändra text- eller bakgrundsfärg. Vi ska nu se hur man ska spara enum-värden i en variabel vilket leder till att vi kan använda dem tillsammans med egenskrivna metoder.

Alla enum-variabeltyper är värdetyper och det gäller även för ConsoleColor. När man ska tilldela ett värde till en enum-variabel så gör man det genom att skriva variabeltypens namn följt av en punkt och därefter namnet på det värde man vill ha. Det är detta som sker i exemplet på raden

ConsoleColor favoritFärg = ConsoleColor.Blue;

där variabeln favoritfärg ges värdet blå. Programmet innehåller metoden SkrivMedFärg som kan skriva ut en rad med text med en valfri färg, efter att metoden anropats så ställs textfärgen tillbaka till samma färg som den var innan metodanropet.

static void Main(string[] args)
{
    // Skapa en variabel av typen ConsoleColor
    ConsoleColor favoritFärg = ConsoleColor.Blue;

    Console.WriteLine("Här är lite text");
    SkrivMedFärg("Röd text", ConsoleColor.Red);
    Console.WriteLine("Mer text med vanlig färg");
    SkrivMedFärg("Min favoritfärg är blå", favoritFärg);
    Console.WriteLine("Lite extra text med vanlig färg");

    Console.ReadKey();
}

static void SkrivMedFärg(string text, ConsoleColor färg)
{
    ConsoleColor nuvarandeFärg = Console.ForegroundColor;
    Console.ForegroundColor = färg;
    Console.WriteLine(text);
    Console.ForegroundColor = nuvarandeFärg;
}

När metoden SkrivMedFärg anropas så sparas den nuvarande textfärgen, som finns sparad i variabeln Console.ForegroundColor, i en variabel som heter nuvarandefärg. Textfärgen ändras till den färg som skickades med till metoden och texten skrivs ut. Efter att texten skrivits ut så ändras textfärgen tillbaka till det värde som är lagrat i Console.ForegroundColor. På detta sätt kan man skriva ut en enskild rad med valfri färg genom att bara skriva en kodrad i Main-metoden istället för 4 och vi har därmed sparat väldigt mycket plats varje gång vi vill göra detta.

Hittills så är ConsoleColor den enda enum-variabeln som vi har stött på men vi kommer att se fler exempel av enum-variabler längre fram när vi jobbar med MonoGame.

Uppgift 8.10

Skapa ett program som innehåller en metod som heter SkrivBaklängesMedFärg. Metoden ska ha en sträng och en ConsoleColor som parametrar och ska skriva ut den angivna strängen baklänges med den angivna färgen. Efter att strängen har skrivits ut så ska programmets textfärg återställas till den textfärg som den hade innan metodanropet.

Lösningsförslag 8.10

Uppgift 8.11

Skapa ett program som innehåller en metod som heter SkrivMedTvåFärger som har en sträng och två ConsoleColor som parametrar. Metoden ska skriva ut den första halvan av strängen med den ena färgen och den andra halvan av strängen med den andra färgen.

Lösningsförslag 8.11

Uppgift 8.12

Skapa ett program som innehåller en metod som kan användas för att skriva ut text där en del av texten ska ha en annan färg. Följande rader är exempel på vad din metod ska kunna skriva ut.

Denna rad har röd text i mitten.
Här är texten blå i början.
Hela denna rad är grön.
Denna rad är avslutningsvis lila.
Lösningstips 8.12

Metodens signatur skulle t.ex. kunna se ut så här:

static void SkrivMedFärg(ConsoleColor färg, string start, string mitten, string slut = "")

Lösningsförslag 8.12

Variablers standardvärden

Om en variabel deklararas, d.v.s. skapas, men inte tilldelas något värde så kommer den att ha standardvärdet för den variabeltyp som den är. Det är inte möjligt att använda en lokal variabel som inte har blivit tilldelad ett värde i C#, men man kan göra det med en klassvariabel. Alla olika taltyper som int och double har standardvärdet 0 medan bool har standardvärdet false, i de flesta fall så har värdetyper ett standardvärde som på något sätt motsvarar talet 0.

Mer viktig teori

Precis som avsnittet om variabeltyper så har inte detta avsnitt några uppgifter, och precis som det andra avsnittet innehåller det viktig teoretiskt information som alla bra programmerare behöver veta för att förstå varför programmen gör eller inte gör som de vill.

Alla variabeltyper som är referenstyper har ett speciellt värde som standardvärde, ett värde som heter null. En referenstyp innehåller som vi tidigare sett en adress till var i datorns minne själva innehållet i variabeln finns. Värdet null innebär att variabeln inte har någon adress till något ställe i datorminnet. Om man försöker hämta information om eller göra någonting med en variabel som har värdet null så kraschar programmet eftersom det inte vet var i datorns minne det ska gå för att hämta informationen vilket händer i programmet nedan. Programmet innehåller 5 klassvariabler som inte tilldelats något värde och därmed har de sina standardvärden.

static int tal;
static double decimaltal;
static bool santEllerFalskt;
static int[] talArray;
static string text;

static void Main(string[] args)
{

    // Skriver ut variablernas standardvärden
    Console.WriteLine($"tal: {tal}");
    Console.WriteLine($"decimaltal: {decimaltal}");
    Console.WriteLine($"santEllerFalskt: {santEllerFalskt}");

    // Programmet kraschar eftersom talArray är null
    Console.WriteLine($"talArray.Length: {talArray.Length}");

    // Programmet kraschar eftersom text är null
    Console.WriteLine(text.IndexOf('a'));

    Console.ReadKey();
}

De första tre raderna skriver ut standardvärdena av tre av programmets variabler. När det är dags att skriva ut längden av talArray så kommer programmet att krascha eftersom standardvärdet av alla arrayer är null. När programmet ska hämta värdet av talArray.Length så ska det kolla vad längden är av den array som finns på minnesadressen som är sparad i talArray. Eftersom det inte finns en minnesadress sparad i talArray så kan programmet inte ta reda på längden av någon array och därför kraschar det.

Även string är en referenstyp. Strängar är ett specialfall som egentligen är en referenstyp men av olika skäl är gjorde att fungera som värdetyper i många olika fall. Det man behöver tänka på med strängar är att ett program kan krascha om man försöker göra någonting med en sträng som inte har tilldelats ett värde. I övriga avseenden fungerar strängar som värdetyper.

Notera att programmet inte skulle krascha om man bara försökte skriva ut talArray eller text med Console.WriteLine, om man försöker skriva ut en variabel som har värdet null så skrivs ingenting ut.

En bra vana är att alltid tilldela variabler ett värde när du deklarerar dem för att undvika problem med null. Det är dock inte alltid möjligt att direkt tilldela en variabel ett värde, t.ex. när man skapar en array. I exemplet nedan så ges själva arrayvariabeln textArray ett värde eftersom en ny array med 5 strängar skapas, men varje sträng i själva arrayen får standardvärdet null. När programmet försöker gå igenom arrayen med en foreach-loop så kommer det att krascha när den första strängen i arrayen ska undersökas eftersom den är null.

string[] textArray = new string[5];

foreach (string text in textArray)
{
    if (text.Length == 3)
    {
        Console.WriteLine("Strängens längd var 3");
    }
}

Console.ReadKey();

För att undvika detta problem så är det en bra idé att gå igenom hela arrayen med en loop och ge varje sträng ett värde direkt efter att arrayen har skapats, i detta exempel hade följande kod kunnat göra detta.

for (int i = 0; i < textArray.Length; i++)
{
    textArray[i] = "";
}

Problemet med null uppstår bara med referenstyper, hade arrayen i exemplet bestått av t.ex. int-variabler så hade programmet inte kunnat krascha p.g.a. ett null-värde.

Exekveringsfel

En av de tre typer av fel som ett program kan ha är exekveringsfel. Eftersom ordet exekvering innebär detsamma som ”körning” av programmet så är ett exekveringsfel ett fel som gör att programmet kraschar när det körs. Vi ska studera några av de vanligaste exekveringsfelen som man kan stöta på som nybliven programmerare och hur man kan göra för att undvika dem. I C# så kallas exekveringsfel också för exceptions vilket översätts till undantag på svenska.

Felhantering

Det går att göra så att ett program inte kraschar när ett exekveringsfel uppstår även om vi inte tar upp hur man gör detta i denna bok. Man måste i sådana fall definiera hur man ska hantera felet, vilket kallas att man fångar upp felet. Detta görs med två typer av block som heter try och catch.

FormatException

När man försöker omvandla en sträng till ett tal med int.Parse så kommer programmet att krascha med ett FormatException om strängen inte kan omvandlas till ett tal. Detta felet är vanligt när användaren ska skriva in ett tal men hen råkar skriva in något som innehåller bokstäver. Du kan undvika de flesta FormatExceptions i dina program genom att använda int.TryParse istället för int.Parse.

IndexOutOfRangeException

När ett program försöker komma åt ett index som inte finns inuti en array eller sträng så kommer programmet att krascha med IndexOutOfRangeException. Indexet som du försöker komma åt är antingen för litet (om det är mindre än 0) eller för stort (om det är större än eller lika med arrayens eller strängens längd). Om du får ett IndexOutOfRangeException inuti en for-loop så får du tänka till lite extra om vilka värden loopens räknarvariabel har som minst och störst.

Ett IndexOutOfRangeException kan också uppstå om användaren skriver in det index som man vill komma åt i en array, då är det viktigt att undersöka det tal användaren skrev in så att det inte är för stort eller litet.

NullReferenceException

Om du försöker göra någonting med en variabel som har värdet null så kommer ditt program att krascha med ett NullReferenceException. Ett bra sätt att försöka undvika NullReferenceException är att alltid tilldela dina variabler ett värde när du skapar dem. När du skapar en array av en referenstyp, t.ex. string, så är det bra att loopa igenom hela arrayen och tilldela varje element ett värde så att arrayen inte innehåller värdet null någonstans.

Blandade uppgifter till kapitel 8

Uppgift 8.13

Skapa ett program som innehåller en metod som kan användas för att skriva ut text med en given textfärg och bakgrundsfärg. Metoden ska ha tre parametrar: texten som ska skrivas ut, färgen texten ska ha och bakgrundsfärgen bakom texten.

Lösningsförslag 8.13

Uppgift 8.14

Skapa ett program som ger ett IndexOutOfRangeException när det körs, användaren ska inte behöva skriva in någonting.

Lösningsförslag 8.14

Uppgift 8.15

Skapa ett program som skriver ut talen 100 till 1 i minskande ordning på var sin rad. Gör så att programmet pausar när talet 5 ska skrivas ut, stega dig därefter igenom resten av programmet rad för rad.

Lösningsförslag 8.15

Uppgift 8.16

Skapa ett program som ger ett NullReferenceException när det körs, användaren ska inte behöva skriva in någonting.

Lösningsförslag 8.16

Uppgift 8.17

Skapa ett program som har en metod som heter Max. Metoden ska ha en int-array som parameter och ska returnera det största talet som finns i arrayen.

Lösningstips 8.17

I metoden kan du börja med att skapa en variabel som heter max och ge den värdet av det första talet i arrayen. Undersök sedan med en loop om det finns något annat tal som är större.

Lösningsförslag 8.17

Uppgift 8.18

Skapa ett program som ger ett FormatException när det körs, användaren ska inte behöva skriva in någonting.

Lösningsförslag 8.18

Kommentarer