• No results found

Jämförelse av versionerna

4.3 Windowsprogrammet Hello Windows

4.3.4 Jämförelse av versionerna

Jämför man de tre versionerna av Hello Windows finns en del saker att lägga märke till. C#- och VB-versionerna är avsevärt kortare än Visual C++-versionen och har framför allt en mycket renare objektorienterad struktur. Visual C++-versionen är beroende av makron för meddelandehanteringen, CWinApp-klassen innehåller publika fält som måste användas och det behövs en global variabel. Dessutom kan det upplevas förvirrande att man inte enkelt ser var någonstans programkörningen börjar. Andra skillnader kan betraktas som mer ”kosmetiska”.

Måste Visual C++-versionen vara så komplicerad? Varför behövs strulet med meddelan- dehanteringsmakrona? Skulle inte OnPaint kunna vara en virtuell metod precis som den är i .NET-ramverket? Svaret på denna fråga är inte helt uppenbart. På sid 26–27 i [1] förklaras att antalet virtuella metoder då skulle bli så stort att det skulle krävas en virtual dispatch- tabell på ca 440 byte för varje klass ärvd från CWnd. Detta var nog ett större problem när MFC skapades i början av 1990-talet än vad det skulle vara idag, men det nämns också att

4.3. WindowsprogrammetHello Windows

även om meddelandehanteringen för Windowsmeddelandena skulle använda virtuella me- toder skulle det krävas något annat för att hantera meddelanden från t ex menykommandon. Grundproblemet är att språket C++ saknar en inbyggd mekanism för händelsehantering. Meddelandehanteringen med makron behöver inte i sig vara något problem, men det är svårt att förneka att den försämrar läsbarheten hos ett exempelprogram av det här slaget om läsaren är bekant med objektorientering men inte kommit i kontakt med Visual C++ tidigare.

C#-versionen har fördelen att man enkelt kan se var exekveringen börjar. Man får en ganska komplett bild av hur programmet fungerar, trots att mycket av grundfunktionaliteten är ärvd från basklassen Form. I VB-versionen är en del av detta dolt genom att Main saknas. Det framgår heller inte vilka namnrymder som används. Eftersom detta är sådant som man kan lägga till om man vill så det dock är svårt att betrakta detta som en nackdel hos VB i jämförelse med C#. Snarare är det en fördel att dessa element kan utelämnas och därigenom ge ett märkbart kortare program. Vilket av dessa två språk som är mest läsbart torde bero på programmerarens erfarenhet och preferenser.

Studeras enbart OnPaint-metoden kan man notera skillnader i hur koden får tillgång till CDC- (CPaintDC-) respektive Graphics-objektet och hur utskriften av texten går till. I Visual C++-versionen skapas CDC-objektet på stacken medan .NET-versionerna får sitt Graphics-objekt levererat inuti en parameter. I anropen till TextOut respektive DrawString ser man skillnaden att DrawString vill ha mer fullständig information om hur texten ska ritas; typsnitt och pensel måste skickas med. Motsvarande val i Visual C++-versionen skulle göras genom att anropa andra CDC-metoder före TextOut. Detta är en allmän skillnad mellan GDI och GDI+ och ligger bakom att GDI+ ibland kallas tillståndslöst (vilket dock bara gäller delvis eftersom annat som kan påverka textens utseende anges med separata metodanrop).

Kapitel 5

Allmän språkjämförelse

I detta kapitel görs en kortfattad genomgång av grundläggande funktionalitet som används i senare kapitel. Som angavs i avsnitt 1.5 förutsätts det att läsaren är bekant med C++ och beskrivningen är därför koncentrerad till det som skiljer sig i C# och VB.NET.

5.1

Primitiva typer

Alla språken har fördefinierade typer för heltal, flyttal, tecken och boolska värden. Tabell 5.1 listar de vanligaste av dessa och hur stora de är i antal bitar.

Tabell 5.1:De vanligaste primitiva typerna i de tre språken (antal bitar).

Visual C++

C#

VB.NET

.NET FCL

int/long (32) int (32) Integer (32) System.Int32 (32) __int64 (64) long (64) Long (64) System.Int64 (64) short (16) short (16) Short (16) System.Int16 (16) char (8) byte (8) Byte (8) System.Byte (8) wchar_t (16) char (16) Char (16) System.Char (16) float (32) float (32) Single (32) System.Single (32) double (64) double (64) Double (64) System.Double (64) bool (8) bool (8) Boolean (8) System.Boolean (8)

Här syns både skillnader och likheter. En long i Visual C++ är bara 32 bitar och för 64-bitars heltal används istället __int64 (som inte är standard-C++). (Observera att storlekssiffrorna för C++ gäller specifikt för Visual C++ (version 7.1/VS.NET 2003); i standard-C++ är inte storleken exakt definierad.) Datatypen char är 16 bitar i C# och VB.NET, vilket hänger samman med att all teckenhantering sker med Unicode-tecken.1

Tabellens fjärde kolumn listar också typernas namn i .NET-ramverket. C#:s och VB:s typer är bara smidigare sätt att komma åt dessa. Om man vill går det lika bra att använda .NET-namnen, vilket föredras i en del litteratur, t ex [5].

1Unicode kan användas även i C++, om datatypen char undviks. Typen wchar_t kan användas istället,

alternativt Windowstypen _TCHAR (som används av MFC). Den senare kan vara antingen 8 eller 16 bitar beroende på en inställning. Liknande gäller för strängar. Av läsbarhetsskäl används inte Unicode i C++ i denna rapport.

Utöver de typer som listas i tabellen finns ytterligare några. C# och VB har typen decimal/Decimal (System.Decimal) som är en 128 bitars decimaltyp främst avsedd för fi- nansiella beräkningar. Den ger bättre noggrannhet än double men är långsammare eftersom den är större och inte har stöd direkt i hårdvaran. Visual C++ och C# har även tecken- lösa heltalstyper, något som saknas i VB (men som kan användas genom .NET-typerna, System.UInt32 etc). Syntaxen skiljer sig här åt mellan C++ och C#; unsigned int i C++ heter kort och gott uint i C#.

I alla tre språken kan lokala variabler deklareras var som helst i koden. Hur variabler av de fördefinierade typerna deklareras och initialiseras framgår av följande exempel:

int a; // C++ och C#

double b = 3.14; bool c = true; Dim a As Integer ' VB.NET

Dim b As Double = 3.14, c As Boolean = True

Nyckelordet Dim användes i ursprungliga BASIC för att bestämma storleken på arrayer och har i VB fått en vidare användning. Här framgår också hur man i VB kan deklarera variabler av olika typ på samma rad. Eftersom en sats avslutas med radbrytning krävs en speciell syntax för detta.

VB.NET har kvar en del funktionalitet från tidigare versioner som inte alltid är önsk- värd. Det som sägs i den här rapporten gäller då inställningarna Option Strict On och Option Explicit On används. Dessa kan anges antingen i källkoden eller i projektinställ- ningarna i Visual Studio. Om Option Explicit sätts till Off behöver inte lokala variabler deklareras innan de används (som i ursprungliga BASIC där de inte kan deklareras). Detta kan göra korta kodsnuttar smidigare att skriva men leder lätt till svårfunna buggar för lite större kodavsnitt. Om Option Strict sätts till Off tillåts ”osäkra” implicita typomvandlingar, sen bindning (utan speciell syntax) m m.

5.2

Arrayer

Stödet för arrayer är annorlunda i C# och VB jämfört med C++. I .NET-språken är en ar- ray en egen datatyp som förutom arrayelementen också innehåller information om arrayens storlek. Detta gör det betydligt smidigare att skicka med arrayer som argument till metoder och att returnera nyskapade arrayer. Precis som andra objekt omfattas arrayer av den auto- matiska minneshanteringen och problemet om vem som ”äger” objektet och är ansvarig för att dess minne frigörs uppkommer inte. Följande är ett C#-exempel på en funktion (statisk metod) som tar två arrayer av heltal och returnerar en ny array med elementen från båda arrayerna placerade efter varandra.

static int[] CombineArrays(int[] first, int[] second)

{

int[] result = new int[first.Length + second.Length];

first.CopyTo(result, 0);

second.CopyTo(result, first.Length);

return result; } 1 2 3 4 5 6 7

C#

Arrayer deklareras i C# genom att hakparenteser placeras efter typen, inte efter variabel- namnet som i C++. Längden av den nya arrayen beräknas med hjälp av Length-egenskapen

5.3. Strängar

som alla arrayer ärver från FCL-klassen System.Array. Därifrån kommer också metoden CopyTo som här används för att kopiera innehållet i de båda ursprungliga arrayerna till den nya. Det andra argumentet anger från vilket index elementen ska placeras.

Motsvarande funktion i C++ skulle (vid användning av det inbyggda stödet för arrayer) dels behöva separata argument för att ange storleken av arrayerna, dels skulle den anropande koden behöva komma ihåg att frigöra minnet för den returnerade arrayen när den inte längre behövs (alternativt skicka med en redan allokerad array som ytterligare ett argument). Även kopieringen av arrayelementen skulle bli lite mer komplicerad.

Samma funktion i VB:

Shared Function CombineArrays(ByVal first As Integer(), _ ByVal second() As Integer) As Integer()

Dim result(first.Length + second.Length - 1) As Integer first.CopyTo(result, 0)

second.CopyTo(result, first.Length)

Return result End Function 1 2 3 4 5 6 7

VB

I VB anges arrayparametrar genom att vanliga parenteser placeras efter variabel- eller typ- namnet. För att illustrera detta används här den ena varianten för den första parametern och den andra för den andra. Returtypen anges efter parameterlistan och här är det nödvändigt att placera parenteserna efter typnamnet eftersom inget variabelnamn finns.

VB har en liten egenhet vid storleksbestämning av arrayer. Istället för att ange hur många element som arrayen ska innehålla anges det högsta indexet. Äldre versioner av BASIC (och även tidigare versioner av VB) använder 1 som första array-element och då blir detta likgiltigt, men i VB.NET indexeras arrayer med start från noll precis som i C- språken. Det här är förklaringen till det ”− 1” som finns i exemplet; högsta indexet för resultatarrayen blir summan av storlekarna av de båda inarrayerna minus ett. I VB används heller inte nyckelordet New för att allokera arrayer.

Förutom de endimensionella arrayer som har studerats här har C# och VB även äk- ta flerdimensionella arrayer med kommasyntax mellan (hak-) parenteserna (se kapitel 14 i [5] för detaljer kring dessa). Arrayer har den fördelen att de är typsäkra och effektiva, men nackdelen att de har en fast storlek. .NET:s klassbibliotek innehåller en rik uppsättning sam- lingsklasser (collections) som används när arrayer inte räcker till. Se kapitel 8 för exempel på hur några av dessa används.

5.3

Strängar

I C++ kan strängar hanteras antingen som nollavslutade arrayer av tecken eller med hjälp av en strängklass som string i standardbiblioteket eller MFC:s CString. I C# och VB används normalt aldrig teckenarrayer. Istället används de inbyggda strängtyperna (string/String). Dessa är både effektiva och lättanvända. De svarar mot klassen System.String i FCL och använder System.Char som datatyp för tecknen. Alla strängar i dessa språk kan således innehålla Unicode-tecken. System.String är en referenstyp (se avsnitt 5.4.1) med den spe- ciella egenskapen att dess objekt är oföränderliga (immutable). Detta innebär att innehållet i ett strängobjekt aldrig kan ändras. Operationer som logiskt sett ändrar strängen returnerar istället ett nytt objekt. Tillsammans innebär dessa båda egenskaper att man kan deklarera

lokala variabler och skicka strängar fram och tillbaka till metoder utan att riskera att göra bort sig.

För fler detaljer kring detta, se kapitel 12 i [5]. För exempel på hur strängar används se även kapitel 8 (i denna rapport).

5.4

Egendefinierade typer

Bortsett från de primitiva typerna och strängtyperna som har egna nyckelord har egendefi- nierade typer i C# och VB.NET samma status som de som ingår i FCL; CLR:en behandlar dem på exakt samma sätt. Det normala sättet att definiera en ny typ är med nyckelordet class/Class som redan framgått av tidigare exempel.

5.4.1 Referenstyper

Alla typer som definieras med class/Class blir referenstyper (reference types). För dessa gäller följande:

• Variabler av typen består av typsäkra referenser till det minnesområde där objektets data lagras.

• Nya objekt skapas med nyckelordet new/New. Undantaget är för strängar (och i VB för arrayer) där en alternativ syntax finns.

• Alla objekt omfattas av den automatiska minneshanteringen. De behöver inte frigöras manuellt och de kan inte frigöras manuellt. Den exakta tidpunkten då ett objekt som inte längre används frigörs är odefinierad.

Till skillnad från i C++ är det alltså inte möjligt att skapa objekt (av referenstyper) på stacken och hantera variabler som innehåller själva objektet. Istället beter sig alla objektvariabler som pekarvariabler i C++ (men tillåter inte pekararitmetik).

System.String och System.Array är referenstyper. 5.4.2 Värdetyper

Om alla typer vore referenstyper skulle prestandan bli lidande på grund av det höga antalet minnesallokeringar med åtföljande upprensningar som då skulle krävas. De primitiva ty- perna är därför så kallade värdetyper (value types) som fungerar annorlunda jämfört med referenstyper. Ett litet antal typer i FCL är värdetyper och det går följaktligen också att de- finiera egna (vilket görs med nyckelordet struct/Structure). För värdetyper gäller följande:

• Variabler av typen innehåller objektets data direkt.

• Lokala variabler av typen allokeras på stacken. Detta gäller alltid, oavsett om objektet skapas med new eller inte.

• Typen kan inte ärva från någon annan typ och inte användas som bastyp för vidare arv. (Alla värdetyper blir implict ärvda från System.ValueType.)

• När en variabel av en värdetyp behandlas som ett objekt av typen System.Object ska- pas en objektreferens till en kopia av variabelns innehåll. Detta kallas för inslagning (boxing). Den omvända processen kallas uppackning (unboxing).

5.4. Egendefinierade typer

Var någonstans ett objekts minne allokeras beror alltså på om objektet är en instans av en referens- eller en värdetyp (förutom då värdetypsobjekt ingår som fält i en referenstyp). Detta går inte som i C++ att påverka vid användningen av typen. Värdetyper är typiskt små, upp till något enstaka dussin byte, så att inte de kopieringar som sker när variabler skickas som argument till metoder påverkar prestandan. Inslagning/uppackning är en viktig funk- tion eftersom den gör det möjligt att använda värdetyper i samlingsklasserna och att hantera gränssnittsreferenser till värdetypsinstanser. Ingen speciell syntax behövs för inslagningen medan det vid uppackningen är nödvändigt med en typbestämning. Onödig inslagning bör undvikas eftersom den påverkar prestandan negativt.2

För en fullständig beskrivning av skillnaden mellan referens- och värdetyper, se kapitel 5 i [5].

Related documents