Skip to content

3. De basis van de taal C#

3.1. Introduction

We behandelen C# eerst als een klassieke programmeertaal. We zullen later ingaan op klassen. In een programma komen twee dingen voor:

  • gegevens
  • de instructies die deze gegevens bewerken

Meestal proberen we de gegevens en de instructies van elkaar te scheiden:

3.2. De gegevens in C#

C# maakt gebruik van de volgende gegevenstypen:

  1. gehele getallen
  1. reële getallen
  2. decimale getallen
  3. tekens en tekenreeksen
  4. booleaanse waarden
  5. objecten

3.2.1. Vooraf gedefinieerde gegevenstypen

C#-type
Type .NET
Weergegeven gegevens
Achtervoegsel
van de letterlijke waarden
Codering
Waardenbereik
char
Char (S)
teken
 
2 bytes
Unicode-teken (UTF-16)
string
String (C)
tekenreeks
  
verwijzing naar een reeks Unicode-tekens
int
Int32 (S)
geheel getal
 
4 bytes
[-231, 231-1] [–2147483648, 2147483647]
uint
UInt32 (S)
..
U
4 bytes
[0, 232-1] [0, 4294967295]
long
Int64 (S)
..
L
8 bytes
[-263, 263 -1] [–9223372036854775808, 9223372036854775807]
ulong
UInt64 (S)
..
UL
8 bytes
[0, 264 -1] [0, 18446744073709551615]
sbyte
 
..
 
1 byte
[-27 , 27 -1] [-128,+127]
byte
Byte(s)
..
 
1 byte
[0 , 28 -1] [0,255]
short
Int16 (S)
..
 
2 bytes
[-215, 215-1] [-32768, 32767]
ushort
UInt16 (S)
..
 
2 bytes
[0, 216-1] [0,65535]
float
Enkel (S)
reëel getal
F
4 bytes
[1.5 10-45, 3.4 10+38] als absolute waarde
double
Dubbel (S)
..
D
8 bytes
[-1.7 10+308, 1.7 10+308] in absolute waarde
decimal
Decimaal (S)
decimaal getal
M
16 bytes
[1.0 10-28,7.9 10+28] als absolute waarde met 28 significante cijfers
bool
Boolean (S)
..
 
1 byte
true, false
object
Object (C)
objectreferentie
  
objectreferentie

Hierboven zijn de C#-typen naast hun equivalente .NET-type geplaatst, met de opmerking (S) als dit type een structuur is en (C) als het type een klasse is. We zien dat er twee mogelijke typen zijn voor een 32-bits geheel getal: int en Int32. Het type int is een C#-type. Int32 is een structuur die behoort tot de naamruimte System. De volledige naam is dus System.Int32. Het type int is een C#-alias die verwijst naar de structuur .NET System.Int32. Evenzo is het C#-type string een alias voor het type .NET System.String. System.String is een klasse en geen structuur. Beide begrippen lijken op elkaar, maar er is een fundamenteel verschil:

  • een variabele van het type Structure wordt gemanipuleerd via haar waarde
  • een variabele van het type Classe wordt bewerkt via haar adres (referentie in objectgeoriënteerde programmeertaal).

Zowel een structure als een classe zijn complexe typen met attributen en methoden. Zo kunnen we schrijven:

string nomDuType=3.GetType().FullName;

Hierboven is de letterlijke waarde 3 standaard van het C#-type int, dus van het type .NET System.Int32. Deze structuur heeft een methode GetType() die een object retourneert waarin de kenmerken van het gegevenstype System.Int32 zijn ingekapseld. Een van deze kenmerken is de eigenschap FullName, die de volledige naam van het type retourneert. We zien dus dat de letterlijke waarde 3 een complexer object is dan het op het eerste gezicht lijkt.

Hier volgt een programma dat deze verschillende punten illustreert:


using System;

namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            // voorbeeld 1
            int ent = 2;
            float fl = 10.5F;
            double d = -4.6;
            string s = "essai";
            uint ui = 5;
            long l = 1000;
            ulong ul = 1001;
            byte octet = 5;
            short sh = -4;
            ushort ush = 10;
            decimal dec = 10.67M;
            bool b = true;
            Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
            Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
            Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
            Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
            Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
            Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
            Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
            Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
            Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
            Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
        }
    }
}
  • regel 7: declaratie van een geheel getal ent
  • regel 19: het type van een variabele v kan worden verkregen via v.GetType().FullName. De grootte van een structuur S kan worden verkregen via sizeof(S). De instructie Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) geeft de tekst die de eerste parameter is weer op het scherm, waarbij elke notatie {i} wordt vervangen door de waarde van de uitdrukking parami.
  • regel 22: aangezien het type string een klasse aanduidt en geen structuur, kan de operator sizeof niet worden gebruikt.

Dit is het resultaat van de uitvoering:

Type de ent[2] : [System.Int32,4]
Type de fl[10,5]: [System.Single,4]
Type de d[-4,6] : [System.Double,8]
Type de s[essai] : [System.String]
Type de ui[5] : [System.UInt32,4]
Type de l[1000] : [System.Int64,8]
Type de ul[1001] : [System.UInt64,8]
Type de b[5] : [System.Byte,1]
Type de sh[-4] : [System.Int16,2]
Type de ush[10] : [System.UInt16,2]
Type de dec[10,67] : [System.Decimal,16]
Type de b[True] : [System.Boolean,1]

De uitvoer toont de typen .NET en niet de C#-aliassen.

3.2.2. Notatie van letterlijke gegevens

entier int (32 bits)
145, -7, 0xFF (hexadecimaal)
entier long (64 bits) - suffixe L
100000L
réel double
134,789, -45E-18 (-45 × 10⁻¹⁸)
réel float (suffixe F)
134.789F, -45E-18F (-45 10-18)
réel decimal (suffixe M)
100000M
caractère char
'A', 'b'
chaîne de caractères string
"vandaag" "c:\\hoofdstuk1\\alinea3" @"c:\hoofdstuk1\alinea3"
booléen bool
true, false
date
new DateTime(1954,10,13) (jaar, maand, dag) voor 13/10/1954

Let op de twee letterlijke tekenreeksen: "c:\\chap1\\paragraph3" en @"c:\chap1\paragraph3". In letterlijke tekenreeksen wordt het teken \ geïnterpreteerd. Zo staat "\n" voor het einde-van-regel-teken en niet voor de opeenvolging van de twee tekens \ en n. Als men deze opeenvolging zou willen, zou men "\\n" moeten schrijven, waarbij de reeks \\ wordt geïnterpreteerd als één enkel teken \. Men zou ook @"\n" kunnen schrijven om hetzelfde resultaat te krijgen. De syntaxis @"tekst" vereist dat tekst precies zo wordt overgenomen als hij is geschreven. Dit wordt soms een verbatim-string genoemd.

3.2.3. Gegevensdefinitie

3.2.3.1. De rol van declaraties

Een programma verwerkt gegevens die worden gekenmerkt door een naam en een type. Deze gegevens worden in het geheugen opgeslagen. Bij het compileren van het programma wijst de compiler aan elk gegeven een geheugenlocatie toe die wordt gekenmerkt door een adres en een grootte. Dit doet hij aan de hand van de declaraties die door de programmeur zijn gemaakt.

Bovendien stellen deze de compiler in staat om programmeerfouten op te sporen. Zo is de bewerking

x=x*2;

als foutief worden aangemerkt als x bijvoorbeeld een tekenreeks is.

3.2.3.2. Verklaring van constanten

De syntaxis voor het declareren van een constante is als volgt:

    const type nom=valeur;          //definieert constante naam=waarde

Bijvoorbeeld:

const float myPI=3.141592F;    

Waarom constanten declareren?

  1. Het programma is gemakkelijker te lezen als de constante een betekenisvolle naam krijgt:
    const  float taux_tva=0.186F;
  1. Het aanpassen van het programma gaat gemakkelijker als de „constante” moet worden gewijzigd. In het voorgaande voorbeeld geldt dus dat, als het btw-tarief naar 33% gaat, de enige aanpassing die nodig is het wijzigen van de instructie is die de waarde ervan definieert:
    const float taux_tva=0.33F;

Als we 0,186 expliciet in het programma hadden gebruikt, zouden er talrijke instructies moeten worden aangepast.

3.2.3.3. Variabelen declareren

Een variabele wordt aangeduid met een naam en heeft betrekking op een gegevenstype. C# maakt onderscheid tussen hoofdletters en kleine letters. De variabelen FIN en fin zijn dus verschillend.

Variabelen kunnen bij hun declaratie worden geïnitialiseerd. De syntaxis voor het declareren van één of meerdere variabelen is:

 Identificateur_de_type variable1[=valeur1],variable2=[valeur2],...;

waarbij Identificateur_de_type een vooraf gedefinieerd type is of een door de programmeur gedefinieerd type. Optioneel kan een variabele tegelijk met de declaratie worden geïnitialiseerd.

Het is ook mogelijk om het exacte type van een variabele niet te specificeren door het sleutelwoord var te gebruiken in plaats van Identificateur_de_type:

 var variable1=valeur1,variable2=valeur2,...;

Het sleutelwoord var betekent niet dat de variabelen geen specifiek type hebben. De variabele variablei heeft het type van de gegevens valeuri die eraan zijn toegewezen. Initialisatie is hier verplicht, zodat de compiler het type van de variabele kan afleiden.

Hier volgt een voorbeeld:


using System;

namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            int i=2;
            Console.WriteLine("Type de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
            var j = 3;
            Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
            var aujourdhui = DateTime.Now;
            Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
        }
    }
}
  • regel 6: een expliciet getypeerde waarde
  • regel 7: (data).GetType().Name is de korte naam van (data), (data).GetType().FullName is de volledige naam van (data)
  • regel 8: een impliciet getypeerde waarde. Omdat 3 van het type int is, zal j van het type int zijn.
  • regel 10: een impliciet getypeerde waarde. Omdat DateTime.Now van het type DateTime is, zal aujourdhui van het type DateTime zijn.

Bij uitvoering krijgt men het volgende resultaat:

1
2
3
Type de int i=2 : Int32,System.Int32
Type de var j=3 : Int32,System.Int32
Type de var aujourdhui : DateTime,System.DateTime

Een variabele die impliciet het type var heeft gekregen, kan daarna niet van type veranderen. Het is dus niet mogelijk om na regel 10 van de code de volgende regel te schrijven:


            var aujourdhui = "aujourd'hui";

We zullen later zien dat het mogelijk is om een type „on-the-fly” in een uitdrukking te declareren. Dit is dan een anoniem type, een type waaraan de gebruiker geen naam heeft gegeven. Het is de compiler die dit nieuwe type een naam zal geven. Als een waarde van een anoniem type aan een variabele moet worden toegewezen, is de enige manier om deze te declareren het gebruik van het sleutelwoord var.

3.2.4. Conversies tussen getallen en tekenreeksen

nombre -> chaîne
nombre.ToString()
chaine -> int
int.Parse(string) of System.Int32.Parse
chaîne -> long
long.Parse(string) of System.Int64.Parse
chaîne -> double
double.Parse (reeks) of System.Double.Parse (reeks)
chaîne -> float
float.Parse (tekenreeks) of System.Float.Parse (tekenreeks)

De conversie van een tekenreeks naar een getal kan mislukken als de tekenreeks geen geldig getal vertegenwoordigt. Er wordt dan een fatale fout gegenereerd, een zogenaamde uitzondering. Deze fout kan worden afgehandeld met de volgende clausule try/catch:


try{
    appel de la fonction susceptible de générer l'exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

Als de functie geen uitzondering genereert, gaan we door naar de volgende instructie; anders gaan we naar de hoofdtekst van de clausule catch en vervolgens naar de volgende instructie. We zullen later terugkomen op het beheer van uitzonderingen. Hier volgt een programma waarin enkele technieken voor het omzetten tussen getallen en tekenreeksen worden getoond. In dit voorbeeld geeft de functie affiche de waarde van haar parameter weer op het scherm. Zo geeft affiche(S) de waarde van S weer op het scherm, waarbij S van het type string is.


using System;

namespace Chap1 {
    class P01 {
        static void Main(string[] args) {

            // gegevens
            const int i = 10;
            const long l = 100000;
            const float f = 45.78F;
            double d = -14.98;

            // getal --> tekenreeks
            affiche(i.ToString());
            affiche(l.ToString());
            affiche(f.ToString());
            affiche(d.ToString());

            //booleaanse waarde --> tekenreeks
            const bool b = false;
            affiche(b.ToString());

            // tekenreeks --> int
            int i1;
            i1 = int.Parse("10");
            affiche(i1.ToString());
            try {
                i1 = int.Parse("10.67");
                affiche(i1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }

            // tekenreeks --> long
            long l1;
            l1 = long.Parse("100");
            affiche(l1.ToString());
            try {
                l1 = long.Parse("10.675");
                affiche(l1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }

            // tekenreeks --> double
            double d1;
            d1 = double.Parse("100,87");
            affiche(d1.ToString());
            try {
                d1 = double.Parse("abcd");
                affiche(d1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }

            // tekenreeks --> float
            float f1;
            f1 = float.Parse("100,87");
            affiche(f1.ToString());
            try {
                d1 = float.Parse("abcd");
                affiche(f1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }

        }// einde main

        public static void affiche(string S) {
            Console.Out.WriteLine("S={0}",S);
        }
    }// einde klasse
}

In de regels 30-32 wordt de eventuele uitzondering afgehandeld die kan optreden. e.Message is de foutmelding die verband houdt met de uitzondering e.

De verkregen resultaten zijn als volgt:

S=10
S=100000
S=45,78
S=-14,98
S=False
S=10
S=Erreur : Input string was not in a correct format.
S=100
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.

Let op: bij reële getallen in de vorm van een tekenreeks moet een komma worden gebruikt in plaats van een decimaalteken. We schrijven dus

double d1=10.7; 

maar

double d2=int.Parse("10,7");

3.2.5. Gegevenstabellen

Een C#-array is een object waarmee gegevens van hetzelfde type onder één identificatie kunnen worden gebundeld. De declaratie ervan is als volgt:

Type[] tableau[]=new Type[n]

n is het aantal gegevens dat de array kan bevatten. De syntaxis Array[i] verwijst naar het gegeven met nummer i, waarbij i tot het interval [0,n-1] behoort. Elke verwijzing naar het gegevenselement Tableau[i] waarbij i niet tot het interval [0,n-1] behoort, zal een uitzondering veroorzaken. Een array kan tegelijk met de declaratie worden geïnitialiseerd:

    int[] entiers=new int[] {0,10,20,30};

of eenvoudiger:

    int[] entiers={0,10,20,30};

Arrays hebben een eigenschap Length, die het aantal elementen van de array aangeeft.

Een tweedimensionale array kan als volgt worden gedeclareerd:

Type[,] array = new Type[n,m];

waarbij n het aantal rijen is en m het aantal kolommen. De syntaxis Tableau[i,j] verwijst naar element j in rij i van de array. De tweedimensionale array kan ook tegelijk met de declaratie worden geïnitialiseerd:

    double[,] réels=new double[,] { {0.5, 1.7}, {8.4, -6}};

of eenvoudiger:

    double[,] réels={ {0.5, 1.7}, {8.4, -6}};

Het aantal elementen in elk van de dimensies kan worden verkregen met de methode GetLength(i), waarbij i=0 staat voor de dimensie die overeenkomt met de eerste index, i=1 voor de dimensie die overeenkomt met de tweede index, …

Het totale aantal dimensies wordt verkregen met de eigenschap Rank, het totale aantal elementen met de eigenschap Length.

Een array van arrays wordt als volgt gedeclareerd:

Type[][] array = new Type[n][];

De bovenstaande declaratie creëert een array met n rijen. Elk element array[i] is een eendimensionale array-referentie. Deze array-referenties array[i] worden bij de bovenstaande declaratie niet geïnitialiseerd. Ze hebben de waarde null.

Het onderstaande voorbeeld illustreert het aanmaken van een array van arrays:


            // een array van arrays
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
            // initialisatie
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//for j
            }//for i
  • regel 2: een array noms met 3 elementen van het type string[][]. Elk element is een array-pointer (een objectreferentie) waarvan de elementen van het type string[] zijn.
  • regels 3-5: de 3 elementen van de array noms worden geïnitialiseerd. Elk element „verwijst“ nu naar een array met elementen van het type string[]. noms[i][j] is het element j van de array van het type string [] waarnaar wordt verwezen door noms[i].
  • regel 9: initialisatie van het element namen[i][j] binnen een dubbele lus. Hier is `noms[i]` een array met `i+1` elementen. Aangezien `noms[i]` een array is, is `noms[i].Length` het aantal elementen ervan.

Hier volgt een voorbeeld waarin de drie soorten arrays die we zojuist hebben besproken, worden gecombineerd:


using System;

namespace Chap1 {
    // arrays

    using System;

    // testklasse
    public class P02 {
        public static void Main() {
            // een geïnitialiseerde eendimensionale array
            int[] entiers = new int[] { 0, 10, 20, 30 };
            for (int i = 0; i < entiers.Length; i++) {
                Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
            }//voor

            // een geïnitialiseerde tweedimensionale array
            double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
            for (int i = 0; i < réels.GetLength(0); i++) {
                for (int j = 0; j < réels.GetLength(1); j++) {
                    Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
                }//for j
            }//for i

            // een array van arrays
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//voor
            // initialisatie
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//voor j
            }//voor i
            // weergave
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
                }//voor j
            }//voor i
        }//Hoofdpagina
    }//klasse
}//naamruimte

Bij uitvoering krijgen we de volgende resultaten:

entiers[0]=0
entiers[1]=10
entiers[2]=20
entiers[3]=30
réels[0,0]=0,5
réels[0,1]=1,7
réels[1,0]=8,4
réels[1,1]=-6
noms[0][0]=nom00
noms[1][0]=nom10
noms[1][1]=nom11
noms[2][0]=nom20
noms[2][1]=nom21
noms[2][2]=nom22

3.3. De basisinstructies van C#

We onderscheiden

1 de basisinstructies die door de computer worden uitgevoerd.

2 de instructies die de uitvoering van het programma sturen.

De basisinstructies worden duidelijk wanneer men de structuur van een microcomputer en zijn randapparatuur bekijkt.

  1. het inlezen van informatie via het toetsenbord

  2. verwerking van informatie

  3. informatie naar het scherm schrijven

3.3.1. Schrijven naar het scherm

Er zijn verschillende instructies om informatie op het scherm weer te geven:

Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)

waarbij expression elk gegevenstype is dat kan worden omgezet in een tekenreeks om op het scherm te worden weergegeven. Alle objecten in C# of .NET beschikken over een methode ToString() die wordt gebruikt om deze conversie uit te voeren.

De klasse System.Console biedt toegang tot schermschrijfbewerkingen (Write, WriteLine). De klasse Console heeft twee eigenschappen, Out en Error, die schrijfstromen van het type TextWriter zijn:

  • Console.WriteLine() is gelijk aan Console.Out.WriteLine() en schrijft naar de Out-stream die doorgaans aan het scherm is gekoppeld.
  • Console.Error.WriteLine() schrijft naar de Error-stream, die doorgaans ook aan het scherm is gekoppeld.

De streams Out en Error kunnen tijdens de uitvoering van het programma naar tekstbestanden worden omgeleid, zoals we straks zullen zien.

3.3.2. Gegevens van het toetsenbord lezen

De gegevensstroom afkomstig van het toetsenbord wordt aangeduid door het object Console.In van het type TextReader. Met dit type objecten kan een tekstregel worden gelezen met de methode ReadLine:

    string ligne=Console.In.ReadLine();

De klasse Console biedt een methode ReadLine die standaard is gekoppeld aan de stroom In. We kunnen dus het volgende schrijven:

    string ligne=Console.ReadLine();

De via het toetsenbord ingevoerde regel wordt opgeslagen in de variabele ligne en kan vervolgens door het programma worden verwerkt. De In-stream kan, net als de Out- en Error-streams, naar een bestand worden omgeleid.

3.3.3. Voorbeeld van invoer en uitvoer

Hier volgt een kort programma ter illustratie van invoer- en uitvoerbewerkingen via toetsenbord en scherm:


using System;

namespace Chap1 {
    // testklasse
    public class P03 {
        public static void Main() {

            // schrijven naar de Out-stream
            object obj = new object();
            Console.Out.WriteLine(obj);

            // schrijven naar de Error-stream
            int i = 10;
            Console.Error.WriteLine("i=" + i);

            // een via het toetsenbord ingevoerde regel lezen
            Console.Write("Tapez une ligne : ");
            string ligne = Console.ReadLine();
            Console.WriteLine("ligne={0}", ligne);
        }//einde main
    }//einde klasse
}
  • regel 9: obj is een objectreferentie
  • regel 10: obj wordt op het scherm weergegeven. Standaard wordt de methode obj.ToString() aangeroepen.
  • regel 14: men kan ook schrijven:

            Console.Error.WriteLine("i={0}",i);

De eerste parameter "i={0}" is het weergaveformaat, de overige parameters zijn de uit te geven uitdrukkingen. De elementen {n} zijn "positionele" parameters. Bij uitvoering wordt de parameter {n} vervangen door de waarde van uitdrukking nr. n.

Het resultaat van de uitvoering is als volgt:

1
2
3
4
System.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
  • regel 1: de weergave die wordt gegenereerd door regel 10 van de code. De methode obj.ToString() heeft de typenaam van de variabele obj weergegeven: System.Object. Het type object is een C#-alias voor het type .NET System.Object.

3.3.4. Omleiding van I/O

Onder DOS en UNIX bestaan drie standaardapparaten met de namen:

  1. standaard invoerapparaat – verwijst standaard naar het toetsenbord en heeft nummer 0
  2. standaarduitvoerapparaat – verwijst standaard naar het scherm en heeft nummer 1
  3. standaardfoutapparaat – verwijst standaard naar het scherm en heeft nummer 2

In C# schrijft de schrijfstream Console.Out naar apparaat 1, schrijft de schrijfstream Console.Error naar apparaat 2 en leest de leesstream Console.In gegevens van apparaat 0.

Wanneer je een programma onder DOS of Unix start, kun je instellen welke apparaten 0, 1 en 2 zijn voor het uitgevoerde programma. Laten we de volgende opdrachtregel eens bekijken:

pg arg1 arg2 .. argn

Achter de argumenten argi van het programma pg kunnen de standaard I/O-apparaten naar bestanden worden omgeleid:

0<in.txt
de standaardinvoerstroom nr. 0 wordt omgeleid naar het bestand in.txt. In het programma haalt de stroom Console.In zijn gegevens dus uit het bestand in.txt.
1>out.txt
leidt uitvoer nr. 1 om naar het bestand out.txt. Dit heeft tot gevolg dat in het programma de stroom Console.Out zijn gegevens naar het bestand out.txt schrijft
1>>out.txt
idem, maar de geschreven gegevens worden toegevoegd aan de huidige inhoud van het bestand out.txt.
2>error.txt
leidt uitgang nr. 2 om naar het bestand error.txt. Dit heeft tot gevolg dat de stroom Console.Error in het programma zijn gegevens naar het bestand error.txt zal schrijven
2>>error.txt
Hetzelfde, maar de geschreven gegevens worden toegevoegd aan de huidige inhoud van het bestand error.txt.
1>out.txt 2>error.txt
De apparaten 1 en 2 worden beide omgeleid naar bestanden

Merk op dat het programma pg de I/O-stromen naar bestanden kan omleiden zonder dat het programma pg hoeft te worden aangepast. Het besturingssysteem bepaalt de aard van de apparaten 0, 1 en 2. Laten we het volgende programma eens bekijken:


using System;

namespace Chap1 {

    // omleidingen
    public class P04 {
        public static void Main(string[] args) {
            // In-stream lezen
            string data = Console.In.ReadLine();
            // Out-stream schrijven
            Console.Out.WriteLine("écriture dans flux Out : " + data);
            // schrijven van foutstroom
            Console.Error.WriteLine("écriture dans flux Error : " + data);
        }//Main
    }//klasse
}

Laten we het uitvoerbare bestand van deze broncode genereren:

  • in [1]: het uitvoerbare bestand wordt aangemaakt door met de rechtermuisknop op het project te klikken / Build
  • in [2]: in een DOS-venster is het uitvoerbare bestand 04.exe aangemaakt in de map bin/Release van het project.

Voer de volgende opdrachten uit in het DOS-venster [2]:

1
2
3
4
5
6
7
8
...\04\bin\Release>echo test >in.txt
...\04\bin\Release>more in.txt
test
...\04\bin\Release>04 0<in.txt 1>out.txt 2>err.txt
...\04\bin\Release>more out.txt
écriture dans flux Out : test
...\04\bin\Release>more err.txt
écriture dans flux Error : test
  • regel 1: we plaatsen de tekenreeks test in het bestand in.txt
  • regels 2-3: we geven de inhoud van het bestand in.txt weer ter controle
  • regel 4: het programma 04.exe wordt uitgevoerd. De stream In wordt omgeleid naar het bestand in.txt, de stroom Out naar het bestand out.txt, de stroom Error naar het bestand err.txt. De uitvoering leidt niet tot een weergave.
  • regels 5-6: inhoud van het bestand out.txt. Uit deze inhoud blijkt dat:
  • het bestand in.txt is gelezen
  • de schermuitvoer is omgeleid naar out.txt
  • regels 7-8: soortgelijke controle voor het bestand err.txt

Het is duidelijk te zien dat de stromen Out en In niet naar dezelfde apparaten schrijven, aangezien we ze afzonderlijk konden omleiden.

3.3.5. Toewijzing van de waarde van een uitdrukking aan een variabele

We richten ons hier op de bewerking variable=expression;

De uitdrukking kan van het volgende type zijn: rekenkundig, relationeel, booleaans, tekenreeks

3.3.5.1. Interpretatie van de toewijzingsbewerking

De bewerking variable=expression;

is zelf een uitdrukking waarvan de evaluatie als volgt verloopt:

  • de rechterkant van de toewijzing wordt geëvalueerd: het resultaat is een waarde V.
  • de waarde V wordt toegewezen aan de variabele
  • de waarde V is tevens de waarde van de toewijzing, ditmaal beschouwd als een uitdrukking.

Zo wordt de bewerking

    V1=V2=expression

geldig is. Vanwege de prioriteit wordt de operator = die het meest rechts staat, geëvalueerd. We hebben dus

    V1=(V2=expression)

De uitdrukking V2=uitdrukking wordt geëvalueerd en heeft als waarde V. De evaluatie van deze uitdrukking heeft ertoe geleid dat V is toegewezen aan V2. Het volgende =-teken wordt vervolgens geëvalueerd in de vorm:

    V1=V

De waarde van deze uitdrukking is nog steeds V. De evaluatie ervan leidt tot de toewijzing van V aan V1.

De bewerking V1=V2=uitdrukking

is dus een uitdrukking waarvan de evaluatie

  • er voor zorgt dat de waarde van expression wordt toegewezen aan de variabelen V1 en V2
  • geeft als resultaat de waarde expression.

Dit kan worden veralgemeend tot een uitdrukking van het type:

V1=V2=....=Vn=expression

3.3.5.2. Rekenkundige uitdrukking

De operatoren voor rekenuitdrukkingen zijn de volgende:

  • optelling

  • aftrekking

* vermenigvuldiging

/ deling: het resultaat is het exacte quotiënt als ten minste één van de operanden reëel is. Als beide operanden gehele getallen zijn, is het resultaat het gehele quotiënt. Dus 5/2 -> 2 en 5,0/2 -> 2,5.

% deling: het resultaat is de rest, ongeacht de aard van de operanden, waarbij het quotiënt een geheel getal is. Dit is dus de modulo-bewerking.

Er bestaan diverse wiskundige functies. Hier volgen er enkele:

double Sqrt(double x)
vierkantswortel
double Cos(double x)
Cosinus
double Sin(double x)
Sinus
double Tan(double x)
Tangens
double Pow(double x,double y)
x tot de macht y (x>0)
double Exp(double x)
Exponentieel
double Log(double x)
Natuurlogaritme
double Abs(double x)
absolute waarde

enz...

Al deze functies zijn gedefinieerd in een C#-klasse met de naam Math. Wanneer je ze gebruikt, moet je ze vooraf laten gaan door de naam van de klasse waarin ze zijn gedefinieerd. Je schrijft dus:

double x, y=4;
x=Math.Sqrt(y);

De volledige definitie van de klasse Math is als volgt:

Image

Image

Image

Image

Image

3.3.5.3. Prioriteiten bij het evalueren van rekenkundige uitdrukkingen

De prioriteit van de operatoren bij het berekenen van een rekenuitdrukking is als volgt (van hoogste naar laagste prioriteit):

[fonctions], [ ( )],[ *, /, %], [+, -]

Operatoren binnen hetzelfde blok [ ] hebben dezelfde prioriteit.

3.3.5.4. Relationele uitdrukkingen

De operatoren zijn als volgt:

       <, <=, ==, !=, >, >=

Prioriteiten van de operatoren

>, >=, <, <=
==, !=

Het resultaat van een relationele uitdrukking is de booleaanse waarde false als de uitdrukking onwaar is, en true in het tegenovergestelde geval.

      bool fin;
      int x=...;
      fin=x>4;

Vergelijking van twee tekens

Stel dat we twee tekens hebben: C1 en C2. Deze kunnen worden vergeleken met de operatoren

    <, <=, ==, !=, >, >=

Het zijn dan hun Unicode-codes, die uit getallen bestaan, die met elkaar worden vergeleken. Volgens de Unicode-volgorde gelden de volgende relaties:

espace < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. <'z'

Vergelijking van twee tekenreeksen

Ze worden teken voor teken vergeleken. De eerste ongelijkheid die tussen twee tekens wordt aangetroffen, leidt tot een ongelijkheid in dezelfde richting voor de tekenreeksen.

Voorbeelden:

Stel dat we de tekenreeksen "Kat" en "Hond" vergelijken

Op basis van dit laatste verschil kunnen we stellen dat "Chat" < "Chien".

Stel dat we de tekenreeksen "Chat" en "Chaton" vergelijken. Er is telkens gelijkheid totdat de tekenreeks "Chat" is uitgeput. In dit geval wordt de uitgeputte tekenreeks als de "kleinste" aangemerkt. We hebben dus de relatie

    "Chat" < "Chaton".

Vergelijkingsfuncties voor twee tekenreeksen

Je kunt de relationele operatoren == en != gebruiken om te controleren of twee tekenreeksen gelijk zijn of niet, of de methode Equals van de klasse System.String. Voor de relaties < <= > >= moet de methode CompareTo van de klasse System.String worden gebruikt:


using System;

namespace Chap1 {
    class P05 {
        static void Main(string[] args) {
            string chaine1="chat", chaine2="chien";
            int n = chaine1.CompareTo(chaine2);
            bool egal = chaine1.Equals(chaine2);
            Console.WriteLine("i={0}, egal={1}", n, egal);
            Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
        }
    }
}

Op regel 7 krijgt de variabele i de waarde:

0 als de twee tekenreeksen gelijk zijn

1 als tekenreeks nr. 1 > tekenreeks nr. 2

-1 als tekenreeks nr. 1 < tekenreeks nr. 2

Regel 8: de variabele egal krijgt de waarde true als de twee tekenreeksen gelijk zijn, en false in het tegenovergestelde geval. Regel 10: we gebruiken de operatoren == en != om te controleren of twee tekenreeksen gelijk zijn of niet.

De resultaten van de uitvoering:

i=-1, egal=False
chien==chaine1:False,chien!=chaine2:False

3.3.5.5. Booleaanse uitdrukkingen

De beschikbare operatoren zijn AND (&&) OR (||) NOT (!). Het resultaat van een booleaanse uitdrukking is een booleaanse waarde.

Prioriteiten van operatoren:

  1. !
  2. &&
  3. ||

            double x = 3.5;
            bool valide = x > 2 && x < 4;

Relationele operatoren hebben voorrang op de operatoren && en ||.

3.3.5.6. Bitverwerking

De operatoren

Stel dat i en j twee gehele getallen zijn.

i<<n
verschuift i n bits naar links. De binnkomende bits zijn nullen.
i>>n
verschuift i n bits naar rechts. Als i een geheel getal met teken is (signed char, int, long), blijft het tekenbit behouden.
i & j
voert de bit-voor-bit logische OR-bewerking uit tussen i en j (ET).
i | j
voert de bit-voor-bit logische OR-bewerking uit tussen i en j (OU).
~i
maakt i complementair tot 1
i^j
voert de OU EXCLUSIF uit op i en j

Stel dat de volgende code geldt:


short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i,j,k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4},k>>4=0x{2:x4},i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
  • het formaat {0:x4} geeft parameter nr. 0 weer in hexadecimale notatie (x) met 4 tekens (4).

De resultaten van de uitvoering zijn als volgt:

i=0x0064, j=0xfff3, k=0xf123
i<<4=0x0640, i>>4=0x0006,k>>4=0x0f12,i&j=0x0060,i|j=0xfff7,~i=0xff9b,j<<2=0xffcc,j>>2=0xfffc

3.3.5.7. Combinatie van operatoren

a=a+b kan worden geschreven als a+=b

a=a-b kan worden geschreven als a-=b

Hetzelfde geldt voor de operatoren /, %, *, <<, >>, &, |, ^. Zo kan a=a/2; worden geschreven als a/=2;

3.3.5.8. Inkrementatie- en dekrementatieoperatoren

De notatie variable++ betekent variable=variable+1 of ook variable+=1

De notatie variable-- betekent variable=variable-1 of ook variable-=1.

3.3.5.9. De ternaire operator ?

De uitdrukking

    expr_cond ? expr1:expr2

wordt als volgt berekend:

1 de uitdrukking expr_cond wordt geëvalueerd. Dit is een voorwaardelijke uitdrukking met de waarde vrai of faux

2 Als deze waar is, is de waarde van de uitdrukking die van expr1 en wordt expr2 niet geëvalueerd.

3 Als de voorwaarde onwaar is, gebeurt het omgekeerde: de waarde van de uitdrukking is die van expr2 en expr1 wordt niet berekend.

De bewerking i=(j>4 ? j+1:j-1); wijst aan de variabele i de waarde j+1 toe als j>4, en anders j-1. Dit is hetzelfde als if(j>4) i=j+1; else i=j-1; schrijven, maar het is beknopter.

3.3.5.10. Algemene prioriteit van operatoren

() []  fonction                  
gd
! ~ ++ --                        
dg
new (type) opérateurs cast       
dg
*  /  %                          
gd
+  -                             
gd
<<  >>                           
gd
< <=  > >= instanceof            
gd
==    !=                         
gd
&                                
gd
^                                
gd
|                                
gd
&&                               
gd
||                               
gd
?   :                            
dg
= += -= etc. .                   
dg

gd geeft aan dat bij gelijke prioriteit de volgorde van links naar rechts wordt gevolgd. Dit betekent dat wanneer er in een uitdrukking operatoren met dezelfde prioriteit voorkomen, de operator die zich het meest links in de uitdrukking bevindt, als eerste wordt geëvalueerd. dg geeft een volgorde van rechts naar links aan.

3.3.5.11. Typeomzettingen

Het is mogelijk om in een uitdrukking de codering van een waarde tijdelijk te wijzigen. Dit wordt het wijzigen van het type van een waarde genoemd, of in het Engels ‘type casting’. De syntaxis voor het wijzigen van het type van een waarde in een uitdrukking is als volgt:

    (type) valeur

De waarde krijgt dan het aangegeven type. Dit leidt tot een wijziging in de codering van de waarde.


using System;

namespace Chap1 {
    class P06 {
        static void Main(string[] args) {
            int i = 3, j = 4;
            float f1=i/j;
            float f2=(float)i/j;
            Console.WriteLine("f1={0}, f2={1}",f1,f2);
        }
    }
}
  • regel 7: f1 krijgt de waarde 0,0. De deling 3/4 is een gehele deling, aangezien beide operanden van het type int zijn.
  • regel 8: (float)i is de waarde van i, omgezet naar float. Nu hebben we een deling tussen een reëel getal van het type float en een geheel getal van het type int. Vervolgens wordt de deling tussen reële getallen uitgevoerd. De waarde van j wordt eveneens omgezet naar het type float, waarna de deling van de twee reële getallen wordt uitgevoerd. f2 krijgt dan de waarde 0,75.

Dit zijn de resultaten van de uitvoering:

f1=0, f2=0,75

In de bewerking (float)i:

  • i is een exact gecodeerde waarde van 2 bytes
  • (float) i is dezelfde waarde, bij benadering gecodeerd als een reëel getal over 4 bytes

Er vindt dus een transcodering plaats van de waarde van i. Deze transcodering vindt slechts plaats tijdens één berekening; de variabele i behoudt altijd het type int.

3.4. De instructies voor de controle van het programmaverloop

3.4.1. Stoppen

Met de methode Exit, gedefinieerd in de klasse Environment, kan de uitvoering van een programma worden gestopt.

syntaxe        void Exit(int status)
action        arrête le processus en cours et rend la valeur status au processus père

Exit beëindigt het lopende proces en geeft de controle terug aan het aanroepende proces. De waarde van status kan door dit proces worden gebruikt. Onder DOS wordt deze statusvariabele teruggegeven in de systeemvariabele ERRORLEVEL, waarvan de waarde in een batchbestand kan worden getest. Onder Unix, met de Bourne-shell, is het de variabele $? die de waarde van status ophaalt.

    Environment.Exit(0);

stopt de uitvoering van het programma met een statuswaarde van 0.

3.4.2. Eenvoudige keuzestructuur

 syntaxe :  if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}

opmerkingen:

  • de voorwaarde staat tussen haakjes.
  • Elke actie wordt afgesloten met een puntkomma.
  • De accolades worden niet afgesloten met een puntkomma.
  • accolades zijn alleen nodig als er meer dan één actie is.
  • de clausule else mag ontbreken.
  • Er is geen clausule then.

Het algoritmische equivalent van deze structuur is de if-then-else-structuur:

Voorbeeld


    if (x>0)  { nx=nx+1;sx=sx+x;} else dx=dx-x;

Keuzestructuren kunnen in elkaar worden genest:

if(condition1)
if (condition2)
        {......}
      else         //voorwaarde2
         {......}
else         //voorwaarde1
     {.......}

Soms doet zich het volgende probleem voor:


using System;

namespace Chap1 {
    class P07 {
        static void Main(string[] args) {
            int n = 5;
            if (n > 1)
                if (n > 6)
                    Console.Out.WriteLine(">6");
                else Console.Out.WriteLine("<=6");
        }
    }
}

In het vorige voorbeeld: naar welke if verwijst de else in regel 10? De regel is dat een else altijd verwijst naar de dichtstbijzijnde if: in dit voorbeeld is dat if(n>6), op regel 8. Laten we nog een voorbeeld bekijken:


            if (n2 > 1) {
                if (n2 > 6) Console.Out.WriteLine(">6");
       } else Console.Out.WriteLine("<=1");    

Hier wilden we een else koppelen aan if(n2>1) en geen else aan if(n2>6). Vanwege de vorige opmerking zijn we genoodzaakt accolades te gebruiken: if(n2>1) {...} else ...

3.4.3. Casestructuur

De syntaxis is als volgt:

switch(expression) {
    case v1:     
            actions1;
            break;
    case v2:     
            actions2;
            break;
         . .. .. .. .. ..
    default:     
            actions_sinon;
            break;
}

opmerkingen

  • de waarde van de controle-uitdrukking van switch kan een geheel getal, een teken of een tekenreeks zijn
  • de controle-uitdrukking staat tussen haakjes.
  • De clausule default mag ontbreken.
  • De waarden vi zijn mogelijke waarden van de uitdrukking. Als de uitdrukking de waarde vi heeft, worden de acties achter de case-clausule vi uitgevoerd.
  • De instructie break verlaat de case-structuur.
  • elk instructieblok dat gekoppeld is aan een waarde vi moet eindigen met een spronginstructie (break, goto, return, ...), anders meldt de compiler een fout.

Voorbeeld

In de algoritmiek

                selon la valeur de choix
                    cas 0
                         fin du module
                    cas 1
                         exécuter module M1
                    cas 2
                        exécuter module M2
                    sinon
                         erreur<--vrai
                findescas

In C#

int choix = 2; 
bool erreur = false;
switch (choix) {
   case 0: return;
   case 1: M1(); break;
   case 2: M2(); break;
   default: erreur = true; break;
}
}// einde Main

static void M1() {
    Console.WriteLine("M1");
}

static void M2() {
    Console.WriteLine("M2");
}
}

3.4.4. Herhalingsstructuren

3.4.4.1. Aantal herhalingen bekend

For-structuur

De syntaxis is als volgt:


    for (i=id;i<=if;i=i+ip){ 
       actions; 
      } 

Opmerkingen

  • De 3 argumenten van for staan tussen haakjes en worden gescheiden door puntkomma's.
  • Elke actie van de for wordt afgesloten met een puntkomma.
  • De accolades zijn alleen nodig als er meer dan één actie is.
  • De accolades worden niet gevolgd door een puntkomma.

Het algoritmische equivalent is de structuur pour:

pour i variant de id à if avec un pas de ip
    actions
finpour

wat kan worden vertaald naar een structuur tantque:

    i  id
    tantque i<=if
        actions
        i i+ip
    fintantque

foreach-structuur

De syntaxis is als volgt:


foreach (Type variable in collection)
    instructions; 
}

Opmerkingen

  • collection is een doorloopbare verzameling van objecten. De doorloopbare verzameling van objecten die we al kennen, is de array
  • Type is het type van de objecten in de verzameling. Bij een array zou dit het type van de array-elementen zijn
  • variable is een lokale variabele binnen de lus die achtereenvolgens alle waarden uit de verzameling als waarde zal aannemen.

De volgende code:


            string[] amis = { "paul", "hélène", "jacques", "sylvie" };
            foreach (string nom in amis) {
                Console.WriteLine(nom);
         }

zou het volgende weergeven:

paul
hélène
jacques
sylvie

3.4.4.2. Aantal herhalingen onbekend

Er bestaan talrijke structuren in C# voor dit geval.

While-structuur


    while(condition){
          actions;
        } 

De lus wordt herhaald zolang aan de voorwaarde wordt voldaan. Het kan zijn dat de lus nooit wordt uitgevoerd.

opmerkingen:

  • de voorwaarde staat tussen haakjes.
  • Elke actie wordt afgesloten met een puntkomma.
  • De accolades zijn alleen nodig als er meer dan één actie is.
  • De accolades worden niet gevolgd door een puntkomma.

De bijbehorende algoritmische structuur is de 'zolang'-structuur:

tantque condition
    actions
fintantque

Herhaalstructuur tot (do while)

De syntaxis is als volgt:


    do{
       instructions;
    }while(condition); 

De lus wordt herhaald totdat de voorwaarde onwaar wordt. In dit geval wordt de lus minstens één keer doorlopen.

opmerkingen

  • De voorwaarde staat tussen haakjes.
  • Elke actie wordt afgesloten met een puntkomma.
  • De accolades zijn alleen nodig als er meer dan één actie is.
  • De accolades worden niet gevolgd door een puntkomma.

De bijbehorende algoritmische structuur is de structuur ‘herhaal … tot’:

répéter
    actions
jusqu'à condition

Structuur voor 'for'

De syntaxis is als volgt:


for(instructions_départ;condition;instructions_fin_boucle){
    instructions; 
}

De lus wordt herhaald zolang de voorwaarde waar is (deze wordt voor elke lusronde geëvalueerd). Instructions_départ worden uitgevoerd voordat de lus voor de eerste keer wordt gestart. Instructions_fin_boucle worden na elke lusronde uitgevoerd.

opmerkingen

  • De verschillende instructies in instructions_depart en instructions_fin_boucle worden gescheiden door komma's.

De bijbehorende algoritmische structuur is als volgt:

instructions_départ
tantque condition
    actions
    instructions_fin_boucle
fintantque

Voorbeelden

De volgende codefragmenten berekenen allemaal de som van de eerste 10 gehele getallen.

            int i, somme, n=10;
            for (i = 1, somme = 0; i <= n; i = i + 1)
                somme = somme + i;

            for (i = 1, somme = 0; i <= n; somme = somme + i, i = i + 1) ;

            i = 1; somme = 0;
            while (i <= n) { somme += i; i++; }

            i = 1; somme = 0;
            do somme += i++;
            while (i <= n);

3.4.4.3. Instructies voor het beheer van de lus

break
verlaat de for-, while- of do...while-lus.
continue
gaat door naar de volgende iteratie van de for-, while- en do ... while-lussen

3.5. Uitzonderingsafhandeling

Veel C#-functies kunnen uitzonderingen, oftewel fouten, genereren. Wanneer een functie een uitzondering kan genereren, moet de programmeur deze afhandelen om programma's te verkrijgen die beter bestand zijn tegen fouten: het oncontroleerbaar "crashen" van een applicatie moet altijd worden vermeden.

Het afhandelen van een uitzondering verloopt volgens het volgende schema:

try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

Als de functie geen uitzondering genereert, gaat men door naar de volgende instructie; anders gaat men naar de hoofdtekst van de clausule catch en vervolgens naar de volgende instructie. Let op de volgende punten:

  • e is een object van het type Exception of een afgeleid type. We kunnen specifieker zijn door typen te gebruiken zoals IndexOutOfRangeException, FormatException, SystemException, enzovoort: er bestaan verschillende soorten uitzonderingen. Door catch (Exception e) te schrijven, geven we aan dat we alle soorten uitzonderingen willen afhandelen. Als de code van de clausule try meerdere soorten uitzonderingen kan genereren, willen we misschien specifieker zijn door de uitzondering af te handelen met meerdere clausules catch:
try{
    code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
    traiter l'exception e1
}
} catch ( FormatException e2){
    traiter l'exception e2
}
instruction suivante
  • Aan de clausules try/catch kan een finally-clausule worden toegevoegd:
try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
finally{
    code exécuté après try ou catch
}
instruction suivante

Of er nu een uitzondering is of niet, de code van de clausule finally wordt altijd uitgevoerd.

  • In de clausule catch wil men het beschikbare object Exception misschien niet gebruiken. In plaats van catch (Exception e){..} te schrijven, schrijft men dan catch(Exception){...} of, eenvoudiger nog, catch {...}.
  • De klasse Exception heeft een eigenschap Message die een bericht bevat met details over de fout die is opgetreden. Als men dit bericht wil weergeven, schrijft men dus:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
    ...
}//catch
  • De klasse Exception heeft een methode ToString die een tekenreeks retourneert met het type van de uitzondering en de waarde van de eigenschap Message. We kunnen dus het volgende schrijven:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
    ...
}//catch

Men kan ook schrijven:

catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex);
    ...
}//catch

De compiler wijst aan de parameter {0} de waarde ex.ToString() toe.

Het volgende voorbeeld toont een uitzondering die wordt gegenereerd door het gebruik van een niet-bestaand array-element:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
            // definitie en initialisatie van een array
            int[] tab = { 0, 1, 2, 3 };
            int i;
            // weergave van een array met een for-lus
            for (i = 0; i < tab.Length; i++)
                Console.WriteLine("tab[{0}]={1}", i, tab[i]);
            // array weergeven met een for each-lus
            foreach (int élmt in tab) {
                Console.WriteLine(élmt);
            }
            // een uitzondering genereren
            try {
                tab[100] = 6;
            } catch (Exception e) {
                Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
                return;
            }//try-catch
            finally {
                Console.WriteLine("finally ...");
            }
        }
    }
}

Hierboven zal regel 18 een uitzondering genereren omdat de array tab geen element nr. 100 heeft. De uitvoering van het programma levert de volgende resultaten op:

tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
0
1
2
3
L'erreur suivante s'est produite : System.IndexOutOfRangeException: L'index se trouve en dehors des limites du tableau.
   à Chap1.P08.Main(String[] args) dans C:\data\travail\2007-2008\c# 2008\poly\Chap1\08\Program.cs:regel 7
finally ...
  • regel 9: de uitzondering [System.IndexOutOfRangeException] is opgetreden
  • regel 11: de clausule finally (regels 23-25) van de code is uitgevoerd, terwijl er op regel 21 een instructie return stond om de methode te verlaten. Merk op dat de clausule finally altijd wordt uitgevoerd.

Hier volgt nog een voorbeeld waarin de uitzondering wordt afgehandeld die wordt veroorzaakt door het toewijzen van een tekenreeks aan een variabele van het type integer, terwijl de tekenreeks geen geheel getal vertegenwoordigt:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {

            // voorbeeld 2
            // Er wordt om de naam gevraagd
            Console.Write("Nom : ");
            // antwoord voorlezen
            string nom = Console.ReadLine();
            // er wordt naar de leeftijd gevraagd
            int age = 0;
            bool ageOK = false;
            while (!ageOK) {
                // vraag
                Console.Write("âge : ");
                // antwoord lezen en controleren
                try {
                    age = int.Parse(Console.ReadLine());
                    ageOK = age>=1;
                } catch {
                }//try-catch
                if (!ageOK) {
                    Console.WriteLine("Age incorrect, recommencez...");
                }
            }//while
            // eindweergave
            Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
        }
    }
}
  • regels 15-27: de lus voor het invoeren van de leeftijd van een persoon
  • regel 20: de via het toetsenbord ingevoerde regel wordt door de methode int.Parse omgezet in een geheel getal. Deze methode genereert een uitzondering als de conversie niet mogelijk is. Daarom is de bewerking in een try/catch-blok geplaatst.
  • regels 22-23: als er een uitzondering wordt gegenereerd, gaat men naar de catch-blok waar niets gebeurt. Daardoor blijft de booleaanse variabele ageOK, die in regel 14 op false is gezet, op false staan.
  • regel 21: als deze regel wordt bereikt, is de conversie van string naar int geslaagd. Er wordt echter gecontroleerd of het verkregen gehele getal groter is dan of gelijk is aan 1.
  • regels 24-26: er wordt een foutmelding weergegeven als de leeftijd onjuist is.

Enkele uitvoerresultaten:

1
2
3
Nom : dupont
âge : 23
Vous vous appelez dupont et vous avez 23 an(s)
1
2
3
4
5
6
7
Nom : durand
âge : x
Age incorrect, recommencez...
âge : -4
Age incorrect, recommencez...
âge : 12
Vous vous appelez durand et vous avez 12 an(s)

3.6. Voorbeeldtoepassing - V1

We willen een programma schrijven waarmee de belasting van een belastingplichtige kan worden berekend. We gaan uit van het vereenvoudigde geval van een belastingplichtige die alleen zijn salaris hoeft aan te geven (cijfers uit 2004 voor inkomsten uit 2003):

  • we berekenen het aantal aandelen van de werknemer nbParts=nbEnfants/2 +1 als hij ongehuwd is, nbEnfants/2 + 2 als hij gehuwd is, waarbij nbEnfants het aantal kinderen is.
  • als hij ten minste drie kinderen heeft, krijgt hij een half deel extra
  • zijn belastbaar inkomen wordt berekend als R = 0,72 * S, waarbij S zijn jaarsalaris is
  • zijn gezinscoëfficiënt wordt berekend als QF = R / nbParts
  • zijn belasting I wordt berekend. Laten we de volgende tabel eens bekijken:
4262
0
0
8382
0,0683
291,09
14753
0,1914
1322,92
23888
0,2826
2668,39
38868
0,3738
4846,98
47932
0,4262
6883,66
0
0,4809
9505,54

Elke regel heeft 3 velden. Om belasting I te berekenen, zoeken we de eerste regel waar QF <= veld1. Als bijvoorbeeld QF = 5000 is, vinden we de regel

    8382        0.0683        291.09

Belasting I is dan gelijk aan 0,0683*R - 291,09*nbParts. Als QF zodanig is dat de relatie QF<=veld1 nooit geldt, dan worden de coëfficiënten van de laatste regel gebruikt. In dit geval:

0 0,4809 9505,54

wat resulteert in de belasting I = 0,4809*R - 9505,54*nbParts.

Het bijbehorende C#-programma is als volgt:


using System;

namespace Chap1 {
    class Impots {
        static void Main(string[] args) {
            // tabel met gegevens die nodig zijn voor de berekening van de belasting
            decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
            decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
            decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };

            // de burgerlijke staat ophalen
            bool OK = false;
            string reponse = null;
            while (!OK) {
                Console.Write("Etes-vous marié(e) (O/N) ? ");
                reponse = Console.ReadLine().Trim().ToLower();
                if (reponse != "o" && reponse != "n")
                    Console.Error.WriteLine("Réponse incorrecte. Recommencez");
                else OK = true;
            }//while
            bool marie = reponse == "o";

            // aantal kinderen
            OK = false;
            int nbEnfants = 0;
            while (!OK) {
                Console.Write("Nombre d'enfants : ");
                try {
                    nbEnfants = int.Parse(Console.ReadLine());
                    OK = nbEnfants >= 0;
                } catch {
                }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
            }// while

            // salaris
            OK = false;
            int salaire = 0;
            while (!OK) {
                Console.Write("Salaire annuel : ");
                try {
                    salaire = int.Parse(Console.ReadLine());
                    OK = salaire >= 0;
                } catch {
                }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
            }// while

            // berekening van het aantal aandelen
            decimal nbParts;
            if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;

            // belastbaar inkomen
            decimal revenu = 0.72M * salaire;

            // gezinsquotiënt
            decimal QF = revenu / nbParts;

            // opzoeken van de belastingschijf die overeenkomt met QF
            int i;
            int nbTranches = limites.Length;
            limites[nbTranches - 1] = QF;
            i = 0;
            while (QF > limites[i]) i++;
            // de belasting
            int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);

            // het resultaat wordt weergegeven
            Console.WriteLine("Impôt à payer : {0} euros", impots);
        }
    }
}
  • regels 7-9: de numerieke waarden krijgen de achtervoegsel M (Money) zodat ze van het type decimal zijn.
  • regel 16:
  • Console.ReadLine() geeft de via het toetsenbord ingevoerde tekenreeks C1 terug
  • C1.Trim() verwijdert de spaties aan het begin en einde van C1 – geeft de tekenreeks C2 terug
  • C2.ToLower() levert de tekenreeks C3 op, wat de tekenreeks C2 is, omgezet naar kleine letters.
  • regel 21: de booleaanse waarde marie krijgt de waarde true of false uit de relatie reponse=="o"
  • regel 29: de via het toetsenbord ingevoerde tekenreeks wordt omgezet naar het type int. Als de omzetting mislukt, wordt er een uitzondering gegenereerd.
  • regel 30: de booleaanse waarde OK krijgt de waarde true of false uit de relatie nbEnfants>=0
  • regels 55-56: men kan niet simpelweg nbEnfants/2 schrijven. Als nbEnfants gelijk was aan 3, zouden we 3/2 hebben, een gehele deling die 1 zou opleveren en niet 1,5. Daarom schrijven we (decimal)nbEnfants om een van de operanden van de deling reëel te maken en zo een deling tussen reële getallen te verkrijgen.

Hier volgen enkele uitvoervoorbeelden:

Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : 2
Salaire annuel : 60000
Impôt à payer : 4282 euros
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 euros

3.7. Argumenten van het hoofdprogramma

De hoofdfunctie Main kan als parameter een array van strings accepteren: String[] (of string[]). Deze array bevat de argumenten van de opdrachtregel die wordt gebruikt om de toepassing te starten. Als we het programma P dus starten met de volgende (DOS-)opdracht:


        P arg0 arg1 … argn

en als de functie Main als volgt is gedeclareerd:

public static void Main(string[] args)

dan is args[0]="arg0", args[1]="arg1" … Hier volgt een voorbeeld:


using System;

namespace Chap1 {
    class P10 {
        static void Main(string[] args) {
            // de ontvangen parameters worden weergegeven
            Console.WriteLine("Il y a  " + args.Length + " arguments");
            for (int i = 0; i < args.Length; i++) {
                Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
            }
        }
    }
}

Om argumenten door te geven aan de uitgevoerde code, gaat men als volgt te werk:

  • in [1]: klik met de rechtermuisknop op het project / Properties
  • in [2]: tabblad [Debug]
  • in [3]: voer de argumenten in

De uitvoering levert de volgende resultaten op:

1
2
3
4
5
Il y a  4 arguments
arguments[0]=a0
arguments[1]=a1
arguments[2]=a2
arguments[3]=a3

Merk op dat de handtekening

public static void Main()

geldig is als de functie Main geen parameters verwacht.

3.8. Enumeraties

Een opsomming is een gegevenstype waarvan het waardenbereik bestaat uit een verzameling gehele constanten. Laten we eens kijken naar een programma dat vermeldingen bij een examen moet verwerken. Er zouden er vijf zijn: Passable, AssezBien, Bien, TrèsBien, Excellent.

We zouden dan een opsomming kunnen definiëren voor deze vijf constanten:


        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };

Intern worden deze vijf constanten gecodeerd door opeenvolgende gehele getallen, beginnend met 0 voor de eerste constante, 1 voor de volgende, enzovoort. Een variabele kan worden gedeclareerd om deze waarden uit de opsomming aan te nemen:


            // een variabele die haar waarden ontleent aan de opsomming ‘Vermeldingen’
Mentions maMention = Mentions.Passable;

Een variabele kan worden vergeleken met de verschillende mogelijke waarden van de opsomming:


            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
}

Men kan alle waarden van de opsomming opvragen:


            // lijst met vermeldingen in de vorm van tekenreeksen
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
}

Net zoals het eenvoudige type int gelijkwaardig is aan de structuur System.Int32, is het eenvoudige type enum gelijkwaardig aan de structuur System.Enum. Deze structuur heeft een statische methode GetValues waarmee alle waarden van een opgesomd type kunnen worden opgehaald dat als parameter wordt doorgegeven. Dit moet een object van het type Type zijn, een klasse die informatie bevat over het type van een gegeven. Het type van een variabele v wordt verkregen via v.GetType(). Het type van een type T wordt verkregen via typeof(T). Dus hier levert maMention.GetType() het object Type uit de opsomming Mentions en Enum.GetValues(maMention.GetType()) de lijst met waarden van de opsomming Mentions.

Als we nu schrijven


            //lijst met vermeldingen in de vorm van gehele getallen
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
}

Regel 2: de lusvariabele is van het type geheel getal. We krijgen dan de lijst met waarden van de opsomming in de vorm van gehele getallen. Het object van het type System.Type, dat overeenkomt met het gegevenstype Mentions, wordt verkregen via typeof(Mentions). We hadden ook, net als eerder, maMention.GetType() kunnen schrijven.

Het volgende programma illustreert wat zojuist is beschreven:


using System;

namespace Chap1 {
    class P11 {
        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
        static void Main(string[] args) {
            // een variabele die zijn waarden ontleent aan de opsomming ‘Vermeldingen’
            Mentions maMention = Mentions.Passable;
            // weergave van de waarde van de variabele
            Console.WriteLine("mention=" + maMention);
            // test met waarde uit de opsomming
            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
            }
            // lijst met vermeldingen in de vorm van tekenreeksen
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
            }
            //lijst met vermeldingen in de vorm van gehele getallen
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
            }
        }
    }
}

De uitvoerresultaten zijn als volgt:

mention=Passable
Peut mieux faire
Passable
AssezBien
Bien
TrèsBien
Excellent
0
1
2
3
4

3.9. Parameters doorgeven aan een functie

We kijken hier naar de manier waarop parameters aan een functie worden doorgegeven. Laten we de volgende statische functie eens bekijken:


        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
}

In de definitie van de functie, regel 1, wordt a een formele parameter genoemd. Deze parameter is uitsluitend bedoeld voor de definitie van de functie changeInt. Hij had net zo goed b kunnen heten. Laten we nu eens kijken naar een toepassing van deze functie:


        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
}

Hier in de instructie op regel 3, ChangeInt(age), is age de effectieve parameter die zijn waarde doorgeeft aan de formele parameter a. We zijn geïnteresseerd in de manier waarop een formele parameter de waarde van een effectieve parameter ophaalt.

3.9.1. Doorgave per waarde

Het volgende voorbeeld laat zien dat de parameters van een functie standaard per waarde worden doorgegeven, dat wil zeggen dat de waarde van de effectieve parameter wordt gekopieerd naar de bijbehorende formele parameter. Het gaat hier om twee afzonderlijke entiteiten. Als de functie de formele parameter wijzigt, blijft de effectieve parameter ongewijzigd.


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
        }
        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
        }
    }
}

De verkregen resultaten zijn als volgt:

Paramètre formel a=30
Paramètre effectif age=20

De waarde 20 van de effectieve parameter age is overgenomen in de formele parameter a (regel 10). Deze is vervolgens gewijzigd (regel 11). De effectieve parameter is ongewijzigd gebleven. Deze overdrachtswijze is geschikt voor invoerparameters van een functie.

3.9.2. Doorgeven via verwijzing

Bij doorgave via verwijzing zijn de werkelijke parameter en de formele parameter één en dezelfde entiteit. Als de functie de formele parameter wijzigt, wordt ook de werkelijke parameter gewijzigd. In C# moeten beide worden voorafgegaan door het sleutelwoord ref:

Hier volgt een voorbeeld:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // voorbeeld 2
            int age2 = 20;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

en de uitvoerresultaten:

Paramètre formel a2=30
Paramètre effectif age2=30

De werkelijke parameter heeft de wijziging van de formele parameter gevolgd. Deze manier van doorgeven is geschikt voor uitvoerparameters van een functie.

3.9.3. Doorgave via verwijzing met het sleutelwoord out

Laten we het vorige voorbeeld eens bekijken, waarin de variabele age2 niet zou worden geïnitialiseerd vóór de aanroep van de functie changeInt:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // voorbeeld 2
            int age2;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

Bij het compileren van dit programma treedt er een fout op:

    Use of unassigned local variable 'age2'

Dit probleem kan worden omzeild door een beginwaarde toe te wijzen aan age2. Ook kan het sleutelwoord ref worden vervangen door het sleutelwoord out. Hiermee wordt aangegeven dat de parameter uitsluitend een uitgangsparameter is en dus geen beginwaarde nodig heeft:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // voorbeeld 3
            int age3;
            ChangeInt3(out age3);
            Console.WriteLine("Paramètre effectif age3=" + age3);
        }
        private static void ChangeInt3(out int a3) {
            a3 = 30;
            Console.WriteLine("Paramètre formel a3=" + a3);
        }
    }
}

De resultaten van de uitvoering zijn als volgt:

Paramètre formel a3=30
Paramètre effectif age3=30