__MAIN_TEXT__
feature-image

Page 1

Programmering 2 Magnus Lilja

40696373_omslag.indd 1

C#

2017-06-30 08:27


Innehåll

Förord

2. Klasshierarkier

3

1. Grunder

6

1.1 Elementära datatyper 7 Literaler 8 Implicit typomvandling 9 Explicit typomvandling 9 1.2 Strängar 10 1.3 Operatorer 12 1.4 Styrstrukturer 13 Foreach-loopen 13 1.5 Samlingar 14 Värdetyper och referenstyper 14 Regelbunda fält 15 Oregelbundna fält 17 1.6 Objekt 17 1.7 Enumerationer 20 Flaggor 22 Bitoperatorer 23 1.8 Metoder 25 Överlagring 25 Frivilliga parametrar 26 Namnade parametrar 26 1.9 Klasser 27 Kopieringskonstruktor 28 Förenklad syntax 28 Statiska metoder 29 Statiska variabler 30 Klassdiagram 33 1.10 Typen dynamic 35 1.11 Undantagshantering 39 Strukturerad felhantering 41 Repetitionsfrågor 44

4

40696373_inlaga.indd 4

46

2.1 Arv 47 2.2 Polymorfism 51 is och as 52 virtual och override 52 virtual function table 53 2.3 Abstrakta klasser 55 Klassrelationer 56 2.4 Slutna klasser 58 2.5 Interface 58 Att designa en klasshierarki 60 2.6 Generiska klasser 65 Indexerare 68 2.7 IComparable* 69 2.8 Dokumentation 72 Repetitionsfrågor 75 Projekt 2 Ritboken 76

3. Fönster

78

3.1 3.2 3.3 3.4 3.5 3.6

Dialoger 79 MenuStrip 81 ContextMenuStrip 82 ToolTip 83 TabControl 84 RichTextBox 85 Teckensnitt 85 Textjustering 86 3.7 DataGridView 87 DataGridView i designläget 88 DataGridView för att visa data 90 DataGridView för att skriva in data 90 DataGridView för att läsa in data 90 Användbara egenskaper 90 Händelser 92 3.8 TreeView 95 TreeView i designläget 97 TreeView i kod 98 3.9 Layout 101 3.10 Fönsterkontroller i kod 104 3.11 Att göra egna kontroller 106 UserControl 107 Att ärva från en standardkontroll 108 Projekt 3 Den lilla ordbehandlaren 111 Repetitionsfrågor 113

2017-06-29 16:12


6. Databaser

114

4.1 4.2 4.3 4.4

Sökvägar 115 Fildialoger 116 Profiler 118 Textfiler 119 Läsa och skriva rad för rad 121 4.5 Binära filer 123 Projekt 4 CSV-filer 126 Repetitionsfrågor 127

5. Nätverk

128

5.1 Local Area Network (LAN) 129 5.2 Wide Area Network (WAN) 130 Accesspunkter 130 Internet 130 Hemnätverk 130 5.3 Protokoll 131 Det fysiska lagret 131 Datalänklagret 132 Nätverkslagret 132 Transportlagret 134 Portnummer 134 TCP 135 Applikationslagret 136 DNS 136 Brandväggar 136 5.4 Nätverksprogrammering 137 Payloads 137 5.5 UDP-programmering 138 5.6 TCP-programmering 140 Server och klient 140 5.7 Asynkron nätverksprogrammering 143 Projekt 5 Chatapplikation 148 Repetitionsfrågor 150

152

6.1 Relationsdatabaser 153 Nycklar 153 6.2 Datatyper 154 6.3 SQL 155 6.4 *Normalisering 161 Första normalformen 161 Andra normalformen 162 Tredje normalformen 162 Främmande nycklar 163 Referensintegritet 163 6.5 Databasdesign 163 ER-diagram 164 Implementering i tabeller 165 6.6 Implementera skoldatabasen 167 Tabellen Elever 167 Tabellen Böcker med automatiska id-nummer och en främmande nyckel 168 Tabellen Kurser 170 Tabellen Läser med två främmande nycklar 170 Tabellen Telefoner 172 6.7 Entity Data Model 172 Skapa EDM i Visual Studio 173 6.8 LINQ 176 Applikationens fönster 177 LINQ 179 Repetitionsfrågor 185

Innehåll

4. Filer

7. Facit till övningar och repetitionsfrågor 188 Register

224

5

40696373_inlaga.indd 5

2017-06-29 16:12


4

Filer

114

En fil är en följd av byte som lagras på hårddisken. Det finns två grundläggande typer av filer: textfiler och binära filer. I en textfil representerar alla byte teckenkoder. Binära filer består av vilka värden som helst. Textfiler kan läsas av vilket program som helst som kan öppna textfiler, till exempel Anteckningar i Windows. En vanlig filändelse på textfiler är .txt, men även html-filer och många andra filformat lagrar data i textform.

40696373_inlaga.indd 114

2017-06-29 16:13


4. Filer

Om du sparar en int och en double till en binär fil kommer den att innehålla 12 byte, fyra för heltalet och 8 för decimaltalet. För att ett program ska kunna läsa in en binär fil måste den veta exakt vad varje byte står för. 12 byte kan läsas in som en int och en double, eller som en double och en int, eller som tre int, eller sex char, och så vidare. Endast det första alternativet skulle återskapa samma värden som sparades till filen.

4.1 Sökvägar På hårddisken identifieras filen av en sökväg. En sökväg till dokumentet AxelsUppsats kan se ut så här: C:\Users\Axel\Mina Dokument\AxelsUppsats.docx

Första bokstaven är en enhet, oftast en lokal hårddisk, följt av kolon. Sedan kommer namnet på mapparna i sökvägen, åtskilda av bakvända snedstreck. Enligt exemplet ovan finns en mapp Users, som innehåller en mapp Axel, som innehåller en mapp Mina Dokument. I den sista mappen finns en fil som heter AxelsUppsats. Filnamnet följs av en filextension (filändelse), som talar om vilken typ av fil det är. I Windows syns inte filextensionerna som standard i utforskaren. De är mest intressanta för operativsystemet, som använder dem för att öppna filen med ett visst program. Du som användare ser vilken sort en fil är med hjälp av dess ikon. För en programmerare är det praktiskt att kunna se filextensionerna i utforskaren. Man kan ställa in detta i Mappalternativ, under fliken Visning. Klicka bort bocken framför ”Dölj filnamnstillägg för kända filtyper”.

115

40696373_inlaga.indd 115

2017-06-29 16:13


4. Filer

Sökvägar skrivs i källkod med dubbla bakvända snedstreck C:\\Users\\Axel\\Mina Dokument\\AxelsUppsats.docx

Det beror på att ett ensamt \ inleder en flyktsekvens, som till exempel \t (tabbtecken) eller \n (ny rad-tecken). Strängen "C:\temp" till exempel är inte sökvägen till en mapp som heter temp, utan en text med ett långt mellanrum mellan ”C:” och ”emp”.

4.2 Fildialoger I Windows Forms finns två komponenter som heter OpenFileDialog och SaveFileDialog. Man lägger till dem i sitt fönster på samma sätt som man lägger till en knapp eller en textruta. De visas dock inte i designvyn, men man ser att de finns i raden under designvyn.

116

Fildialogerna är egna fönster som kan visas eller döljas av huvudfönstret. Syftet med dem är att en användare ska kunna ange en sökväg genom att klicka på en fil istället för att skriva sökvägen manuellt. Så här ser en OpenFileDialog ut när den visas.

40696373_inlaga.indd 116

2017-06-29 16:13


Först och främst vill man kanske ändra namnet till dlgÖppnaFil eller dlgSparaFil. Några andra egenskaper som ofta är bra att utnyttja är DefaultExt, Filter och InitialDirectory. Den allra väsentligaste egenskapen är dock FileName, som innehåller sökvägen till en fil.

4. Filer

Fildialoger har som alla andra komponenter ett antal egenskaper som kan inspekteras och ändras i Properties-panelen.

117

DefaultExt är den filändelse som ska läggas till filnamnet, om användaren inte själv skriver en filändelse i dialogen när programmet kör. Filter är en sträng som beskriver vilka filer som ska visas av dialogen. I ett bildredigeringsprogram är det till exempel vanligt att fildialogerna bara visar filer med ändelserna jpg, bmp, png och andra bildformat. Särskilt när man öppnar en fil i ett bildredigeringsprogram kan det vara en fördel om användaren inte kan markera en mp3-fil, docx-fil eller något annat som inte är en bild. FileName är sökvägen till den fil som användaren väljer i dialogen, och den kan användas till att föreslå ett standardnamn när man sparar en ny fil. Det kan finnas anledning att ge ett värde till InitialDirectory. Ett vanligt scenario är att man vill att fildialogen automatiskt går till användarens Mina dokument. Exakt vilken mapp det är beror emellertid på vilken användare som är inloggad.

40696373_inlaga.indd 117

2017-06-29 16:13


4. Filer

När man har lagt till en fildialog och gett den namnet dlgÖppnaFil, kan man visa dialogen med anropet ShowDialog(). dlgÖppnaFil.ShowDialog()

Huvudfönstret pausar på den här satsen tills användaren klickar på knappen Öppna eller Avbryt i fildialogen. Anropet returnerar då ett värde som talar om vilken knapp användaren klickade på. Antag att du har lagt till en knapp btnÖppna i ditt huvudfönster. Då skulle man kunna visa fildialogen och hantera returvärdet så här: private void btnÖppna_Click( object sender, EventArgs e ) { DialogResult resultat = dlgÖppnaFil.ShowDialog(); if ( resultat == DialogResult.OK ) { // Användaren har valt en fil och klickat på // fildialogens Öppna-knapp. } }

Fildialogen har en egenskap som heter FileName. Om användaren har valt en fil och klickat på Öppna i fildialogen har FileName ett värde som är filens sökväg. Om användaren klickade på Avbryt returneras värdet DialogResult.Cancel, och då ska vårt program inte göra något.

4.3 Profiler

118

Ett operativsystem är ofta gjort så att flera personer kan logga in på datorn med olika konton. Till varje konto hör då en profil, som innebär att användaren kan ha sina egna inställningar, såsom bakgrundsbild på skrivbordet och egna genvägar på startmenyn. För varje profil finns en mapp, som på de senaste Windowsversionerna ligger i mappen C:\Användare, och i varje sådan profilmapp finns till exempel mappen Mina dokument. Det är ofta bekvämt för användaren av ett program som vill spara ett dokument, att fildialogen där man väljer filens namn och lagringsplats automatiskt föreslår just den användarens Mina dokument. Det finns en klass i .NET som heter Environment. Den innehåller egenskaper och metoder för att hämta information från operativsystemet. Om man till exempel vill veta sökvägen till Mina dokument för den användare som just nu är inloggad på Windows, kan man anropa GetFolderPath. // Ta reda på sökvägen till den inloggade användarens Mina dokument-mapp string anvMapp = Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments ); SpecialFolder är en enumeration som även innhåller värdena MyMusic, MyPictures, UserProfile, Desktop, Programs och Fonts, för att bara nämna

några.

40696373_inlaga.indd 118

2017-06-29 16:13


4. Filer

4.4 Textfiler Textfiler är filer som bara innehåller teckenkoder. Man skulle kunna säga, att en textfil är en enda lång string som finns på datorns hårddisk istället för i arbetsminnet. Innehållet i filen hämtas med en ström. En ström i programmeringssammanhang är en följd av byte-värden. Det finns en färdig klass för filströmmar som heter FileStream. Byte-följden måste omvandlas till char-värden som kan bli till en sträng. För detta finns en färdig klass som heter StreamReader. När man öppnar en filström måste man ange om man vill skriva till den (kopiera data från arbetsminnet till hårddisken) eller om man vill läsa från den (kopiera data från hårddisken till arbetsminnet).

Exempel 4.1 Att läsa från och spara till en textfil private void btnÖppna_Click( object sender, EventArgs e ) { DialogResult resultat = dlgÖppnaFil.ShowDialog(); if ( resultat == DialogResult.OK ) { // Öppna en ström från filen som valdes i dialogen dlgÖppnaFil. FileStream inStröm = new FileStream( dlgÖppnaFil.FileName, FileMode.Open, FileAccess.Read ); // Skapa en läsare som läser byten i strömmen som char-värden. StreamReader läsare = new StreamReader( inStröm ); // Skapa en sträng som får filinnehållet från läsaren: string filText = läsare.ReadToEnd(); // Visa strängen i en textruta: tbxDokument.Text = filText; // Stäng filen: Läsare.Dispose(); }

119

private void btnSpara_Click( object sender, EventArgs e ) { // Visa en dialogruta där man kan ange ett filnamn. DialogResult resultat = dlgSparaFil.ShowDialog(); // Om användaren klickade på Öppna (inte Avbryt) if ( resultat == DialogResult.OK ) { // Öppna en ström till filen som valdes i dialogen dlgSparaFil: FileStream utStröm = new FileStream( dlgSparaFil.FileName, FileMode.OpenOrCreate, FileAccess.Write );

f 40696373_inlaga.indd 119

2017-06-29 16:13


4. Filer

// Skapa en skrivare som omvandlar texten till råa bytes. StreamWriter skrivare = new StreamWriter( utStröm ); // Skriv texten i fönstrets textruta till filen: skrivare.Write( tbxDokument.Text ); // Stäng filen, frigör resurser. skrivare.Dispose(); } } ReadToEnd() läser hela filen på en gång. Man kan välja att läsa en rad i taget med ReadLine() genom att göra det i en loop som itererar ända tills ReadLine() retur-

nerar null (vilket betyder att slutet på filen nåtts).

Övning 4.1 Läs en bok Skriv ett program som kan öppna och spara en txt-fil med hjälp av en meny som heter ”Arkiv”. Låt programmet fungera så som man är van vid. Det vill säga, användaren behöver bara ge filen ett namn om det är ett helt nytt dokument, eller om han uttryckligen väljer ”Spara som…” Lägg också till en funktion som gör att man kan byta ut alla förekomster av ett visst ord i texten mot ett annat ord. Från den svenska sajten Projekt Runeberg eller den engelska Project Gutenberg kan man ladda ner gamla böcker i txt-format. Hämta filen ”Bibeln.txt” eller någon annan större text-fil och prova att öppna den med ditt program.

120

40696373_inlaga.indd 120

2017-06-29 16:13


Om filens text innehåller radbrytningar går det att läsa in filen en rad i taget enligt följande.

4. Filer

Läsa och skriva rad för rad

string rad = läsare.ReadLine(); while ( rad != null ) { // Gör något med raden, t.ex. visar den i fönstret. rad = läsare.ReadLine();

// Läser fram till nästa radbrytning.

}

När man öppnar en fil skapas en filpekare som pekar på den allra första byten. När sedan ReadLine läser en del av filen flyttas filpekaren till den första byte som inte redan lästs. Nästa gång man anropar ReadLine hämtas byten från den plats där förra läsningen stannade. När man sparar data till en textfil är det ofta fördelaktigt att spara varje värde på en ny rad, eftersom det blir mycket lättare att läsa in värdena var för sig.

Exempel 4.2 Skriva och läsa rad för rad Sparar två variabler string förnamn = "Oskar"; string efternamn = "Ek"; skrivare.WriteLine( förnamn ); skrivare.WriteLine( efternamn );

Återskapar variablerna string förnamn = läsare.ReadLine(); string efternamn = läsare.ReadLine();

121 Övning 4.2 Spara och läsa en kontakt Skriv ett program som har ett formulär för att fylla i kontaktuppgifter. Man ska kunna spara personens uppgifter till en fil. Man ska också kunna öppna en fil och se innehållet i formuläret. I och med att filen har en bestämd struktur, men fyra rader där första raden är ett förnamn och så vidare, har vi i praktiken definierat ett filformat. Bestäm dig för en filändelse (till exempel *.kon) som programmet ska använda för sina filer.

40696373_inlaga.indd 121

2017-06-29 16:13


4. Filer

Exempel 4.3 Spara och öppna listor Antag att ett program har en hel kontaktlista med kontakter, där varje kontakt har de fyra strängarna förnamn, efternamn, epostadress och telefonnummer. Koden nedan visar hur man skulle kunna spara innehållet i en sådan lista, där skrivare är en StreamWriter enligt tidigare och Kontakt är en klass. foreach ( Kontakt k in kontakter ) { skrivare.WriteLine( k.Förnamn ); skrivare.WriteLine( k.Efternamn ); skrivare.WriteLine( k.Epost ); skrivare.WriteLine( k.TelefonNr ); }

Koden för att återskapa kontaktlistan från en fil skulle kunna se ut så här, där läsare är en StreamReader. string förnamn = läsare.ReadLine(); while ( förnamn != null ) { kontakter.Add( new Kontakt() { Förnamn = förnamn, Efternamn = läsare.ReadLine(), Epost = läsare.ReadLine(), TelefonNr = läsare.ReadLine() } ); förnamn = läsare.ReadLine(); }

Övning 4.3 Hantera en kontaktlista

122

Skriv ett program där man kan skapa flera kontakter och lägga till dem i en lista. Hela listan ska kunna sparas till en textfil, och man ska kunna öppna samma textfil igen. När man markerar ett namn i listrutan ska formuläret visa den personens uppgifter. Givetvis ska det finnas en klass Kontakt som lagrar uppgifterna som medlemsvariabler.

40696373_inlaga.indd 122

2017-06-29 16:13


4. Filer

4.5 Binära filer En binär fil lagrar värden som de är. Det vill säga, när man skriver heltalet 523 till en binär fil lagras det som en 4 byte stor int, precis som i arbetsminnet. Man kan spara 523 till en textfil också, men det lagras som tre teckenkoder, vilket blir 6 byte i Unicode. Binära filer kan därför ofta bli mindre, och det går snabbare att skriva och läsa till dem, särskilt om inga ToString() eller Parse()-anrop behövs. Det är dock viktigt att man vet precis vilken sorts värden som man lagrat och i vilken ordning, så att man kan läsa in filen igen och återskapa värdena i arbetsminnet. Detta innebär i praktiken att man definierar sitt eget filformat. Filformatet säger till exempel att först kommer en int, sedan en double, sedan något annat, och så vidare. När man lagrar en sträng lagras först ett heltal som säger hur många tecken strängen innehåller, sedan lagras så många teckenkoder. Antag att vi ska spara information om en ingrediens till ett recept. För ingrediensen ska det finnas en beskrivande text, ett tal för mängden och en text för måttet. Det blir ungefär som att spara en kontakt, men nu gör vi det med en BinaryWriter istället för en StreamWriter. Inläsning sker följaktligen med en BinaryReader.

Exempel 4.4 Spara/Öppna en binär fil. private void btnSpara_Click( object sender, EventArgs e ) { // Visa en dialogruta där man kan ange ett filnamn. DialogResult resultat = dlgSparaFil.ShowDialog(); // Om användaren klickade på Öppna (inte Avbryt) if ( resultat == DialogResult.OK ) { // Öppna en ström till filen som valdes i dialogen dlgSparaFil: FileStream utStröm = new FileStream( dlgSparaFil.FileName, FileMode.OpenOrCreate, FileAccess.Write ); // Skapa en skrivare som omvandlar värden till råa bytes. BinaryWriter skrivare = new BinaryWriter( utStröm ); // Skriv några värden till filen: skrivare.Write( "Mjöl" ); // Skriv 4 följt av 4 teckenkoder (4+8 byte) skrivare.Write( 3.5f ); // Skriv en float (4 byte) skrivare.Write( "dl" ) // Skriv 2 följt av 2 teckenkoder (4+4 byte) // Stäng filen, frigör resurser. skrivare.Dispose(); } }

40696373_inlaga.indd 123

123

f

2017-06-29 16:13


4. Filer

private void btnÖppna_Click( object sender, EventArgs e ) { DialogResult resultat = dlgÖppnaFil.ShowDialog(); if ( resultat == DialogResult.OK ) { // Öppna en ström från filen som valdes i dialogen dlgÖppnaFil. FileStream inStröm = new FileStream( dlgÖppnaFil.FileName, FileMode.Open, FileAccess.Read ); // Skapa en läsare som läser byten i strömmen till olika variabler. BinaryReader läsare = new BinaryReader( inStröm ); // Återskapa värdena från filen: string ingrediens = läsare.ReadString(); // Läs en sträng float mängd = läsare.ReadSingle(); // Läs en float string enhet = läsare.ReadString(); // Läs en sträng // Gör något med variablerna... }

Filformatet skulle kunna beskrivas med int, char[], double, int, char[]. Heltalen behövs när man sparar en sträng. Det första värdet är nämligen antalet tecken i strängen. Det skulle kunna illustreras så här, byte för byte: 0

0

0

4

int antalet tecken som följer

0

77

0

106

0

246

0

4 teckenkoder I det här fallet M, j, ö och l

108

64

96

0

float

0

0

0

0

2

int antalet tecken som följer

0

100

0

108

2 teckenkoder I det här fallet d och l

Egentligen sparas värden i omvänd byte-ordning på Windows-datorer, enligt ett system som kallas Little-Endian. Det betyder att ett värde i de fyra byten ABCD sparas i ordningen DCBA i filen. Men det är en teknikalitet som BinaryWriter tar hand om.

124

Man kan också lägga märke till att en teckenkod för vanliga bokstäver (i västerländska alfabet) egentligen inte kräver mer än 1 byte, eftersom alla teckenkoder är under 255. Det finns en teckenkodning som heter UTF8, som bara använder 1 byte (8 bitar), för varje tecken. Om man sparade strängarna med den teckenkodningen skulle filen bli mindre. Det är viktigt att värdena läses in i samma ordning som de skrevs, alltså enligt filformatet. Man läser en float med metoden ReadSingle. Man kan ju tycka att metoden skulle heta ReadFloat, men Single är ett annat namn på Float. Det är logiskt, om man tänker på att ett flyttal med dubbel precision heter double. Det finns också ReadInt, ReadDouble och så vidare.

40696373_inlaga.indd 124

2017-06-29 16:13


Skriv ett program där man kan skriva in ett recept. Varje ingrediens ska ha ett namn, en mängd och en måttenhet. Det är lämpligt att skapa en klass som heter Ingrediens. Filformatet kan vara som i exemplet, men man skulle kunna spara ett heltal för antalet ingredienser allra först, för att underlätta läsningen av filen. Filändelsen kan vara till exempel .rec. Filändelsen kan ställas in med egenskaperna Filter och/eller DefaultExt i fildialogerna, eller så får man skriva den själv när man anger filnamnet i Spara-fönstret.

4. Filer

Övning 4.4 Receptprogrammet

Ingredienserna visas i en listruta eller datagridvy, och man ska kunna spara receptet till en binär fil. Filen ska kunna öppnas och visas i fönstret igen. Ingrediens

−namn :string −mängd :double −mått :string +Namn :string +Mängd :double +Mått :string +ToString () :string

125

40696373_inlaga.indd 125

2017-06-29 16:13


4. Filer

Projekt 4 CSV-filer Excel sparar som standard i ett filformat som har ändelen xlsx. Det går dock att spara filer från Excel i en rad andra format, till exempel Comma Separated Values, som har ändelsen csv. Om man öppnar en csv-fil i Anteckningar, ser man att varje rad i Exceldokumentet blir en textrad i csv-filen. Värdena från enkilda celler åtkiljs med kommatecken (eller på ett svenskt operativsystem med semikolon).

Testa själv!

Csv-formatet sparar bara cellvärdena, inga teckenformat, kantlinjeformat, funktioner eller annat som en xlsx-fil kan innehålla. Men många program och internetsajter, bland annat SCB:s statistikdatabas, kan exportera data i csv-format. Det öppnar för möjligheten att enkelt importera data till ett eget program. Man skriver helt enkelt in kod för att öppna csv-filer och ”sortera in” alla värden i sina egna variabler, eller visa dem i någon kontroll. Skriv ett program som kan öppna och visa data från csv-filer i en DataGridView. För enkelhets skull bör csv-filen bara innehålla en tabell och inte ha värden utspridda i enskilda celler. Lägg även till en Spara-funktion för att spara i formatet csv, så att du kan redigera filen i ditt program.

126

40696373_inlaga.indd 126

2017-06-29 16:13


 4.1

På en dator finns det en mapp Gustav i en mapp Användare på hårddisken C. Ange sökvägen i en sträng:

 4.2

I mappen ovan finns filen MinUppsats.pdf.

4. Filer

Repetitionsfrågor

a) Vad är filändelsen? b) Vad är filnamnet?  4.3

Du sparar texten ”Mjölk 4,5 dl” i en textfil. Hur stor blir filen i antal byte a) med teckenkodningen Unicode? b) med teckenkodningen UTF8?

 4.4

Du sparar strängen ”Mjölk”, flyttalet 4,5 (en double) och heltalet 8 i en binär fil, i ordningen string-double-int. Strängarna sparas med teckenkodningen UTF8. a) Hur stor blir filen i antal byte? b) I vilken ordning måste filen läsas in?

127

40696373_inlaga.indd 127

2017-06-29 16:13

Profile for Smakprov Media AB

9789140696373  

9789140696373  

Profile for smakprov

Recommendations could not be loaded

Recommendations could not be loaded

Recommendations could not be loaded

Recommendations could not be loaded