35 min read

C# och .NET

C# och .NET

Innehållsförteckning

Introduktion

Hej, detta är den andra modulen i en komplett kurs i C# programmering med .NET som jag har utvecklat.

I denna modulen ska vi titta lite närmare på .NET(Core) och vad som gör det möjligt att skriva applikationer en gång och köra överallt. Vi kommer dessutom att dyka ner på djupet i programmeringsspråket C#.

Bakgrund och historik

Innan vi börjar med att lära oss programmera med C# så ska vi ha en liten historia lektion. Det är alltid bra att känna till lite om bakgrund och orsak till vad vi har och vad vi gör.

Före .NET (Framework)

Ett av de stora problemen som vi som utvecklare upplevde under den senare hälften av 90-talet. När vi byggde applikationer för Windows plattformen och framförallt när vi skapade COM applikationer, var avsaknaden av kompabilitet mellan olika versioner av Windows operativsystem, men även mellan olika Windows applikationer påfrestande. På den tiden användes något som kallades för DLL'er(Dynamic Linked Libraries) som användes för att kommunicera med operativsystemens olika funktioner samt mellan olika applikationer. Vi upplevde något som kom att kallas för DLL Hell.

Microsoft var fullt medvetna om detta problem och bestämde sig för att göra ett fullständigt omtag gällande applikationsutveckling på Windows plattformen. Det var så som .NET Framework föddes. .NET Framework var och är ett ramverk som skapade en virtuell exekverings miljö som betedde sig lika oavsett vilken typ av applikation som utvecklades. Vi som utvecklare kunde nu fokusera på applikationerna istället för att fokusera på att hantera Windows operativsystemens ändringar internt och hur andra applikationer var skrivna.

Samtidigt som Microsoft bestämde sig för att utveckla ett nytt ramverk så bestämde sig de också att utveckla ett nytt programmeringsspråk som var gjort enbart för det nya ramverket, C#. Samtidigt förstod Microsoft att de måste flytta sitt populära programmeringsspråk Visual Basic(VB) till .NET ramverket. Vid den tiden så var Visual Basic det mest populära språket för att skapa Windows applikationer. Det fanns någonstans mellan 6 - 10 miljoner utvecklare i Europa som älskade Visual Basic, dessa ville inte Microsoft förlora.

.NET Framework

2002 lanserades .NET Framework och C# officiellt och blev en omedelbar succé. Det som gjorde det till en sådan succé var att ramverket tillåter applikationer skrivna i olika programspråk nu att kommunicera med varandra utan komplicerade översättningar och konverteringar. Detta innebar och fortfarande innebär att utvecklare inte behöver lära sig ett nytt programspråk utan kan fortsätta att utveckla i sitt favorit språk, t ex Visual Basic. Sedan kan andra utvecklare som till exempel utvecklar i C# kommunicera direkt med Visual Basic utvecklade applikationer.

.NET Core

2016 lanserade Microsoft sitt nya ramverk .NET Core, det som idag enbart benämns som .NET. Anledning till detta nya ramverk var att Microsoft behövde skapa ett plattforms oberoende(Cross Platform) ramverk för utvecklare. Med .NET Framework så fick vi ett programmeringsspråks oberoende, med .NET Core gick Microsoft ett steg längre. Nu är vi inte längre låsta till Microsofts Windows miljö. Nu kan vi skapa applikationer som går att köra på Windows, macOS samt Linux. Inte bara köras på dessa plattformar utan även utvecklas på dem. Vilket innebär att vi kan utveckla våra applikationer på vårt favorit operativsystem och sedan köra det vart vi vill(Window, macOS eller Linux).

Detta i sin tur öppnade upp C# till en helt ny grupp av utvecklare. De som tidigare var tvungna att använda sig av Java, Kotlin, Objective-C, Swift eller C++ kunde nu använda sig av C# alla typer av applikationer.

Om sanning ska fram så kunde vi redan innan 2016 åstadkomma detta via ett initiativ som kallades Mono projektet. Men det stora genomslaget kom inte förrän Microsoft lanserade .NET Core

Denna modul kommer att fokusera på .NET (Core) och C#, men det är alltid bra att ha en liten bakgrund till vart vi är idag.

Fördelarna med .NET (Core) ramverket

.NET (Core) är en plattform för att utveckla webb applikationer, tjänster, desktop applikationer på Windows, macOS samt Linux. Så vad tillför då .NET (Core)

  • Öppenhet mellan de olika ramverken. Äldre .NET Framework applikationer kan kommunicera med modernare .NET Core applikationer via .NET Standard.
  • Stort stöd för olika programspråk, C#, VB, F#. Där C# och F# är idag de primära språken för .NET(Core).
  • En gemensam exekveringsmotor, som delas mellan alla .NET(Core) språken.
  • Integration mellan programspråk, klasser skapade i ett språk kan användas av applikationer skrivna i andra språk.
  • Ett omfattande klass bibliotek som tillhandahåller tusentals fördefinierade typer. Som låter oss allt ifrån enkla applikationer till avancerade webb applikationer.
  • En förenklad distributionsmodell, som gör det enkelt att driftsätta applikationer lokalt eller i molnet.
  • Ett mycket kraftfullt stöd för terminal eller konsol kommandon.

De fundamentala byggstenarna i .NET Core

Nu när vi har en bakgrund till vart vi har hamnat är det dags att titta på hur allt detta är möjligt(Plattforms och programspråks oberoende). Följande byggstenar är av väsentlig betydelse för att få det hela att fungera

  • .NET Core Runtime
  • CTS (Common Type System)
  • CLS (Common Language Specification)
  • Base Class Libraries
  • .NET Standard

.NET Core Runtime

.NET Core Runtime som tidigare gick under namnet CLR(Common Language Runtime) är en exekverings miljö med ett omfattande klass bibliotek. Exekverings miljön innehåller en minimal uppsättning implementeringar som är knutna direkt till ett operativsystem (Window, macOS och Linux) samt den arkitektur som stöds av respektive operativsystem(x86, x64, ARM, ARM64). Dessutom finns alla grundläggande typerna som behövs för .NET (Core).

CTS - Common Type System

CTS är en specifikation som beskriver alla tillgängliga typer och kod strukturer, klasser, structs, enumerations, som stöds av exekverings miljön. I specifikation finns definitioner om hur de olika typerna kan kommunicera med varandra samt hur de är definierade i .NET's metadata format.

Det är viktigt att förstå alla .NET Core språken kanske inte stöder alla funktioner i CTS. Därav anledningen till att det finns en speciell specifikation(CLS).

CLS - Common Language Specification

CLS är en kompletterande specifikation som förtydligar CTS. CLS specificerar en delmängd av CTS som alla programspråk måste följa. Så om vi följer specifikationen CLS kan vi vara säkra på att vår kod alltid kommer att fungera tillsammans med applikationer utvecklade med andra programspråk för .NET.

Base Class Libraries

Detta är ett grundläggande bibliotek med klasser som är tillgängliga för alla språk i .NET. I detta bibliotek kan vi hitta stöd för att skapa och hantera trådar, I/O, rendera grafik, kommunikation med extern hårdvara samt ett antal tjänster(services) som oftast behövs i större system. Som t ex nätverkskommunikation, en-vägs och två-vägs kommunikation.

.NET Standard

.NET Standard är en standardisering av hur kommunikation ska kunna fungera mellan applikationer skapade med den äldre .NET Framework och med den nya .NET(Core) plattformarna. Så enkelt uttryckt så definierar .NET Standard vad som är tillgängligt i bägge plattformarna. Så vi som utvecklare kan ta reda på om det vi försöker(vill) göra är tillgängligt på bägge plattformarna.

C#

C# är idag det primära programmeringsspråket för .NET. När man börjar med C# så ser man ganska snabbt likheter med Java, men C# är ingen Java klon. Både C# och Java har sitt ursprung ur alla programspråks moder, C. Det är därför vi ser likheter i språken men sanningen är den att C# lånar enkelheten ifrån Visual Basic och kraftfullheten ifrån C++. Om man som jag har en lång erfarenhet av programmering, ända tillbaka till 80-talet så ser man även likheter ifrån andra programspråk som t ex LISP.

Sanningen är den att merparten av C# är influerad av C-baserade språk som C++ och Java.

Här är en kort lista med grundläggande funktioner som vi hittar i alla versioner av C#.

  • Inga pekare! Vi behöver inte hålla reda på vilka adresser i minnet som vi har allokerat.
  • Automatisk minneshantering via en process som heter GC(Garbage Collection)
  • Formaliserad konstruktion av klasser, interface, delegates, structs och enumerations.
  • Överlagring av metoder och operatorer.
  • Stöd för attribut baserad programmering.
Om Ni inte känner igen några av dessa begrepp, var inte oroliga vi kommer att ta hand om dessa när vi kommer till dem i vår kod.

Låt oss komma igång med C#

Nog med teori, låt oss nu komma igång och skapa applikationer med C#.

Jag kommer att använd mig av VS Code för alla exempel, det går att använda Visual Studio också, men för att se samma saker och hitta dem på samma ställen så rekommenderar jag att ni också använder VS Code.

En viktigt detalj är att jag sitter på en Mac och skapar mina exempel applikationer, så mina VS Code menyer ligger inte direkt i VS Code utan uppe i menyraden på Mac. På Windows hittar ni menyerna direkt i VS Code.

Snabb orientering i VS Code

Vi ska ta en snabb rundvandring i VS Code först innan vi skapar en C# applikation.

Så börja med att öppna upp VS Code.

Start page VS Code

Till vänster hittar vi uppifrån och ner

  1. Explorer fönstret
  2. Sök
  3. Källkods hantering
  4. Kör och avlusning
  5. Tillägg
Det som är viktigt att förstå med VS Code är att VS Code är en fil baserad editor. Det betyder att vi inte har samma struktur på våra applikationer som vi får i t ex Visual Studio och Rider, som är projekt baserade.
  1. Explorer fönstret: Om vi klickar på denna ikon så kommer vi att se våra filer som ingår i vår applikation.
  2. Sök: Här kan vi söka eller ersätta kod i våra filer.
  3. Källkodshantering: Här kan vi koppla på Git för att versionshantera vår kod.
  4. Kör och avslusning: (Run and Debug), här kan vi t ex köra en .NET applikation, sätta brytpunkter och stega oss igenom koden för att hitta eventuella problem.
  5. Tillägg: Det är här som vi hämtar och installera det stöd som vi behöver ha för att bli effektiva och produktiva.

Installation av .NET och C# stöd i VS Code

Innan vi kan börja utveckla C# applikation för .NET måste installera stödet för .NET Core och C# i VS Code. Klicka på ikonen Tillägg i det vänstra meny fältet i VS Code. I sökfältet skriv C#, då får vi ett antal träffar. Det enda tillägget som är intressant för oss just nu är det första som vi får träff på C# for Visual Studio Code(powered by OmniSharp). Klicka på knappen install och vänta tills tillägget är installerat.

VS Code plugins(OmniSharp)

Nu är vi redo att skapa vår första applikation!

Vår första Hello World applikation

Vi kommer att skapa den traditionella och lite tråkiga Hello World applikationen som man alltid får göra när man ska lära sig ett nytt programspråk.

Börja med att öppna upp ett terminalfönster i macOS eller kommandotolken i windows. I terminal eller konsol fönstret navigera till en katalog där ni vill placera applikationen.

Sedan skriver vi in följande kommando dotnet new console -n HelloWorld och trycker på Enter.

dotnet är kommandot för att ange att vi vill skapa något i .NET.
dotnet är ett Command Line Interface(CLI) verktyg. new betyder att vi vill skapa en ny applikation. console talar om för dotnet new kommandot att det är en konsol applikation som ska skapas. Flaggan -n använder vi för att ge ett namn på katalogen som vi vill skapa för vår applikation och HelloWorld är namnet på katalogen. Om vi inte anger flaggan -n så kommer applikationen att skapas i en katalog med samma namn som katalogen vi står i.

När applikationen är skapad så navigerar vi in i den nya katalogen HelloWorld.

I katalogen skriver vi nu in följande kommando i terminalen code . ja det är en punkt efter code (glöm inte mellanslaget mellan code och punkten). Detta är ett kommando som öppnar upp VS Code i ett nytt fönster med vår applikation.

Om inte kommandot fungerar så beror detta oftast på att vid installationen av VS Code, att sökvägen till VS Code inte har hamnat rätt.

Det är väldigt enkelt att fixa detta. i VS Code tryck ner tangent kombinationen Shift+Command+p(på Mac), Shift+Ctrl+p(på Windows). Då får vi upp kommando paletten i VS Code. I sökfältet skriv in PATH och välj alternativet Install 'code' command in PATH.

Install 'code' command in PATH
När det är gjort så borde det fungera att igen skriva in code . för att öppna ett nytt VS Code fönster med vår applikation.

Genererad kod

Om vi nu tar och klickar på explorer ikonen i det vänstra meny fältet, knappen högst upp till vänster. Så öppnas filstrukturen för vår applikationen. För tillfället är vi bara intresserade av två saker här. Filerna HelloWorld.csproj och Program.cs.

HelloWorld.csproj är en projekt fil som innehåller inställningar och beroenden som behövs för att köra vår applikation. Så klicka på den så den öppnar sig i kod fönstret.

.csproj

Om vi börjar uppifrån och går ner så har vi högst upp vilket SDK som vi använder. I denna applikation så är det grundläggande biblioteket "Microsoft.NET.Sdk"
Om vi sedan går ner till raden 4 så hittar vi vad för typ av applikation är det vi skapat. Vi ser detta i elementet <OutputType>Exe</OutputType>. Här ser vi att vi har skapat en körbar(exekverbar) applikation.
Nästa viktiga del i denna fil är det som vi hittar innanför elementet <TargetFramework></TargetFramework>. I mitt fall står det net7.0 detta beror på att jag har installerat net 7.0 Release Candidate på min dator. Hos er bör det stå net6.0.

Låt oss nu öppna upp filen Program.cs i kod fönstret.

HelloWorld Program.cs

Lite av ett antiklimax kan man känna här😁. En enda rad kod, men väldigt viktig. Vad gör den då?

Öppna upp terminal fönstret igen om ni har råkat stänga det. I terminalen skriv in kommandot dotnet run

Vi bör nu se samma sak som vi såg i föregående modul. Vi ser texten Hello, World! Utskriven i terminal fönstret.

Hur gick detta till? Låt oss gå tillbaka till koden igen och gå igenom vad som egentligen sker här.

Raden Console.WriteLine("Hello, World!"); använder sig av de inbyggda funktionerna i .NET som vi gick igenom ovan.

Console är en klass som finns i Base class biblioteket. Som vi får på köpet vid installation av .NET. Dess syfte är att kommunicera med konsol eller terminal fönstret via ett annat klass bibliotek, I/O(Input and Output). Vi behöver inte veta hur detta görs utan vi bara utnyttjar en klass för detta.

Nästa del i raden är WriteLine, detta är en metod(funktion) som finns i klassen Console. Som har som syfte att skriva ut det som står innanför paranteserna.

Innan vi avslutar denna del så ska vi bara leka lite med inställningar som vi kan göra med klassen Console. Skriv in koden som ni ser på bilden nedan och sedan kör om applikationen med dotnet run igen.

Playing with Console

Det vi gjort här är att anpassa hur vi vill ha texten och bakgrunden konfigurerad när vi skriver till terminal fönstret.

Console klassen har fördefinierade inställningar som vi kan använda som t ex ForegroundColor och BackgroundColor. Det finns även färdiga kodstrukturer som ger oss tillgång till färger. ConsoleColor.Green exempelvis.

En sista sak innan vi går vidare till språket C# så vill jag bara ta upp en sak till. Ibland behöver vi dokumentera vår kod så att någon annan utvecklare förstår hur vi tänkte. Ibland behöver vi det för vårt eget minnes skull.

Givetvis är inte kommentarer detta enda och kanske ibland inte det bästa sättet att kommenterar vår kod. I kombination med bra namn på variabler, klasser och metoder så får vi en väl läsbar och förståelig kod. Vi kommer senare att gå igenom variabler, klasser och metoder.

Kommentarer i kod

Vi har två olika varianter på kod kommentarer i C#. En rads kommentarer och fler rads kommentarer. Om sanningen ska fram så har vi faktiskt ett tredje sätt att kommentera vår kod och det är med tre snedstreck (///).

Jag kommer inte att gå igenom det tredje sättet här utan det lämnar jag till när vi kommer in på REST API'er med .NET

För att skapa kommentar som bara är en rad anger vi två stycken framåt snedstreck(Shift+7) //.

För fler rads kommentarer så börjar vi kommentaren med /* sedan skriver vi kommentaren över flera rader och avslutar med */


// Detta är en kommentar över endast en rad
/* Detta är en kommentar 
som går över flera rader */
Console.ForegroundColor = ConsoleColor.Green;
Console.BackgroundColor = ConsoleColor.Yellow;
Console.WriteLine("Hello, World!");
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.White;

Nu när har vi konfigurerat VS Code och vi har sett hur vi kan använda dotnet cli för att skapa en enkel konsol applikation är det dags att börja förstå språket C#.

Språket C#

Statements

När vi i det svenska språket ska formulera meningar så sätter vi ihop ord till meningar och avslutar med en punkt.

I C# så formulerar vi statements som kan jämföras med meningar i det svenska språket. I C# består statements av variabler och uttryck och avslutas med semikolon(;).

Ta följande exempel:
decimal result = value1 + value2;

Hela raden är ett statement(mening) som avslutas med ett semikolon.
value1 + value2 är ett uttryck i detta fall en beräkning

Block

När vi i det svenska språket vill sätta samman flera meningar använder vi paragrafer eller stycken av text.

I C# har vi block av kod som motsvarar flera statement och uttryck grupperade mellan måsvingar eller krullparanteser😁({}).

Exempel:

if(10 < 8){
    // Då gör vi något.
}
else{
    // Annars gör vi detta.
}

Data Typer

Vi ska börja med att gå igenom de primära datatyperna som finns tillgängliga i .NET Core. Listan är inte på något sätt komplett, vi kommer under resans lopp att upptäcka nya typer. Däremot så kommer vi att gå igenom ett mycket viktigt område. Minneshantering, var lagras våra datatyper och hur kommer vi åt dess värde.

I .NET har vi två kategorier av datatyper Värde typer (Value types) och Referens typer (Reference types).

Typ V R
int X
char X
bool X
double X
decimal X
enum X
string X
array X
object X

V = Värde typ(Value type) R = Referens typ(Reference type)

Value och Reference variabler

För att förstå vad skillnaden är mellan en referens typ och en värde typ så måste vi förstå hur datorns minne är uppdelat för våra applikationer.

Internminnet eller RAM(Random Access Memory) i en dator är uppdelat i tre delar:

  • Static Area (Static Memory)
    • Är placerat i början på RAM
    • Är till för att lagra information som inte ändras under applikationens exekvering
  • Stack (Automatic Memory)
    • Här lagras information som automatiskt tas bort när den inte längre används
    • Datatypen måste vara en värde typ(Value Type)
  • Heap (Free Store)
    • Här lagras information som är skapas via nyckelorder new
    • Eller om datatypen är en referens typ(Reference Type)
  • Value = variabeln och dess värde är lagrat på stack arean. Vilket betyder att när vi behöver få tag i variabeln och dess värde behöver vi endast gå till stack arean i minnet och leta upp variabeln och vi har dess värde
  • Referens = variablen är lagrad på stack arean i minnet men värdet är lagrat på heap arean. Hur kommer vi då åt värdet om variabeln är placerad någon annanstans än värdet. När vi skapar en referens typ och sätter ett värde så placeras värdet på heap arean och en adress skapas. Denna adress knyts till variabelns namn som lagras på stack arean.

Detta innebär att när vi vill läsa ut värdet på en referens typ så måste först variabeln hittas på stacken. Där hittar vi adressen till värdet och denna adress används nu för att hämta värdet på vår variabel. Se följande exempel:


double amount;              // Kommer att placeras på stacken.
string fromCurrency = "SEK" // Kommer att placeras på heap.
string toCurrency = "USD"   // Kommer att placeras på heap.

Nu har vi en bättre förståelse för vilka datatyper vi kan använda samt var de lagras och hur vi kan vi läsa ut dess värden.

Låt oss gå vidare och diskutera uttryck och operatorer.

Uttryck och Operatorer

Uttryck

Ett uttryck i kod kan summeras som en beräkning som returnerar något

Exempel:

int x = 10;
int y = 5;
int result = 0;

result = x + y;

Raden (x + y) är ett uttryck. Precis som variabler så har ett uttryck en typ och ett värde. I vårt fall är typen int för x och y och värdena är 10 för x och 5 för y.

Operatorer

De grundläggande operatorerna i C# är följande

Operator typ Symbol Beskrivning
Matematiska + - * / % Hederliga företrädes regler gäller
Tilldelning = Initiering eller tilldelning
Ökning eller minskning av värde ++ -- Lägger till 1 eller tar bort 1
Jämförelse == != lika med eller inte lika med
Relations jämförelser > < >= <=
Logiska && || !
Lambda => Genväg för en funktion eller metod
Villkor ? : Inline conditionals
Nullable ? ??

Vi ska nu titta lite närmare på tilldelning och jämförelse operatorerna

Tilldelning

När vi tilldelar ett värde till en värdetyp(value type) skapas en kopia av värdet och placeras på stacken.

När vi tilldelar ett värde till en referenstyp(reference type) skapas en kopia av adressens(reference) på heap delen av minnet.

Exempel:

int x = 10;
int y = x; //y får en kopia av x värde.

string name1 = "Michael";
string name2;

name2 = name1; //name2 får nu en kopia av adressen till name1 i minnet.

Jämförelse

När vi jämför två värde typer så jämför vi det faktiska värdet.

När vi jämför två referens typer så jämför vi minnesadresser.

Exempel:


int x = 10;
int y = x;

if(x == y) // Sant, värdena är detsamma...

string name1 = "Michael";
string name2 = "Gustavsson" ;

if(name1 == name2) // falskt, på grund av att jämförelsen sker på adress 
                   // och inte på värde.

Arrays och Strängar

Arrays

I C# kan vi skapa linjära lagringar av element av vilken datatyp vi vill. Vi kommer åt individuella element via dess index.

Arrays i C# allokeras dynamiskt på heap genom utnyttjandet av nyckelorder new

int[] numberList = new int[100] // Deklarerar en array med plats för 100 heltal.

Det är också möjligt att skapa en array med ett initieringsblock.
int[] primes = {2,3,5,7,11} // Deklarerar en array med de 5 först primtalen.

Vi kan även väldigt enkelt ta reda på antalet element i en array
int numbers = primes.Length

När vi har deklarerat och allokerat en array så kan vi lägga till data genom att använda index
numberList[1] = 45 // Placera värdet 45 i det andra elementet.

Tänk på att index startar på 0 inte 1!

!OBSERVERA! En array när den väl är skapad är låst till antalet element som vi definierat. Om vi skulle försöka med följande:
numberList[100] = 78 // Så skulle vi få ett outofbounds fel

Vilket egentligen bara innebär att vi försöker lägga till ett nytt element på en position som inte existerar i vår array och det är inte tillåtet.

Strängar

Strängar är inte av typen array, men vi kan manipulera en sträng som om den vore en array. Vi kan använda index för att hämta ut en bokstav ur en sträng, men vi kan inte byta ut en bokstav!

string name = "Michael";
char x = name[2] // hämta 3:e bokstaven(c) ur strängen

Vi kan också ta reda på antalet bokstäver i en sträng.
int characterCount = name.Length;

De flesta datatyper som vi har gått igenom har det som kallas för namngivna operationer, vilket egentligen bara betyder att det finns metoder på datatyperna som vi kan använda. Vi använder punkt notation "." för att få tillgång till dessa metoder.

Framförallt datatypen string har ett stort antal metoder som vi kan använda för att manipulera strängar.

Exempel:
Om jag till exempel vill plocka ut den 5:e bokstaven ur följande sträng.

string name = "Michael";
string character = name.Substring(5,1);

Strängar har även metoder för att formatera dem.

Exempel:

double x = 7721.532;
string formatedValue = string.Format("Värdet är: ${0:N2}", x);

Detta vill resultera i 7721.53

Följande format finns att tillgå

  • C = valuta
  • D = decimal
  • E = exponent
  • F = fixed decimal
  • G = generellt formatterat
  • N = samma som F men med en tusen separator

Organisering av kod

Innan vi fortsätter och diskuterar egna datatyper, ska vi bara ta en kort genomgång av kod strukturering.

I .NET har vi ett begrepp som heter namespaces. Vi använder namespace för att organisera vår kod i grupper av kod som hör ihop. Hela .NET ramverket är organiserat i namespaces. Till exempel System.Console.WriteLine, där System är ett namespace, Console är en klass och Writeline är en metod. De kan även vara organiserade i hierarkier som till exempel System.IO där IO är ett namespace som finns i System namespace. Det finns inget krav på att använda namespace förutom när vi skapar komponenter, men det är alltid god praxis att använda det.

För att definiera ett namespace behöver vi bara ange ett namespace ovanför vår kod.

Exempel:
namespace nameOfNamespace
... vår kod ...

Det finns två olika sätt att definiera namespace det gamla sättet och det nya som introducerades i samband med lanseringen av .NET 6.0.

Före .NET 6.0
namespace nameOfNamespace{
	... vår kod ...
}

Från och med .NET 6.0
namespace nameOfNamespace;
... vår kod ...

Vad är syftet med namespace?

Som jag nämnde ovan så är det ett sätt att organisera vår kod, det är en anledning men det finns fler.

  • Kod i olika fysiska filer kan tillhöra samma namespace
  • Vi kan undvika problem med namn kollisioner
    • Vi kan återanvända samma namn på klasser/metoder så länge som de befinner sig i olika namespace
    • Underlättar utrullning av applikationer och kod
    • Förespråkar återanvändning
  • I stora system kan vi skapa hierarkier av namespaces

Använda kod ifrån andra namespace

För att använda kod som ligger i ett annat namespace än koden som vi skriver använder vi direktivet using.

Exempel:
using System;
using System.IO;
using Invoices.Models;

Klasser och Struct

Förutom de fördefinierade datatyperna som finns i .NET så kan vi skapa egna typer genom att använda oss av classes eller structs.

Structs

Structs är en värdetyp som vi kan använda för kapsla in data och beteende(funktionalitet). Vi använder struct nyckelordet struct för att definiera en struct typ.

public struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    public override string ToString() => $"{X} - {Y}";
    
}
Fokusera inte för mycket på koden som står inuti struct blocket, utan fokusera enbart på hur vi skapar en struct. Koden kommer vi att gå igenom i detalj när vi kommer dit.

Klasser

Klass(Class) är en referens typ som vi kan använda för att kapsla in data och beteende(funktionalitet). Vi använder class nyckelordet för att definiera en klass.

public class Person
{
    public string Name { get; }
    public int Age { get; }
    
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    
    public override string ToString() => $"{Name}, {Age}";
}
Fokusera inte för mycket på koden som står inuti class blocket, utan fokusera enbart på hur vi skapar en klass. Koden kommer vi att gå igenom i detalj när vi kommer dit. Vi kommer även att dyka ner på djupet med klasser när vi kommer till modulen Objekt Orienterad Programmering med C#.

Variabler

Vi har redan i ovanstående kod exempel sett hur vi kan deklarera variabler. Nu ska vi titta lite närmare på hur vi kan utnyttja C# för att underlätta deklaration av variabler och framförallt se vad variabler är och vad de används till. Vi ska också kika lite närmare på en förändring som skett i och med lanseringen av .NET 6.

Arbeta med variabler

Alla applikationer bearbetar data. Data kommer in och bearbetas och skickas tillbaka eller vidare på något sätt.

Data kan komma in till våra applikationer på flera olika sätt:

  • Ifrån databaser
  • Ifrån användare som matar in data i applikationerna
  • Ifrån filer som läses in

Det kan även vara data som vi enbart lagrar temporärt i minnet och när applikationen stängs ner så är datat förlorat. Normalt så används variabler för att lagra data som ska skickas vidare till en databas, en fil, skärmen eller till en skrivare.

Att tänka på vid användandet av variabler

När vi skapar variabler ska vi först och främst tänka på storleken på datatypen vi väljer för variabeln. Det vill säga hur mycket minne kommer den att behöva för att lagras. I andra hand hur snabbt variabeln kan bearbetas.

Hur kontrollerar vi detta?
Genom att välja en korrekt datatyp. Tänk att varje datatyp behöver en låda att lagras i och olika datatyper behöver olika stora lådor att lagras i.

Till exempel int och decimal är två olika datatyper som behöver olika stora lådor att lagras i. En int behöver 32 bytes för att lagras i minnet medan en decimal behöver 16 bytes för att lagras i minnet.

Att alltid välja en mindre typ är inte alltid det korrekta valet!

Ta till exempel en int igen. En int(32-bit) variabel behöver 32 bytes av minnet medans en long(64-bit) behöver 64 bytes. En int kanske inte blir lika snabbt bearbetad som en long på ett 64-bit operativ system.

Dessutom så kan vissa dessa lådor(datatyper) placeras intill varandra i minnet. Medans andra lådor kanske hamnar någonstans i heap arean.

Kom ihåg diskussionen angående värde typer respektive referens typer

Namngivning

För att underlätta namngivning av t ex variabler och metoder så används namngivnings standarder.

  • Camel case
    • name, age, dateOfBirth, vehicleName
    • Används för lokala och privata variabler
  • Pascal case
    • Name, Age, DateOfBirth, VehicleName
    • Används för typer, publika variabler och metodnamn
  • Kebab case
    • first-name, date-of-birth
    • Används inte speciellt ofta i C# utan är mer för webb applikationer och CSS.
Vi kommer att komma tillbaka till detta i modulen Objekt Orienterad Programmering med C#.

Deklarera lokala variabler

Vi pratat väldigt mycket om variabler, nu är det dags att se hur vi kan deklarera variabler. Vi har redan sett ett sätt i exemplen ovan.

Lokala variabler är variabler som är deklarerade inuti metoder, som vi ska prata om i nästa steg. Variabler deklarerade inuti metoder har en livslängd som innebär att de endast existerar under den tid som metoden exekveras. När metoden har exekverats så frigörs minnet som den har använt och alla variablers värde är borta.

För att vara helt korrekt, så betyder det att alla värde typer är frigjorda och minnet är städat. När det gäller referens typer så ligger de kvar tills .NET gör en Garbage Collection(GC).

Det finns två olika sätt att deklarera en variabel på i .NET

  • Genom att definiera vilken typ som variabeln ska ha
  • Genom att låta "runtime" bestämma vilken typ som variabeln ska ha genom att titta på värdet som tilldelas variabeln, inference.

Exempel 1: Explicit data typning

public void DemoMethod()
{
    int modelYear = 2020;
    string manufacturer = "Volvo";
    string model = "V60";
    double weight = 2.5; // i ton
}

Detta är lite onödigt i detta fall att ange typen när vi ändå anger initieringsvärdet. Ett bättre och enklare sätt är att använda inference och låta runtime titta på initieringsvärdena och sätta rätt typ baserat på det.

Exempel 2: Implicit(Inference) data typning

public void DemoMethod()
{
    var modelYear = 2020;
    var manufacturer = "Volvo";
    var model = "V60";
    var weight = 2.5; // i ton
}

Att tänka på! Om vi tilldelar en implicit deklarerad variabel ett tal utan decimaler så kommer den alltid att tolkas som ett heltal. Om vi skulle vilja att den tolkas som något annat måste ange ett suffix.

  • L: long
  • M: decimal
  • D: double
  • F: float

Metoder

Vi ska nu börja gå igenom metoder, hur vi skapar dem och hur vi anropar dem.

Metod Definition

En metod består av två delar

  • En specifikation(även kallat header eller interface). Som beskriver hur vi kan använda metoden.
  • En kropp(även kallat body eller implementering). Som beskriver vad metoden utför.

Definitionen på en metods specifikation är som följer:
[Qualifiers] [Return Type] Name ([Parameters/Arguments])

I vårt fall ovan:

  • Metodens namn är SayHello
  • Qualifier eller Accessor är static
    • static betyder att metoden är åtkomlig på klassnivå, utan att behöva skapa en objekt instans
  • Vi har en parameter/argument som är av typen sträng(string)
  • Return Type är i vårt fall en sträng, vilket betyder att vi kommer att returnera ett värde av typen sträng.

Metod Signaturer och överlagring

En metods signatur är metodens namn kombinerat med dess parametrar/argument

Så för att identifiera en metod som anropas så tittar exekverings motorn på signaturen, INTE enbart namnet.

Olika signaturer men med samma metodnamn innebär att det är olika metoder!

Låt oss ta ett exempel:

    int Calc(int x){ return x; }  
    int Calc(double x){ return x; }

Om vi nu anropar metoden på följande sätt: Calc(2.25)
Vilken av metoderna kommer att anropas?

Svaret: den som tar en datatyp av typen double som argument/parameter, vi skickar in ett decimal tal och då väljs den metod som kan ta emot ett flyttal.

Detta kallas för överlagring(overloading), något som vi kommer att gå in på i detalj i modulen Objekt Orienterad Programmering med C#.

Överlagring Gone Wild

Det är väldigt lätt att hamna i ett läge där vi har väldigt många överlagringar av en och samma metod.

Låt oss säga att vi ska skapa en kalkylator som har en metod Sum som behöver ta olika antal heltals parametrar.

int Sum(int number1, int number2){}
int Sum(int number1, int number2, int number3){}
int Sum(int number1, int number2, int number3, int number4){}
int Sum(int number1, int number2, int number3, int number4, int number5){}
int Sum(int number1, int number2, ...){}
int Sum(int number1, int number2, ...){}

Detta är inte snyggt!
Framförallt är det inte ett korrekt sätt att lösa probemet på.

Hur ska vi göra istället?

Dynamiska parametrar

Om vi har ett okänt antal parametrar som vi måste kunna hantera i våra metoder så har vi några olika sätt att lösa detta på. Vi ska se på två olika alternativ här.

Alternativ 1.
Vi kan använda en array enligt följande exempel

//Vi skapar en metod som tar en array av heltal som parameter...
int Sum(int[] numbers{}

//Vi kan sedan anropa metoden med en array som parameter...
var result = Sum(new int[]{1,2,34, 75,12,43});

Ovanstående kod fungerar visserligen, men är inte så snygg ur ett användarvänligt perspektiv. Finns det något bättre sätt då? Givetvis, genom att använda params nyckelordet som finns i C# språket.

Nyckelordet params definierar att parametern tar ett variabelt antal parametrar.

//Vi skapar en metod som tar en array av heltal som parameter
//genom att använda nyckelordet params innan typen av array...
int Sum(params int[] numbers{}

//Vi kan sedan anropa metoden med variabelt antal heltal...
var result = Sum(1,2,34, 75,12,43);

Mer om parameter definitioner

De två sista sakerna om parametrar som jag kommer att ta upp är något som en del utvecklare anser vara "code smell" eller dålig kod. Oavsett så kommer ni att stöta på dem framför allt i kod som ni utnyttjar ifrån .NET ramverket och det är nyckelorden ref och out.

Ref nyckelordet

ref nyckelordet används för att definiera att en värde typ variabel som skickas till en metod ska behandlas som en referens typ.

ref används i huvudsak i tre olika sammanhang

  1. I en metods signatur och i anropet för att skicka med parametern som en referens.
  2. I en metods signatur för att returnera ett värde som en referens.
  3. I en metods implementering för att indikera att ett retur värde av referenstyp är lagrad lokalt för att låta den anropande koden ändra dess värde.
För att använda en ref parameter så måste både metodens signatur och anropet explicit använda nyckelordet ref.
Observera att ref används i metoder där parametrar är av värde typ men behöver behandlas som referens typer.

Låt oss se på ett exempel.

int x = 10;

int DoStuff(int y, ref int i)
{
  i = 100;
  return y * 2;
}

var result = DoStuff(2, ref x);
Console.WriteLine("Result from return statement {0}", result);
Console.WriteLine("Result from methods ref parameter {0}", x);

Ett par saker att observera här:
metoden DoStuff's andra parameter ska skickas in som en referens, fastän det är en heltals parameter.

I metoden sätter vi parametern i till värdet 100!

När vi anropar metoden så observera att vi anger nyckelordet ref framför variabeln x. Detta betyder att vi skickar inte värdet på x utan adressen till x.

Om vi nu kör exemplet så bör vi få följande output:

Result from return statement 4
Result from methods ref parameter 100

Out nyckelordet

Nyckelordet out kan hjälpa oss komma över ett problem med att metoder enbart kan definiera ett returvärde.

Givetvis kan vi definiera en array som returvärde på en metod. Detta är en väldigt vanligt metod när vi får tillbaka listor av information ifrån en metod. Om vi bara behöver få tillbaka några få extra värden så är det lite "overkill" att skapa en array.

Vad vi behöver göra för att uppnå målet att kunna returnera flera värden ifrån en metod är att använda nyckelordet out framför alla parametrar som ska gå att läsa tillbaka.

Vad som händer när vi anger nyckelordet out är att oavsett datatyp så kommer parametern att hanteras som om den vore en referenstyp.

Låt oss se på ett exempel.

int x;

int DoStuff(int y, out int i)
{
  i = 100;
  return y * 2;
}

var result = DoStuff(2, out x);
Console.WriteLine("Result from return statement {0}", result);
Console.WriteLine("Result from methods out parameter {0}", x);

Ett par saker att observera här:
Vi initierar inte vår lokala variabel x, vi kommer att få ett värde på x ifrån metoden.

metoden DoStuff's andra parameter ska skickas in som en referens, fastän det är en heltals parameter. Vi gör detta genom att definiera parameter som out.

I metoden sätter vi parametern i till värdet 100!

När vi anropar metoden så observera att vi anger nyckelordet out framför variabeln x. Detta betyder att vi skickar inte värdet på x utan adressen till x.

Om vi nu kör exemplet så bör vi få följande output:

Result from return statement 4
Result from methods out parameter 100

Summering ref och out

Vad är egentligen skillnaden på ref och out? De åstadkommer i princip samma sak, det vill säga tillåter en metod att hantera en värde typ som en referens typ.
Det kan vara svår att se skillnaden, men

  • För att skicka med en variabel som ref, MÅSTE variabeln vara initierad, det vill säga att variabeln måste ha ett värde innan vi skickar med den till metoden
  • För att skicka med en variabel som out behöver inte variabeln vara initierad. Detta görs av metodens implementering

Valfria parametrar

När vi definierar metoder kan vi bestämma vilka parametrar ska vara obligatoriska eller valfria. Alla anrop måste skicka med alla parametrar som är obligatoriska men kan ignorera de som är valfria.

För att definiera en metod med valfria parametrar så måste varje parameter ges ett standard värde. Om ett värde inte skickas med så kommer det angivna standardvärdet att användas.

Ett standard värde måste vara något av följande:

  • Ett konstant uttryck
  • En värdetyp

Låt oss se på ett exempel

int DoStuff(int x, int y = 20)
{
  return x + y;
}

var result = DoStuff(2);
Console.WriteLine("Result from return statement {0}", result);

Observera att den 2:e parametern i DoStuff har ett standarvärde(20).
I anropet så ignoreras den parametern och standardvärdet används.

Resultatet blir då:

Result from return statement 22

Om vi istället skickar med ett värde.

var result = DoStuff(2, 10);
Console.WriteLine("Result from return statement {0}", result);

Resultatet blir då:

Result from return statement 12

Namngivna parametrar

Det sätt som vi hittills har skickat med parametrar till metoder på, är baserat på dess position i argumentlistan. Det är fullt möjligt att använda sig av namnen på parametrarna för att själv bestämma ordningen som man vill skicka in dem på.

Låt oss se ett exempel

void DoStuff(string name, double value, int mileage)
{
  Console.WriteLine("name: {0}, value: {1}, mileage: {2}",
    name, value, mileage);
}

DoStuff(mileage: 87500, value: 88000, name: "Renault Trafic");

Regler

Följande regler gäller för namngivna parametrar

  • Om vi börjar ange parametrar namngivet får vi inte avsluta med positionsangivna parametrar
  • Såvida de namngivna och positionsangivna parametrarna är på sina ursprunliga positioner
  • Vi kan ange parametrar med sina position och sedan använda de namngivna i vilken ordning vi vill

Exempel

void DoStuff(string name, double value, int mileage)

// Detta är ok
DoStuff("Renault Trafic", mileage: 97500, value: 87000);
// Detta är också ok
DoStuff(name: "Renault Trafic", 87000, 97500);
// Detta är inte ok
DoStuff(name: "Renault Trafic", 87000, value: 97500);
//Ger oss följande felmeddelande
error CS1744: Named argument 'value' specifies a parameter for which a positional argument has already been given

Felhantering

Definition

I .NET så sköts felhantering och felindikationer via en standardiserad mekanism Exceptions.

I .NET så finns det ett stort antal färdiga exceptions eller undantag(som vi utvecklare kallar det, vi skapar aldrig fel i vår kod, bara undantag ifrån normal exekvering😂).

Här är några exempel

  • System.OverflowException
  • System.ArithmeticException
  • System.IO.IOException
  • System.IO.FileNotFoundException
    Plus många, många fler

Gemensamt är att alla kommer ifrån en klass System.Exception som är definierad i bas biblioteket för .NET

Vi kan även skapa egna exceptions för saker och ting som kan inträffa i vår kod som ligger utanför vår kontroll.

Hantera fel i vår kod

Det är av största vikt att vi kan hantera exceptions/fel i vår kod på ett korrekt sätt.
Därför har vi tillgång till ett kod mönster just för detta try...catch...finally.

Här är den formella definitionen.

try{
}
catch(exceptiontyp-1 variabel namn){
    //Här hamnar vi om felet i catch är av typen exceptiontype-1
}
catch(exceptiontyp-2 variabel namn){
    //Här hamnar vi om felet i catch är av typen exceptiontype-2
}
finally{ //valfri
    // Här hamnar vi alltid oavsett om det gått bra eller dåligt
    // Här brukar vi lägga till kod för att eventuellt städa upp.
}

Kasta fel i vår kod

Att kasta ett fel uppåt i applikationen är ett rekommenderat sätt att hantera fel som inträffar som vi inte kan förutspå. För detta använder vi nyckelordet throw.

Exempel.

public void Deposit(decimal amount){
 if(amount <= 0) {
  throw new ArgumentException("Summan måste vara ett positivt värde");
 }
}

Hantera applikationsflöden

Alla applikationer behöver kunna hantera beslut så att exekveringen av applikationen kan fortsätta en vald väg eller avbryta. Dessutom behöver vi ofta i applikationer kunna gå igenom listor av data och bearbeta informationen i dessa listor. Det är vad vi ska gå igenom nu.

Fatta beslut och välja väg

Varje applikation måste kunna välja fortsatt exekvering baserat på olika alternativ och fortsätta exekveringen.

Välja väg med if uttryck

if uttrycket bestämmer vilken väg som ska följas genom att utvärdera ett sant eller falskt uttryck. Om uttrycket är sant så utförs koden i if blocket. Vi kan ha alternativa väger i ett if uttryck med hjälp av else blocket, detta är helt valfritt att använda. Else kommer att exekveras om if är falskt.

Vi kan även kombinera ett if uttryck med fler if block med else if uttryck som exemplet nedan visar.

if(uttryck_1)
{
    // Detta block körs i uttryck_1 är sant.
}
else if(uttryck_2)
{
    // Detta block körs i uttryck_1 är falskt och uttryck_2 är sant.
}
else if(uttryck_3)
{
    // Detta block körs i uttryck_1 är falskt och uttryck_2 är falskt
    // och uttryck_3 är sant.
}
else 
{
    // Detta block körs i fall alla andra uttryck är falska.
}
VIKTIGT!
Som ni ser så använder jag måsvingar för att kapsla varje block. Detta är valfritt om det endast finns ett uttryck i kod blocket. Men undvik denna teknik, använd alltid måsvingar för att kapsla uttryck. Om vi inte gör detta så kan vi införa allvarliga buggar i våra applikationer.
Tänk på att vi skriver kod för ramverket .NET(Core) och att det är plattformsoberoende. Det är inte säkert att koden kommer att fungera buggfritt om vi t ex kompilerar koden att köras på en iOS enhet.

Det finns ingen effektivitet eller prestanda att vinna på att utesluta måsvingarna.

Välj väg med switch uttrycket

Om vi har väldigt många kombinationer av else if så kan koden lätt bli svåröverskådlig. Ett bättre alternativ är att använda switch uttrycket.

switch jämför ett uttryck och jämför detta uttrycket med en lista av möjliga case utfall. Varje case är kopplat till det uttryck eller värde som switch jämför med.

Regler för case uttryck, varje case måste avslutas med:

  • break nyckelordet eller
  • goto case nyckelordet eller
  • inte ha något uttryck eller kod eller
  • goto nyckelordet som refererar en namngiven etikett eller slutligen
  • return som gör att exekveringen avbryts

Exempel:

int value = 10;

switch(value)
{
    case 1: // Om value är 1 hamnar vi här
        Console.WriteLine("Värdet är 1");
        break;
    case 2: // Om value är 2 hamnar vi här
        Console.WriteLine("Värdet är 2");
        break;
    case 3: // Detta är ett fallthrough uttryck
    case 4: // Endast tillåten om case 3 inte har någon kod.
        Console.WriteLine("Värdet är 3 eller 4");
        break;
    case 5:
        goto case 2; // Detta tar oss tillbaka till case 2.
    case 6:
        goto My_Label;
    default: // Här hamnar vi om inte någon av ovanstående fall fångar
             // upp värdet.
} // Slut på switch uttrycket

My_Label:
Console.WriteLine("Exekverar My_Label");

Arbeta med listor

Arbeta genom listor eller som vi brukar säga "iterera", innebär att ett block av kod exekveras tills ett villkor inte längre är sant. Eller tills det inte finns fler element i listan.

while loopen

while loopen utvärderar ett boeleskt värde (sant eller falskt) och fortsätter att exekvera så länge som värdet är sant.

Exempel:

int i = 0;

while(i < 5)
{
    Console.WriteLine(i);
    i++; // Glöm inte denna rad, annars går vi in i en oändlig loop.
}

do while loopen

Är väldigt lik while loopen med den skillnaden är kontrollen för fortsatt exekvering ligger sist i loopen. Detta för att garantera att loopen körs minst en gång.

Exempel:

string? password;

do 
{
    Console.WriteLine("Skriv in ditt lösenord");
    password = Console.ReadLine(); // ReadLine väntar på inmatning av 
                                   // användaren och på Enter.
}
while(password != "Password");
Console.WriteLine("Du är inloggad");

for loopen

for loopen är lite mer komplex än while loopen. Orsaken är att den tar lite fler argument

  • Ett initierings uttryck som körs första gången som loopen startar
  • Ett villkor som kontrollerar om loopen ska fortsätta
  • Ett uppräkningsuttryck som räknas upp varje gång som slutet på loopen nås

Exempel:

for(int i = 0; i <= 10; i++)
{
    Console.WriteLine(i);
}

Vad vi säger här är att börja på värdet 0 och för varje iteration räkna upp i med 1 och kontrollera så att i fortfarande är mindre eller lika med 10.

foreach loopen

foreach loopen är en aning annorlunda är de föregående looparna. Den är utformad för att exekvera kod för varje element i en lista, en array eller en collection. Vanligtvis är varje element skrivskyddat, vilket betyder att om vi försöker ändra något element under iterationens gång så får vi ett fel(exception).

Exempel:

string[] manufacturers = {"Volvo", "BMW", "Kia", "Ford"};

foreach(string manufacturer in manufacturers)
{
    Console.WriteLine(manufacturer);
}

Konvertera data mellan olika datatyper

Det är väldigt vanligt att vi måste omvandla ett värde ifrån en typ till en annan för fortsatt bearbetning. Det kan till exempel vara att vi får inmatat en sträng som sedan behöver behandlas som ett tal eller datum innan vi kan till exempel spara det till en databas. I C# använder vi begreppet "casting" när vi gör om ifrån en typ till en annan. Casting finns i två olika varianter, implicit och explicit.

Implicit betyder att omvandlingen sker automatiskt och är säker. Vi förlorar ingen information.

Explicit betyder att vi måste manuellt tvinga ett värde ifrån en typ till en annan. Här kan en risk finnas att vi förlorar exakthet i vår information. Till exempel göra om ett double värde till ett int värde.

Genom att använda explicit casting så säger vi till C# kompilatorn att vi vet vad vi gör och vi är medvetna om riskerna och är villiga att acceptera dem.

Exempel 1:
Detta är tillåtet(implicit casting)

int i = 10;
double x = i; 

resultat 10

En int är mindre än en double, så detta är helt ok att göra.
Mindre till större typ fungerar oftast utan någon åtgärd.

Exempel 2:
Detta är inte tillåtet

double i = 10.4;
int x = i; 

Här försöker vi att tvinga ett värde ifrån en större typ att få plats i en mindre typ(double till int).
Här måste vi använda explicit(tvingad) konvertering.

Exempel 3:

double i = 10.4;
int x = (int)i; 

resultat 10

Observera att vi sätter måltypen inom parenteser före värdet vi vill tvinga ner till en int.

Parenteserna kallas för cast operatorn

Konvertera med System.Convert

Ett annat alternativ än cast operatorn som vi kan använda System.Convert metoderna. Med hjälp av System.Convert metoderna kan vi konvertera till och från alla C# typerna, inklusive tal, boeleska, strängar och datum and tids värden.

Syntaxen är System.Convert.[Måltypen](värdet som ska omvandlas)

Exempel:

double i = 10.4;
int x = System.Convert.ToInt32(i);

resultat 10

Konvertera vad som helst till en sträng

Den absolut mest vanliga konvertering är att göra om värden till sträng representation. Den är så vanlig att .NET har byggt in en metod för detta i alla tillgängliga typer, metoden ToString().

Exempel:

int i = 10;
Console.WriteLine(i.ToString());
resultat 10

bool isActive = true;
Console.WriteLine(isActive.ToString());
resultat True

DateTime date = DateTime.Now;
Console.WriteLine(date.ToString());
resultat 2022-10-03 14:27:52

Konvertera strängar till tal eller datum

En annan väldigt vanligt omvandling är att omvandla en sträng till ett tal eller ett datum. För detta så finns det en Parse metod som inte alla men några typer har inbyggt.

Exempel 1:

int mileage = int.Parse("8740");
Console.WriteLine(mileage);

resultat 8740

Exempel 2:

DateTime registeredDate = DateTime.Parse("2018-01-10");
Console.WriteLine(registeredDate);

resultat 2018-01-10 00:00:00 // Tidsdelen är inte angiven därav midnatt.

Detta är ganska enkelt att arbeta med, men vad händer om vi i int.Parse() skickar in något som inte kan tolkas som heltal?

Exempel:

int mileage = int.Parse("Michael");
Console.WriteLine(mileage);

resultat Unhandled exception. System.FormatException: The input string 'Michael' was not in a correct format.

Undvik konverteringsfel

Hur kan vi undvika att få applikationen att krascha om vi skulle göra ovanstående misstag?

Genom att använda en annan parse metod, TryParse().
TryParse försöker att konvertera värdet och om det går bra så returnera metoden true annars false. För att kunna använda TryParse metoden så måste vi ange en variabel som skall innehålla det konverterade värdet med hjälp av out nyckelordet.

Exempel:

string mileage = "Michael";
int outputValue;

if (int.TryParse(mileage, out outputValue))
{
  Console.WriteLine("Det gick bra, kolla värdet outputValue");
}
else
{
  Console.WriteLine($"Det gick inte att konvertera värdet {mileage}");
}

Console.WriteLine(outputValue);

Dynamiska listor

När vi tidigare gick igenom hur vi kunde lagra linjär information av vilken datatyp som helst i typen array så upptäckte vi en stor nackdel med typen array och det var att när den väl är deklarerad så kunde vi inte utöka den med fler element eller minska ner storleken på den när vi tog bort något element. En array är låst till antalet element som den är deklarerad för.

Vad är då lösningen om vi behöver ha linjär lagring av information men vi vet inte hur många element som kommer att behöva lagras? Lösningen heter ArrayList.

ArrayList

ArrayList är en klass som vi kan hitta i namespace System.Collections och som dynamiskt kan ändra sin storlek för att anpassas efter fler eller färre element.

Vi kan lagra vilka typer som helst i en ArrayList.
Låt oss se på ett exempel

using System.Collections;
class Program
{
  public static void Main(string[] args)
  {
  	// Skapa en ny lista...
    ArrayList things = new ArrayList();
	
    // Lägg till lite element av olika datatyper
    things.Add("Michael");
    things.Add(DateTime.Today);
    things.Add(25);
    things.Add("Hej");

	// Anropa Display för att visa innehåll
    Display(things);
  }

  public static void Display(ArrayList list)
  {
  	// Nu kan vi använda oss av loopen foreach...
    foreach (var item in list)
    {
      Console.WriteLine("My thing {0}", item);
    }
  }
}

Problem med ArrayList

Tyvärr så lider ArrayList av ett problem och problemet är precis det som vi gör i ovanstående exempel. Vi lagrar olika typer i vår ArrayList, vilket kanske är precis vad vi vill. Problemet uppstår när vi måste gå igenom listan och plocka ut respektive objekt/element ur listan. I och med att det kan vara precis vad som helst som ligger i listan så måste vi veta vad respektive objekt är för typ för att kunna plocka ut det på ett korrekt sätt.

ArrayList är en gammal struktur för dynamiska listor som introducerades redan i det gamla .NET ramverket version 1.1.

Läser vi dokumentation angående ArraList så rekommenderar Microsoft INTE att vi använder ArrayList vid utveckling av nya applikationer utan istället använder oss av generiska listor.

Generiska Listor

I .NET och i C# kan vi bestämma vilken typ som vi vill kunna lagra i en lista genom att ange typen som ska lagras. Vi gör detta genom att använda ett begrepp som kallas för Generics i .NET.

Syntaxen för att skapa en generisk lista är:

List<T>

List  är motsvarigheten till ArrayList men med den stora skillnaden att List är typsäker , där <T> representerar typen som vi vill använda i listan.

List finns i namespace eller namnutrymmet System.Collection.Generic.

Låt oss titta på ett exempel:

using System.Collections.Generic;
public class Program
{
  private static void Main()
  {
    // Deklarera en variabel list som enbart kommer att 
    // innehålla strängar.
    var list = new List<String>();
    list.Add("Michael");
    list.Add("Eva");
    list.Add("Paula");
    list.Add("Bosse");

    foreach (var name in list)
    {
      Console.WriteLine(name);
    }
  }
}

Output resultat:

Michael
Eva
Paula
Bosse

Vad händer om vi försöker addera ett objekt som inte är av typen sträng?

using System.Collections.Generic;
public class Program
{
  private static void Main()
  {
    // Deklarera en variabel list som enbart kommer att 
    // innehålla strängar.
    var list = new List<String>();
    list.Add("Michael");
    list.Add("Eva");
    list.Add("Paula");
    list.Add("Bosse");
    list.Add(200);

    foreach (var name in list)
    {
      Console.WriteLine(name);
    }
  }
}

Output resultatet i det här fallet är ett felmeddelande:
error CS1503: Argument 1: cannot convert from 'int' to 'string'

Som tydligt indikerar att vi försöker placera ett heltalsvärde in en lista som endast tillåter strängar.

Detta var bara en liten introduktion till generics eller generiska typer. Vi kommer att se mer på detta när vi kommer in på webb utveckling. Där vi kommer att använda dem mer frekvent.

Avslutning

Detta blev tyvärr en väldigt lång artikel om .NET och C#, förhoppningsvis ger den en god inblick i .NET och en bra start i språket C# för Er.

I nästa modul ska vi kasta oss in i Objekt Orienterad Programmering med C#.