Skip to content

3. Grundlagen der Sprache C#

3.1. Introduction

Wir betrachten C# zunächst als klassische Programmiersprache. Auf Klassen gehen wir später ein. In einem Programm gibt es zwei Dinge:

  • Daten
  • Anweisungen, die diese Daten bearbeiten

Man ist in der Regel bestrebt, die Daten von den Anweisungen zu trennen:

3.2. Die Daten in C#

C# verwendet die folgenden Datentypen:

  1. Ganzzahlen
  1. reelle Zahlen
  2. Dezimalzahlen
  3. Zeichen und Zeichenfolgen
  4. Boolesche Werte
  5. Objekte

3.2.1. Vordefinierte Datentypen

C#-Typ
Typ .NET
Dargestellte Daten
Suffix
der Literalwerte
Kodierung
Wertebereich
char
Zeichen (S)
Zeichen
 
2 Byte
Unicode-Zeichen (UTF-16)
string
Zeichenkette (C)
Zeichenkette
  
Referenz auf eine Unicode-Zeichenfolge
int
Int32 (S)
Ganzzahl
 
4 Bytes
[-231, 231-1] [–2147483648, 2147483647]
uint
UInt32 (S)
..
U
4 Bytes
[0, 232-1] [0, 4294967295]
long
Int64 (S)
..
L
8 Byte
[-263, 263 -1] [–9223372036854775808, 9223372036854775807]
ulong
UInt64 (S)
..
UL
8 Byte
[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
Einzelnummer (S)
Reelle Zahl
F
4 Byte
[1.5 10-45, 3.4 10+38] als Absolutwert
double
Doppel (S)
..
D
8 Byte
[-1.7 10+308, 1.7 10+308] als Absolutwert
decimal
Dezimal (S)
Dezimalzahl
M
16 Byte
[1.0 10-28,7.9 10+28] als Absolutwert mit 28 signifikanten Ziffern
bool
Boolescher Wert (S)
..
 
1 Byte
true, false
object
Objekt (C)
Objektreferenz
  
Objektreferenz

Oben wurden den C#-Typen ihre entsprechenden .NET-Typen gegenübergestellt, mit dem Kommentar (S), wenn es sich bei diesem Typ um eine Struktur handelt, und (C), wenn es sich um eine Klasse handelt. Es zeigt sich, dass es für eine 32-Bit-Ganzzahl zwei mögliche Typen gibt: int und Int32. Der Typ int ist ein C#-Typ. Int32 ist eine Struktur, die zum Namensraum System gehört. Ihr vollständiger Name lautet somit System.Int32. Der Typ int ist ein C#-Alias, der auf die Struktur .NET System.Int32 verweist. Ebenso ist der C#-Typ string ein Alias für den Typ .NET System.String. System.String ist eine Klasse und keine Struktur. Die beiden Begriffe sind sich zwar ähnlich, weisen jedoch den folgenden grundlegenden Unterschied auf:

  • Eine Variable vom Typ Structure wird über ihren Wert bearbeitet
  • eine Variable vom Typ Classe wird über ihre Adresse (Referenz in der objektorientierten Programmierung) bearbeitet.

Sowohl eine structure als auch eine classe sind komplexe Typen mit Attributen und Methoden. Daher kann man schreiben:

string nomDuType=3.GetType().FullName;

Das obenstehende Literal 3 ist standardmäßig vom Typ C# int, also vom Typ .NET System.Int32. Diese Struktur verfügt über eine Methode GetType(), die ein Objekt zurückgibt, das die Eigenschaften des Datentyps System.Int32 kapselt. Zu diesen gehört die Eigenschaft FullName, die den vollständigen Namen des Typs zurückgibt. Man sieht also, dass das Literal 3 ein komplexeres Objekt ist, als es auf den ersten Blick erscheint.

Hier ist ein Programm, das diese verschiedenen Punkte veranschaulicht:


using System;

namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            // Beispiel 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));
        }
    }
}
  • Zeile 7: Deklaration einer Ganzzahl ent
  • Zeile 19: Der Typ einer Variablen v kann mit v.GetType().FullName ermittelt werden. Die Größe einer Struktur S kann mit sizeof(S) ermittelt werden. Die Anweisung Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) gibt den Text, der ihr erster Parameter ist, auf dem Bildschirm aus, wobei jede Notation {i} durch den Wert des Ausdrucks parami ersetzt wird.
  • Zeile 22: Da der Typ string eine Klasse und keine Struktur bezeichnet, kann der Operator sizeof nicht verwendet werden.

Hier ist das Ergebnis der Ausführung:

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]

Die Ausgabe zeigt die Typen .NET an und nicht die C#-Aliase.

3.2.2. Notation von Literalwerten

entier int (32 bits)
145, -7, 0xFF (hexadezimal)
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
„heute“ „c:\\Kapitel1\\Absatz3“ @„c:\Kapitel1\Absatz3“
booléen bool
true, false
date
new DateTime(1954,10,13) (Jahr, Monat, Tag) für den 13.10.1954

Beachten Sie die beiden Literalzeichenfolgen: „c:\\chap1\\paragraph3“ und @"c:\chap1\paragraph3". In Literalzeichenfolgen wird das Zeichen \ interpretiert. So steht „\n“ für das Zeilenendezeichen und nicht für die Folge der beiden Zeichen \ und n. Wenn man diese Zeichenfolge haben möchte, müsste man „\\n“ schreiben, wobei die Sequenz \\ als ein einziges Zeichen \ interpretiert wird. Man könnte auch @"\n" schreiben, um das gleiche Ergebnis zu erhalten. Die Syntax @"text" verlangt, dass text genau so übernommen wird, wie er geschrieben ist. Dies wird manchmal als Verbatim-Zeichenkette bezeichnet.

3.2.3. Datendeklaration

3.2.3.1. Rolle der Deklarationen

Ein Programm verarbeitet Daten, die durch einen Namen und einen Typ gekennzeichnet sind. Diese Daten werden im Speicher abgelegt. Bei der Übersetzung des Programms weist der Compiler jedem Datenelement einen Speicherplatz zu, der durch eine Adresse und eine Größe gekennzeichnet ist. Dabei stützt er sich auf die vom Programmierer vorgenommenen Deklarationen.

Außerdem ermöglichen diese dem Compiler, Programmierfehler zu erkennen. So wird die Operation

x = x * 2;

wird beispielsweise als fehlerhaft deklariert, wenn x eine Zeichenkette ist.

3.2.3.2. Deklaration von Konstanten

Die Syntax zur Deklaration einer Konstante lautet wie folgt:

    const type nom=valeur;          //definiert eine Konstante name=wert

Beispiel:

const float myPI=3.141592F;    

Warum Konstanten deklarieren?

  1. Das Programm lässt sich leichter lesen, wenn man der Konstante einen aussagekräftigen Namen gegeben hat:
    const  float taux_tva=0.186F;
  1. Die Änderung des Programms wird einfacher, falls sich die „Konstante“ ändern sollte. Wenn also im vorigen Fall der Mehrwertsteuersatz auf 33 % steigt, muss lediglich die Anweisung geändert werden, die ihren Wert definiert:
    const float taux_tva=0.33F;

Hätte man im Programm explizit den Wert 0,186 verwendet, müssten zahlreiche Anweisungen geändert werden.

3.2.3.3. Deklaration von Variablen

Eine Variable wird durch einen Namen identifiziert und bezieht sich auf einen Datentyp. C# unterscheidet zwischen Groß- und Kleinschreibung. Daher sind die Variablen „FIN“ und „fin“ unterschiedlich.

Variablen können bei ihrer Deklaration initialisiert werden. Die Syntax zur Deklaration einer oder mehrerer Variablen lautet:

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

wobei Identificateur_de_type ein vordefinierter Typ oder ein vom Programmierer definierter Typ ist. Optional kann eine Variable gleichzeitig mit ihrer Deklaration initialisiert werden.

Man kann auch den genauen Typ einer Variablen weglassen, indem man das Schlüsselwort „var“ anstelle von Identificateur_de_type verwendet:

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

Das Schlüsselwort „var“ bedeutet nicht, dass die Variablen keinen bestimmten Typ haben. Die Variable variablei hat den Typ des ihr zugewiesenen Wertes valeuri. Die Initialisierung ist hier obligatorisch, damit der Compiler den Typ der Variablen ableiten kann.

Hier ein Beispiel:


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);
        }
    }
}
  • Zeile 6: ein explizit typisierter Wert
  • Zeile 7: (Daten).GetType(). „Name“ ist der Kurzname von (Daten), (Daten).GetType().FullName ist der vollständige Name von (Daten)
  • Zeile 8: ein implizit typisierter Wert. Da 3 vom Typ int ist, ist j vom Typ int.
  • Zeile 10: Ein implizit typisierter Wert. Da DateTime.Now vom Typ DateTime ist, ist aujourdhui vom Typ DateTime.

Bei der Ausführung erhält man folgendes Ergebnis:

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

Eine Variable, deren Typ implizit durch das Schlüsselwort var festgelegt wurde, kann anschließend ihren Typ nicht mehr ändern. Daher könnte man nach Zeile 10 des Codes nicht die folgende Zeile schreiben:


            var aujourdhui = "aujourd'hui";

Wir werden später sehen, dass es möglich ist, einen Typ „on the fly“ in einem Ausdruck zu deklarieren. Dabei handelt es sich um einen anonymen Typ, also einen Typ, dem der Benutzer keinen Namen gegeben hat. Der Compiler weist diesem neuen Typ einen Namen zu. Wenn eine Datenangabe vom anonymen Typ einer Variablen zugewiesen werden soll, kann diese nur mit dem Schlüsselwort „var“ deklariert werden.

3.2.4. Konvertierungen zwischen Zahlen und Zeichenketten

nombre -> chaîne
nombre.ToString()
chaine -> int
int.Parse(Zeichenkette) oder System.Int32.Parse
chaîne -> long
long.Parse(Zeichenkette) oder System.Int64.Parse
chaîne -> double
double.Parse (Zeichenkette) oder System.Double.Parse (Zeichenkette)
chaîne -> float
float.Parse (Zeichenkette) oder System.Float.Parse (Zeichenkette)

Die Konvertierung einer Zeichenkette in eine Zahl kann fehlschlagen, wenn die Zeichenkette keine gültige Zahl darstellt. In diesem Fall wird ein schwerwiegender Fehler, eine sogenannte Ausnahme, ausgelöst. Dieser Fehler kann durch die folgende Klausel try/catch behandelt werden:


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

Wenn die Funktion keine Ausnahme auslöst, wird die nächste Anweisung ausgeführt; andernfalls wird der Hauptteil der Klausel catch aufgerufen und anschließend die nächste Anweisung ausgeführt. Wir werden später noch auf die Ausnahmebehandlung zurückkommen. Hier ist ein Programm, das einige Techniken zur Konvertierung zwischen Zahlen und Zeichenketten veranschaulicht. In diesem Beispiel gibt die Funktion affiche den Wert ihres Parameters auf dem Bildschirm aus. So gibt affiche(S) den Wert von S auf dem Bildschirm aus, wobei S vom Typ string ist.


using System;

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

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

            // Zahl --> Zeichenkette
            affiche(i.ToString());
            affiche(l.ToString());
            affiche(f.ToString());
            affiche(d.ToString());

            //Boolescher Wert --> Zeichenkette
            const bool b = false;
            affiche(b.ToString());

            // Zeichenkette --> 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);
            }

            // Zeichenkette --> 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);
            }

            // Zeichenkette --> 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);
            }

            // Zeichenkette --> 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);
            }

        }// Ende main

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

In den Zeilen 30–32 wird die möglicherweise auftretende Ausnahme behandelt. e.Message ist die Fehlermeldung, die mit der Ausnahme e verbunden ist.

Die erzielten Ergebnisse lauten wie folgt:

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.

Es ist zu beachten, dass reelle Zahlen in Form einer Zeichenkette das Komma und nicht den Dezimalpunkt verwenden müssen. Daher schreibt man

double d1=10.7; 

, aber

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

3.2.5. Datentabellen

Ein C#-Array ist ein Objekt, mit dem Daten desselben Typs unter einem gemeinsamen Bezeichner zusammengefasst werden können. Es wird wie folgt deklariert:

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

n ist die Anzahl der Daten, die das Array enthalten kann. Die Syntax „Array[i]“ bezeichnet den Datenwert Nr. i, wobei i zum Intervall [0,n-1] gehört. Jeder Verweis auf den Datenwert „Tableau[i]“, bei dem „i“ nicht zum Intervall „[0,n-1]“ gehört, löst eine Ausnahme aus. Ein Array kann gleichzeitig mit seiner Deklaration initialisiert werden:

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

oder einfacher:

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

Arrays verfügen über die Eigenschaft „Length“, die die Anzahl der Elemente des Arrays angibt.

Ein zweidimensionales Array kann wie folgt deklariert werden:

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

wobei n die Anzahl der Zeilen und m die Anzahl der Spalten ist. Die Syntax Tableau[i,j] bezeichnet das Element j in Zeile i des Arrays. Das zweidimensionale Array kann ebenfalls gleichzeitig mit seiner Deklaration initialisiert werden:

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

oder einfacher:

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

Die Anzahl der Elemente in jeder der Dimensionen lässt sich mit der Methode GetLength(i) ermitteln, wobei i=0 für die Dimension steht, die dem ersten Index entspricht, i=1 für die Dimension, die dem zweiten Index entspricht, …

Die Gesamtzahl der Dimensionen wird mit der Eigenschaft „Rank“ ermittelt, die Gesamtzahl der Elemente mit der Eigenschaft „Length“.

Ein Array von Arrays wird wie folgt deklariert:

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

Die obige Deklaration erstellt ein Array mit n Zeilen. Jedes Element tableau[i] ist eine eindimensionale Array-Referenz. Diese Referenzen tableau[i] werden bei der obigen Deklaration nicht initialisiert. Sie haben den Wert der Null-Referenz.

Das folgende Beispiel veranschaulicht die Erstellung eines Arrays von Arrays:


            // ein Array von Arrays
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
            // Initialisierung
            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
  • Zeile 2: Ein Array noms mit 3 Elementen vom Typ string[][]. Jedes Element ist ein Array-Zeiger (eine Objektreferenz), dessen Elemente vom Typ string[] sind.
  • Zeilen 3–5: Die drei Elemente des Arrays noms werden initialisiert. Jedes „zeigt“ nun auf ein Array mit Elementen vom Typ string[]. noms[i][j] ist das Element j des Arrays vom Typ string [], auf das noms[i] verweist.
  • Zeile 9: Initialisierung des Elements „noms[i][j]“ innerhalb einer doppelten Schleife. Hier ist „noms[i]“ ein Array mit i+1 Elementen. Da „noms[i]“ ein Array ist, gibt „noms[i].Length“ die Anzahl seiner Elemente an.

Hier ist ein Beispiel, das die drei soeben vorgestellten Array-Typen zusammenfasst:


using System;

namespace Chap1 {
    // Arrays

    using System;

    // Testklasse
    public class P02 {
        public static void Main() {
            // ein initialisiertes eindimensionales 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]);
            }//für

            // ein initialisiertes zweidimensionales 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

            // ein Array von Arrays
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//für
            // Initialisierung
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//für j
            }//für i
            // Anzeige
            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]);
                }//für j
            }//für i
        }//Hauptseite
    }//Klasse
}//Namensraum

Bei der Ausführung erhalten wir folgende Ergebnisse:

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. Die Grundanweisungen in C#

Man unterscheidet

1 die vom Computer ausgeführten elementaren Befehle.

2 die Anweisungen zur Steuerung des Programmablaufs.

Die elementaren Befehle werden deutlich, wenn man die Struktur eines Mikrocomputers und seiner Peripheriegeräte betrachtet.

  1. Einlesen von Informationen von der Tastatur

  2. Verarbeitung von Informationen

  3. Ausgabe von Informationen auf dem Bildschirm

3.3.1. Ausgabe auf dem Bildschirm

Es gibt verschiedene Befehle zum Schreiben auf den Bildschirm:

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

wobei expression ein beliebiger Datentyp ist, der in eine Zeichenkette umgewandelt werden kann, um auf dem Bildschirm angezeigt zu werden. Alle Objekte in C# oder .NET verfügen über eine Methode ToString(), die für diese Umwandlung verwendet wird.

Die Klasse System.Console ermöglicht den Zugriff auf Bildschirm-Schreiboperationen (Write, WriteLine). Die Klasse Console verfügt über zwei Eigenschaften, „Out“ und „Error“, bei denen es sich um Schreibströme vom Typ TextWriter handelt:

  • Console.WriteLine() entspricht Console.Out.WriteLine() und schreibt in den Out-Stream, der üblicherweise dem Bildschirm zugeordnet ist.
  • Console.Error.WriteLine() schreibt in den Error-Stream, der in der Regel ebenfalls mit dem Bildschirm verknüpft ist.

Die Streams Out und Error können zur Laufzeit des Programms in Textdateien umgeleitet werden, wie wir gleich sehen werden.

3.3.2. Einlesen von über die Tastatur eingegebenen Daten

Der Datenstrom von der Tastatur wird durch das Objekt Console.In vom Typ TextReader bezeichnet. Mit Objekten dieses Typs lässt sich eine Textzeile mit der Methode ReadLine lesen:

    string ligne=Console.In.ReadLine();

Die Klasse „Console“ bietet eine Methode ReadLine, die standardmäßig dem Datenstrom In zugeordnet ist. Man kann also Folgendes schreiben:

    string ligne=Console.ReadLine();

Die über die Tastatur eingegebene Zeile wird in der Variablen ligne gespeichert und kann anschließend vom Programm verarbeitet werden. Der In-Stream kann wie die Out- und Error-Streams in eine Datei umgeleitet werden.

3.3.3. Beispiel für Ein- und Ausgabe

Hier ist ein kurzes Programm zur Veranschaulichung der Ein- und Ausgabeoperationen über Tastatur und Bildschirm:


using System;

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

            // Schreiben in den Out-Stream
            object obj = new object();
            Console.Out.WriteLine(obj);

            // Schreiben in den Error-Stream
            int i = 10;
            Console.Error.WriteLine("i=" + i);

            // Lesen einer über die Tastatur eingegebenen Zeile
            Console.Write("Tapez une ligne : ");
            string ligne = Console.ReadLine();
            Console.WriteLine("ligne={0}", ligne);
        }//Ende der Hauptfunktion
    }// Ende der Klasse
}
  • Zeile 9: obj ist eine Objektreferenz
  • Zeile 10: obj wird auf dem Bildschirm ausgegeben. Standardmäßig wird die Methode obj.ToString() aufgerufen.
  • Zeile 14: Man kann auch schreiben:

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

Der erste Parameter „i={0}“ ist das Anzeigeformat, die anderen Parameter sind die anzuzeigenden Ausdrücke. Die Elemente {n} sind „positionale“ Parameter. Bei der Ausführung wird der Parameter {n} durch den Wert des Ausdrucks Nr. n ersetzt.

Das Ergebnis der Ausführung lautet wie folgt:

1
2
3
4
System.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
  • Zeile 1: Die durch Zeile 10 des Codes erzeugte Ausgabe. Die Methode obj.ToString() hat den Typnamen der Variablen obj angezeigt: System.Object. Der Typ object ist ein C#-Alias für den Typ .NET System.Object.

3.3.4. E/A-Umleitung

Unter DOS und UNIX gibt es drei Standardgeräte mit den folgenden Bezeichnungen:

  1. Standard-Eingabegerät – bezeichnet standardmäßig die Tastatur und trägt die Nummer 0
  2. Standard-Ausgabegerät – bezeichnet standardmäßig den Bildschirm und hat die Nummer 1
  3. Standard-Fehlergerät – bezeichnet standardmäßig den Bildschirm und hat die Nummer 2

In C# schreibt der Schreibstrom Console.Out auf Gerät 1, der Schreibstrom Console.Error schreibt auf Gerät 2 und der Lesestrom Console.In liest Daten von Gerät 0.

Beim Starten eines Programms unter DOS oder Unix kann festgelegt werden, welche Geräte 0, 1 und 2 für das ausgeführte Programm verwendet werden sollen. Betrachten wir die folgende Befehlszeile:

pg arg1 arg2 .. argn

Hinter den Argumenten argi des Programms pg können die Standard-E/A-Geräte auf Dateien umgeleitet werden:

0<in.txt
Der Standardeingabestrom Nr. 0 wird in die Datei in.txt umgeleitet. Im Programm bezieht der Strom Console.In seine Daten somit aus der Datei in.txt.
1>out.txt
leitet die Ausgabe Nr. 1 in die Datei „out.txt“ um. Dies hat zur Folge, dass im Programm der Datenstrom „Console.Out“ seine Daten in die Datei „out.txt“ schreibt
1>>out.txt
ebenso, jedoch werden die geschriebenen Daten an den aktuellen Inhalt der Datei out.txt angehängt.
2>error.txt
leitet die Ausgabe Nr. 2 in die Datei „error.txt“ um. Dies hat zur Folge, dass im Programm der Datenstrom „Console.Error“ seine Daten in die Datei „error.txt“ schreibt
2>>error.txt
ebenso, jedoch werden die geschriebenen Daten an den aktuellen Inhalt der Datei „error.txt“ angehängt.
1>out.txt 2>error.txt
Die Geräte 1 und 2 werden beide in Dateien umgeleitet

Es ist zu beachten, dass das Programm pg nicht geändert werden muss, um die E/A-Ströme des Programms pg in Dateien umzuleiten. Das Betriebssystem legt fest, um welche Art von Geräten es sich bei den Geräten 0, 1 und 2 handelt. Betrachten wir das folgende Programm:


using System;

namespace Chap1 {

    // Umleitungen
    public class P04 {
        public static void Main(string[] args) {
            // Einlesen des In-Datenstroms
            string data = Console.In.ReadLine();
            // Schreiben des Out-Stroms
            Console.Out.WriteLine("écriture dans flux Out : " + data);
            // Schreiben in den Fehler-Stream
            Console.Error.WriteLine("écriture dans flux Error : " + data);
        }//Hauptklasse
    }//Klasse
}

Erstellen wir die ausführbare Datei aus diesem Quellcode:

  • in [1]: Die ausführbare Datei wird durch einen Rechtsklick auf das Projekt / „Build“
  • in [2]: In einem DOS-Fenster wurde die ausführbare Datei 04.exe im Ordner bin/Release des Projekts erstellt.

Geben wir die folgenden Befehle im DOS-Fenster [2] ein:

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
  • Zeile 1: Die Zeichenkette test wird in die Datei in.txt geschrieben
  • Zeilen 2–3: Der Inhalt der Datei in.txt wird zur Überprüfung angezeigt
  • Zeile 4: Ausführung des Programms 04.exe. Der Datenstrom In wird in die Datei in.txt umgeleitet, der Datenstrom Out in die Datei out.txt, der Datenstrom Error in die Datei err.txt. Die Ausführung führt zu keiner Anzeige.
  • Zeilen 5–6: Inhalt der Datei out.txt. Dieser Inhalt zeigt uns, dass:
  • die Datei „in.txt“ gelesen wurde
  • die Bildschirmausgabe wurde in die Datei „out.txt“ umgeleitet
  • Zeilen 7–8: Analoge Überprüfung für die Datei err.txt

Es ist deutlich zu erkennen, dass die Datenströme Out und In nicht auf dieselben Geräte schreiben, da sie separat umgeleitet werden konnten.

3.3.5. Zuweisung des Werts eines Ausdrucks an eine Variable

Wir betrachten hier den Vorgang variable=expression;

Der Ausdruck kann folgender Art sein: arithmetisch, relational, boolesch, Zeichen

3.3.5.1. Interpretation der Zuweisungsoperation

Die Operation variable=expression;

ist selbst ein Ausdruck, dessen Auswertung wie folgt abläuft:

  • Die rechte Seite der Zuweisung wird ausgewertet: Das Ergebnis ist ein Wert V.
  • Der Wert V wird der Variablen zugewiesen
  • Der Wert V ist zugleich der Wert der Zuweisung, die nun als Ausdruck betrachtet wird.

So funktioniert die Operation

    V1=V2=expression

zulässig ist. Aufgrund der Priorität wird der Operator „=“ ganz rechts ausgewertet. Es ergibt sich also

    V1=(V2=expression)

Der Ausdruck V2=Ausdruck wird ausgewertet und hat den Wert V. Die Auswertung dieses Ausdrucks hat dazu geführt, dass V den Wert V2 erhalten hat. Der nächste Operator = wird dann in folgender Form ausgewertet:

    V1=V

Der Wert dieses Ausdrucks ist wiederum V. Seine Auswertung führt zur Zuweisung von V an V1.

Somit ist die Operation V1=V2=Ausdruck

ist ein Ausdruck, dessen Auswertung

  • die Zuweisung des Werts von expression an die Variablen V1 und V2 bewirkt
  • gibt als Ergebnis den Wert expression zurück.

Man kann dies auf einen Ausdruck der folgenden Form verallgemeinern:

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

3.3.5.2. Arithmetischer Ausdruck

Die Operatoren für arithmetische Ausdrücke sind folgende:

  • Addition

  • Subtraktion

* Multiplikation

/ Division: Das Ergebnis ist der exakte Quotient, wenn mindestens einer der Operanden reell ist. Sind beide Operanden ganzzahlig, ist das Ergebnis der ganzzahlige Quotient. So ergibt sich beispielsweise 5/2 -> 2 und 5,0/2 -> 2,5.

% Division: Das Ergebnis ist der Rest, unabhängig von der Art der Operanden, wobei der Quotient ganzzahlig ist. Es handelt sich also um die Modulo-Operation.

Es gibt verschiedene mathematische Funktionen. Hier sind einige davon:

double Sqrt(double x)
Quadratwurzel
double Cos(double x)
Kosinus
double Sin(double x)
Sinus
double Tan(double x)
Tangens
double Pow(double x,double y)
x hoch y (x > 0)
double Exp(double x)
Exponentialfunktion
double Log(double x)
Naturlogarithmus
double Abs(double x)
Absolutwert

usw.

Alle diese Funktionen sind in einer C#-Klasse namens „Math“ definiert. Bei ihrer Verwendung muss ihnen der Name der Klasse vorangestellt werden, in der sie definiert sind. Man schreibt also:

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

Die vollständige Definition der Klasse Math lautet wie folgt:

Image

Image

Image

Image

Image

3.3.5.3. Prioritäten bei der Auswertung arithmetischer Ausdrücke

Die Priorität der Operatoren bei der Auswertung eines arithmetischen Ausdrucks ist wie folgt (von der höchsten zur niedrigsten Priorität):

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

Operatoren innerhalb desselben Blocks [ ] haben dieselbe Priorität.

3.3.5.4. Relationale Ausdrücke

Es gibt folgende Operatoren:

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

Prioritäten der Operatoren

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

Das Ergebnis eines relationalen Ausdrucks ist der Boolesche Wert false, wenn der Ausdruck falsch ist, andernfalls true.

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

Vergleich zweier Zeichen

Gegeben seien zwei Zeichen C1 und C2. Diese können mit den Operatoren verglichen werden

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

Dabei werden ihre Unicode-Codes, also Zahlen, miteinander verglichen. Gemäß der Unicode-Reihenfolge gelten folgende Beziehungen:

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

Vergleich zweier Zeichenfolgen

Sie werden Zeichen für Zeichen verglichen. Die erste festgestellte Ungleichheit zwischen zwei Zeichen führt zu einer Ungleichheit in derselben Richtung bei den Zeichenfolgen.

Beispiele:

Angenommen, es sollen die Zeichenfolgen „Katze“ und „Hund“ verglichen werden

Diese letzte Ungleichheit lässt den Schluss zu, dass „Chat“ < „Chien“ gilt.

Nehmen wir an, wir wollen die Zeichenfolgen „Chat“ und „Chaton“ vergleichen. Es herrscht durchgehend Gleichheit, bis die Zeichenfolge „Chat“ vollständig abgearbeitet ist. In diesem Fall wird die vollständig abgearbeitete Zeichenfolge als die „kleinere“ deklariert. Es gilt also die Beziehung

    "Chat" < "Chaton".

Vergleichsfunktionen für zwei Zeichenfolgen

Man kann die Vergleichsoperatoren == und != verwenden, um zu prüfen, ob zwei Zeichenfolgen gleich sind oder nicht, oder aber die Methode Equals der Klasse System.String. Für die Beziehungen < <= > >= muss die Methode “ CompareTo der Klasse System.String verwendet werden:


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);
        }
    }
}

In Zeile 7 erhält die Variable i den Wert:

0 , wenn die beiden Zeichenfolgen identisch sind

1 wenn Zeichenfolge Nr. 1 > Zeichenfolge Nr. 2

-1 wenn Zeichenfolge Nr. 1 < Zeichenfolge Nr. 2

Zeile 8: Die Variable egal nimmt den Wert true an, wenn die beiden Zeichenfolgen gleich sind, andernfalls den Wert false. Zeile 10: Mit den Operatoren == und != wird geprüft, ob zwei Zeichenfolgen gleich sind oder nicht.

Die Ergebnisse der Ausführung:

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

3.3.5.5. Boolesche Ausdrücke

Die verwendbaren Operatoren sind AND (&&) OR (||) NOT (!). Das Ergebnis eines booleschen Ausdrucks ist ein Boolescher Wert.

Prioritäten der Operatoren:

  1. !
  2. &&
  3. ||

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

Relationale Operatoren haben Vorrang vor den Operatoren && und ||.

3.3.5.6. Bitweise Verarbeitung

Die Operatoren

Seien i und j zwei ganze Zahlen.

i<<n
verschiebt i um n Bits nach links. Die eingehenden Bits sind Nullen.
i>>n
verschiebt i um n Bits nach rechts. Ist i eine vorzeichenbehaftete Ganzzahl (signed char, int, long), bleibt das Vorzeichenbit erhalten.
i & j
führt die bitweise logische Verknüpfung von i und j durch (ET).
i | j
führt die bitweise logische Verknüpfung von i und j gemäß OU durch.
~i
komplementiert i auf 1
i^j
führt die Operation OU EXCLUSIF mit i und j durch

Nehmen wir folgenden Code:


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));
  • Das Format {0:x4} zeigt den Parameter Nr. 0 im Hexadezimalformat (x) mit 4 Zeichen (4) an.

Die Ergebnisse der Ausführung lauten wie folgt:

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. Kombination von Operatoren

a=a+b kann als a+=b geschrieben werden

a=a-b kann als a-=b geschrieben werden

Das Gleiche gilt für die Operatoren /, %, *, <<, >>, &, |, ^. Somit lässt sich a=a/2; als a/=2; schreiben

3.3.5.8. Inkrementierungs- und Dekrementierungsoperatoren

Die Schreibweise variable++ bedeutet variable=variable+1 oder auch variable+=1

Die Schreibweise variable-- bedeutet variable=variable-1 oder auch variable-=1.

3.3.5.9. Der ternäre Operator ?

Der Ausdruck

    expr_cond ? expr1:expr2

wird wie folgt berechnet:

1 Der Ausdruck „expr_cond“ wird ausgewertet. Es handelt sich um einen bedingten Ausdruck, dessen Wert entweder „vrai“ oder „faux“ ist

2 Ist sie wahr, ist der Wert des Ausdrucks der von expr1, und expr2 wird nicht ausgewertet.

3 Ist sie falsch, geschieht das Gegenteil: Der Wert des Ausdrucks ist der von expr2, und expr1 wird nicht ausgewertet.

Die Operation i=(j>4 ? j+1:j-1); weist der Variablen i den Wert j+1 zu, wenn j>4 gilt, andernfalls j-1. Das entspricht dem Ausdruck if(j>4) i=j+1; else i=j-1;, ist jedoch prägnanter.

3.3.5.10. Allgemeine Operatorpriorität

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

gd gibt an, dass bei gleicher Priorität die Priorität von links nach rechts gilt. Das bedeutet, dass in einem Ausdruck, in dem Operatoren mit gleicher Priorität vorkommen, der Operator ganz links im Ausdruck zuerst ausgewertet wird. dg gibt eine Priorität von rechts nach links an.

3.3.5.11. Typumwandlungen

In einem Ausdruck ist es möglich, die Darstellung eines Wertes vorübergehend zu ändern. Dies wird als Typumwandlung oder auf Englisch als „type casting“ bezeichnet. Die Syntax für die Typumwandlung eines Wertes in einem Ausdruck lautet wie folgt:

    (type) valeur

Der Wert nimmt dann den angegebenen Typ an. Dies führt zu einer Änderung der Kodierung des Werts.


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);
        }
    }
}
  • In Zeile 7 hat f1 den Wert 0,0. Die Division 3/4 ist eine ganzzahlige Division, da beide Operanden vom Typ int sind.
  • Zeile 8: (float)i ist der Wert von i, der in float umgewandelt wurde. Nun liegt eine Division zwischen einer reellen Zahl vom Typ float und einer ganzen Zahl vom Typ int vor. Es wird dann die Division zwischen reellen Zahlen durchgeführt. Der Wert von j wird ebenfalls in den Typ float umgewandelt, anschließend erfolgt die Division der beiden reellen Zahlen. f2 hat dann den Wert 0,75.

Hier sind die Ergebnisse der Ausführung:

f1=0, f2=0,75

Bei der Operation (float)i:

  • i ist ein exakt auf 2 Byte codierter Wert
  • (Float) i ist derselbe Wert, der als 4-Byte-Gleitkommazahl (Real) approximativ codiert ist

Es findet also eine Typumwandlung des Werts von i statt. Diese Typumwandlung erfolgt nur für die Dauer einer Berechnung, wobei die Variable i stets ihren Typ int beibehält.

3.4. Anweisungen zur Steuerung des Programmablaufs

3.4.1. Anhalten

Die in der Klasse Environment definierte Methode Exit ermöglicht es, die Ausführung eines Programms anzuhalten.

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

Exit beendet den laufenden Prozess und gibt die Kontrolle an den aufrufenden Prozess zurück. Der Wert von status kann von diesem verwendet werden. Unter DOS wird diese Statusvariable in die Systemvariable ERRORLEVEL zurückgegeben, deren Wert in einer Batch-Datei geprüft werden kann. Unter Unix wird mit der Bourne-Shell der Wert von status in der Variablen $? gespeichert.

    Environment.Exit(0);

beendet die Ausführung des Programms mit einem Statuswert von 0.

3.4.2. Einfache Auswahlstruktur

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

Hinweise:

  • Die Bedingung steht in Klammern.
  • Jede Aktion endet mit einem Semikolon.
  • Die geschweiften Klammern werden nicht mit einem Semikolon abgeschlossen.
  • Geschweifte Klammern sind nur erforderlich, wenn mehr als eine Aktion vorhanden ist.
  • Die Klausel else kann fehlen.
  • Es gibt keine Klausel then.

Das algorithmische Äquivalent dieser Struktur ist die if-then-else-Struktur:

Beispiel


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

Entscheidungsstrukturen können verschachtelt werden:

if(condition1)
if (condition2)
        {......}
      else         //Bedingung2
         {......}
else         //Bedingung1
     {.......}

Manchmal tritt folgendes Problem auf:


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");
        }
    }
}

Auf welches if bezieht sich im vorigen Beispiel das else in Zeile 10? Die Regel lautet, dass sich ein else immer auf das nächstgelegene if bezieht: im Beispiel also auf if(n>6), in Zeile 8. Betrachten wir ein weiteres Beispiel:


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

Hier wollten wir ein else dem if(n2>1) zuordnen und kein else dem if(n2>6). Aufgrund der vorherigen Anmerkung sind wir gezwungen, geschweifte Klammern in if(n2>1) {...} else ... zu setzen.

3.4.3. Fallstruktur

Die Syntax lautet wie folgt:

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

Hinweise

  • Der Wert des Steuerausdrucks von switch kann eine Ganzzahl, ein Zeichen oder eine Zeichenfolge sein
  • Der Steuerausdruck steht in Klammern.
  • Die Klausel default kann fehlen.
  • Die Werte vi sind mögliche Werte des Ausdrucks. Hat der Ausdruck den Wert vi, werden die Aktionen hinter der case-Klausel vi ausgeführt.
  • Die Anweisung break verlässt die case-Struktur.
  • Jeder Anweisungsblock, der mit dem Wert vi verknüpft ist, muss mit einer Verzweigungsanweisung (break, goto, return, …) enden, andernfalls meldet der Compiler einen Fehler.

Beispiel

In der Algorithmik

                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;
}
}// Ende von Main

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

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

3.4.4. Wiederholungsstrukturen

3.4.4.1. Bekannte Anzahl der Wiederholungen

„for“-Struktur

Die Syntax lautet wie folgt:


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

Hinweise

  • Die drei Argumente von for stehen in Klammern und sind durch Semikolons getrennt.
  • Jede Aktion von for endet mit einem Semikolon.
  • Die geschweifte Klammer ist nur erforderlich, wenn mehr als eine Aktion vorhanden ist.
  • Auf die geschweifte Klammer folgt kein Semikolon.

Das algorithmische Äquivalent ist die Struktur pour:

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

was sich in eine Struktur tantque übersetzen lässt:

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

foreach-Struktur

Die Syntax lautet wie folgt:


foreach (Type variable in collection)
    instructions; 
}

Hinweise

  • collection ist eine durchzählbare Objektsammlung. Die uns bereits bekannte durchzählbare Objektsammlung ist das Array
  • Type ist der Typ der Objekte in der Sammlung. Bei einem Array wäre dies der Typ der Array-Elemente
  • variable ist eine lokale Variable der Schleife, die nacheinander alle Werte der Sammlung annimmt.

So sieht der folgende Code aus:


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

würde Folgendes anzeigen:

paul
hélène
jacques
sylvie

3.4.4.2. Anzahl der Wiederholungen unbekannt

In C# gibt es zahlreiche Strukturen für diesen Fall.

While-Schleife


    while(condition){
          actions;
        } 

Die Schleife wird so lange ausgeführt, wie die Bedingung erfüllt ist. Es kann sein, dass die Schleife nie ausgeführt wird.

Hinweise:

  • Die Bedingung steht in Klammern.
  • Jede Aktion wird mit einem Semikolon abgeschlossen.
  • Die geschweifte Klammer ist nur erforderlich, wenn mehr als eine Aktion vorhanden ist.
  • Auf die geschweifte Klammer folgt kein Semikolon.

Die entsprechende algorithmische Struktur ist die „solange“-Struktur:

tantque condition
    actions
fintantque

Wiederholungsstruktur bis (do while)

Die Syntax lautet wie folgt:


    do{
       instructions;
    }while(condition); 

Die Schleife wird so lange wiederholt, bis die Bedingung falsch wird. In diesem Fall wird die Schleife mindestens einmal durchlaufen.

Hinweise

  • Die Bedingung steht in Klammern.
  • Jede Aktion wird mit einem Semikolon abgeschlossen.
  • Die geschweifte Klammer ist nur erforderlich, wenn mehr als eine Aktion vorhanden ist.
  • Auf die geschweifte Klammer folgt kein Semikolon.

Die entsprechende algorithmische Struktur ist die „Wiederhole … bis“-Struktur:

répéter
    actions
jusqu'à condition

Struktur für „for“

Die Syntax lautet wie folgt:


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

Die Schleife wird so lange durchlaufen, wie die Bedingung wahr ist (sie wird vor jedem Schleifendurchlauf ausgewertet). Instructions_départ werden vor dem ersten Eintritt in die Schleife ausgeführt. Instructions_fin_boucle werden nach jedem Schleifendurchlauf ausgeführt.

Hinweise

  • Die verschiedenen Anweisungen in instructions_depart und instructions_fin_boucle sind durch Kommas getrennt.

Die entsprechende algorithmische Struktur sieht wie folgt aus:

instructions_départ
tantque condition
    actions
    instructions_fin_boucle
fintantque

Beispiele

Die folgenden Codeausschnitte berechnen alle die Summe der ersten 10 ganzen Zahlen.

            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. Anweisungen zur Schleifenverwaltung

break
beendet die for-, while- oder do ... while-Schleife.
continue
springt zur nächsten Iteration der Schleifen „for“, „while“ und „do ... while“

3.5. Ausnahmebehandlung

Viele C#-Funktionen können Ausnahmen, d. h. Fehler, auslösen. Wenn eine Funktion eine Ausnahme auslösen kann, sollte der Programmierer diese behandeln, um fehlerresistentere Programme zu erhalten: Ein unkontrollierter „Absturz“ der Anwendung muss stets vermieden werden.

Die Behandlung einer Ausnahme erfolgt nach folgendem Schema:

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

Wenn die Funktion keine Ausnahme auslöst, wird zur nächsten Anweisung übergegangen; andernfalls wird der Hauptteil der Klausel catch aufgerufen und anschließend zur nächsten Anweisung übergegangen. Beachten Sie folgende Punkte:

  • e ist ein Objekt vom Typ „Exception“ oder einem davon abgeleiteten Typ. Man kann genauer sein, indem man Typen wie IndexOutOfRangeException, FormatException, SystemException usw. verwendet: Es gibt mehrere Ausnahmetypen. Mit der Schreibweise `catch (Exception e)` geben wir an, dass wir alle Ausnahmetypen behandeln wollen. Wenn der Code der Klausel `try` mehrere Ausnahmetypen erzeugen kann, möchten wir möglicherweise genauer sein und die Ausnahme mit mehreren Klauseln `catch` behandeln:
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
  • Den Klauseln try/catch kann eine finally-Klausel hinzugefügt werden:
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

Unabhängig davon, ob eine Ausnahme vorliegt oder nicht, wird der Code der Klausel finally immer ausgeführt.

  • In der Klausel catch möchte man möglicherweise das verfügbare Objekt Exception nicht verwenden. Anstatt catch (Exception e){..} zu schreiben, schreibt man dann catch(Exception){...} oder einfacher catch {...}.
  • Die Klasse Exception verfügt über eine Eigenschaft „Message“, die eine detaillierte Meldung zum aufgetretenen Fehler enthält. Wenn man diesen anzeigen möchte, schreibt man daher:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
    ...
}//catch
  • Die Klasse Exception verfügt über eine Methode ToString, die eine Zeichenkette zurückgibt, die den Typ der Ausnahme sowie den Wert der Eigenschaft Message angibt. Man kann also Folgendes schreiben:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
    ...
}//catch

Man kann auch schreiben:

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

Der Compiler weist dem Parameter {0} den Wert ex.ToString() zu.

Das folgende Beispiel zeigt eine Ausnahme, die durch die Verwendung eines nicht vorhandenen Array-Elements ausgelöst wird:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
            // Deklaration und Initialisierung eines Arrays
            int[] tab = { 0, 1, 2, 3 };
            int i;
            // Ausgabe des Arrays mit einer „for“-Schleife
            for (i = 0; i < tab.Length; i++)
                Console.WriteLine("tab[{0}]={1}", i, tab[i]);
            // Anzeige eines Arrays mit einer „for each“-Schleife
            foreach (int élmt in tab) {
                Console.WriteLine(élmt);
            }
            // Auslösen einer Ausnahme
            try {
                tab[100] = 6;
            } catch (Exception e) {
                Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
                return;
            }//try-catch
            finally {
                Console.WriteLine("finally ...");
            }
        }
    }
}

Im obigen Beispiel löst Zeile 18 eine Ausnahme aus, da das Array tab kein Element Nr. 100 enthält. Die Ausführung des Programms liefert folgende Ergebnisse:

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:Zeile 7
finally ...
  • Zeile 9: Die Ausnahme [System.IndexOutOfRangeException] ist aufgetreten
  • Zeile 11: Die Anweisung finally (Zeilen 23–25) des Codes wurde ausgeführt, obwohl in Zeile 21 eine Anweisung return zum Verlassen der Methode vorhanden war. Es ist zu beachten, dass die Anweisung finally immer ausgeführt wird.

Hier ist ein weiteres Beispiel, in dem die Ausnahme behandelt wird, die durch die Zuweisung einer Zeichenkette zu einer Variablen vom Typ „Ganzzahl“ ausgelöst wird, wenn die Zeichenkette keine Ganzzahl darstellt:


using System;

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

            // Beispiel 2
            // Der Name wird abgefragt
            Console.Write("Nom : ");
            // Antwort vorlesen
            string nom = Console.ReadLine();
            // Nach dem Alter wird gefragt
            int age = 0;
            bool ageOK = false;
            while (!ageOK) {
                // Frage
                Console.Write("âge : ");
                // Antwort lesen und überprüfen
                try {
                    age = int.Parse(Console.ReadLine());
                    ageOK = age>=1;
                } catch {
                }//try-catch
                if (!ageOK) {
                    Console.WriteLine("Age incorrect, recommencez...");
                }
            }//while
            // Abschließende Anzeige
            Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
        }
    }
}
  • Zeilen 15–27: Die Schleife zur Eingabe des Alters einer Person
  • Zeile 20: Die über die Tastatur eingegebene Zeichenfolge wird durch die Methode int.Parse in eine Ganzzahl umgewandelt. Diese Methode löst eine Ausnahme aus, wenn die Konvertierung nicht möglich ist. Aus diesem Grund wurde die Operation in einen try/catch-Block gesetzt.
  • Zeilen 22–23: Wird eine Ausnahme ausgelöst, springt die Ausführung in den `catch`-Block, wo nichts geschieht. Somit bleibt die boolesche Variable `ageOK`, die in Zeile 14 auf `false` gesetzt wurde, auf dem Wert `false`.
  • Zeile 21: Wenn diese Zeile erreicht wird, ist die Konvertierung von String zu int erfolgreich verlaufen. Es wird jedoch überprüft, ob die erhaltene Ganzzahl größer oder gleich 1 ist.
  • Zeilen 24–26: Wenn das Alter falsch ist, wird eine Fehlermeldung ausgegeben.

Einige Ausführungsergebnisse:

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. Beispielanwendung – V1

Wir wollen ein Programm schreiben, mit dem sich die Steuer eines Steuerpflichtigen berechnen lässt. Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat (Zahlen von 2004 für Einkünfte aus dem Jahr 2003):

  • Die Anzahl der Anteile des Arbeitnehmers wird berechnet: nbParts = nbEnfants/2 + 1, wenn er unverheiratet ist, nbEnfants/2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl seiner Kinder ist.
  • Wenn er mindestens drei Kinder hat, erhält er einen halben Anteil zusätzlich
  • Sein zu versteuerndes Einkommen R wird wie folgt berechnet: R = 0,72 * S, wobei S sein Jahresgehalt ist
  • Man berechnet seinen Familienkoeffizienten QF = R / nbParts
  • Man berechnet seine Steuer I. Betrachten wir die folgende Tabelle:
4262
0
0
8382
0,0683
291,09
14753
0,1914
1322,92
23.888
0,2826
2668,39
38.868
0,3738
4846,98
47.932
0,4262
6883,66
0
0,4809
9505,54

Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, wird die erste Zeile gesucht, in der QF <= Feld 1 gilt. Wenn beispielsweise QF = 5000 ist, wird die Zeile

    8382        0.0683        291.09

Die Steuer I beträgt dann 0,0683*R – 291,09*nbParts. Wenn QF so gewählt ist, dass die Bedingung QF <= Feld1 niemals erfüllt ist, werden die Koeffizienten der letzten Zeile verwendet. In diesem Fall:

0 0,4809 9505,54

was die Steuer I = 0,4809 * R – 9505,54 * nbParts ergibt.

Das entsprechende C#-Programm lautet wie folgt:


using System;

namespace Chap1 {
    class Impots {
        static void Main(string[] args) {
            // für die Steuerberechnung erforderliche Datentabellen
            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 };

            // Abfrage des Familienstands
            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";

            // Anzahl der Kinder
            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

            // Gehalt
            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

            // Berechnung der Anzahl der Anteile
            decimal nbParts;
            if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;

            // steuerpflichtiges Einkommen
            decimal revenu = 0.72M * salaire;

            // Familienquotient
            decimal QF = revenu / nbParts;

            // Ermittlung der Steuerklasse entsprechend QF
            int i;
            int nbTranches = limites.Length;
            limites[nbTranches - 1] = QF;
            i = 0;
            while (QF > limites[i]) i++;
            // die Steuer
            int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);

            // Das Ergebnis wird angezeigt
            Console.WriteLine("Impôt à payer : {0} euros", impots);
        }
    }
}
  • Zeilen 7–9: Die numerischen Werte werden mit dem Suffix „M“ (Money) versehen, damit sie vom Typ decimal sind.
  • Zeile 16:
    • Console.ReadLine() gibt die über die Tastatur eingegebene Zeichenfolge C1 zurück
    • C1.Trim() entfernt die Leerzeichen am Anfang und am Ende von C1 – gibt die Zeichenfolge C2 zurück
    • C2.ToLower() gibt die Zeichenfolge C3 zurück, bei der es sich um die in Kleinbuchstaben umgewandelte Zeichenfolge C2 handelt.
  • Zeile 21: Der Boolesche Wert marie erhält den Wert true oder false aus der Relation reponse=="o"
  • Zeile 29: Die über die Tastatur eingegebene Zeichenkette wird in den Typ int umgewandelt. Wenn die Umwandlung fehlschlägt, wird eine Ausnahme ausgelöst.
  • Zeile 30: Der Boolesche Wert OK erhält den Wert true oder false aus der Relation nbEnfants>=0
  • Zeilen 55–56: Man kann nicht einfach nbEnfants/2 schreiben. Wäre nbEnfants gleich 3, hätte man 3/2, eine ganzzahlige Division, die 1 und nicht 1,5 ergeben würde. Daher schreibt man (decimal)nbEnfants, um einen der Operanden der Division in eine reelle Zahl umzuwandeln und so eine Division zwischen reellen Zahlen zu erhalten.

Hier sind einige Ausführungsbeispiele:

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. Argumente des Hauptprogramms

Die Hauptfunktion Main kann als Parameter ein String-Array akzeptieren: String[] (oder string[]). Dieses Array enthält die Argumente der Befehlszeile, mit der die Anwendung gestartet wurde. Wenn man also das Programm P mit dem folgenden (DOS-)Befehl startet:


        P arg0 arg1 … argn

und die Funktion Main wie folgt deklariert ist:

public static void Main(string[] args)

dann gilt: args[0]="arg0", args[1]="arg1" … Hier ein Beispiel:


using System;

namespace Chap1 {
    class P10 {
        static void Main(string[] args) {
            // die übergebenen Parameter werden aufgelistet
            Console.WriteLine("Il y a  " + args.Length + " arguments");
            for (int i = 0; i < args.Length; i++) {
                Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
            }
        }
    }
}

Um Argumente an den ausgeführten Code zu übergeben, geht man wie folgt vor:

  • in [1]: Rechtsklick auf das Projekt / Eigenschaften
  • in [2]: Registerkarte [Debug]
  • in [3]: Argumente eingeben

Die Ausführung liefert folgende Ergebnisse:

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

Es ist zu beachten, dass die Signatur

public static void Main()

gültig ist, wenn die Funktion Main keine Parameter erwartet.

3.8. Enumerationen

Eine Aufzählung ist ein Datentyp, dessen Wertebereich aus einer Menge ganzzahliger Konstanten besteht. Betrachten wir ein Programm, das Prüfungsanmeldungen verwalten soll. Es gäbe fünf davon: Passable, AssezBien, Bien, TrèsBien, Excellent.

Man könnte also eine Aufzählung für diese fünf Konstanten definieren:


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

Intern werden diese fünf Konstanten durch aufeinanderfolgende Ganzzahlen kodiert, beginnend mit 0 für die erste Konstante, 1 für die nächste usw. Eine Variable kann so deklariert werden, dass sie diese Werte aus der Aufzählung annimmt:


            // eine Variable, deren Werte aus der Aufzählung „Angaben“ stammen
Mentions maMention = Mentions.Passable;

Man kann eine Variable mit den verschiedenen möglichen Werten der Aufzählung vergleichen:


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

Man kann alle Werte der Aufzählung abrufen:


            // Liste der Angaben in Form von Zeichenketten
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
}

Genauso wie der einfache Typ int der Struktur System.Int32 entspricht, entspricht der einfache Typ enum der Struktur System.Enum. Diese Struktur verfügt über eine statische Methode GetValues, mit der alle Werte eines Aufzählungstyps abgerufen werden können, der als Parameter übergeben wird. Dieser muss ein Objekt vom Typ „Type“ sein, bei dem es sich um eine Klasse handelt, die Informationen über den Typ eines Datums enthält. Der Typ einer Variablen v wird über v.GetType() ermittelt. Der Typ eines Typs T wird über typeof(T) ermittelt. Hier liefert also maMention.GetType() das Objekt Type aus der Aufzählung Mentions und Enum.GetValues(maMention.GetType()) die Liste der Werte der Aufzählung Mentions.

Wenn man nun schreibt


            //Liste der Einträge in Form von Ganzzahlen
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
}

Zeile 2, ist die Schleifenvariable vom Typ Ganzzahl. Man erhält dann die Liste der Werte der Aufzählung in Form von Ganzzahlen. Das Objekt vom Typ System.Type, das dem Datentyp Mentions entspricht, wird durch typeof(Mentions) erhalten. Man hätte wie zuvor maMention.GetType() schreiben können.

Das folgende Programm veranschaulicht das eben Beschriebene:


using System;

namespace Chap1 {
    class P11 {
        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
        static void Main(string[] args) {
            // eine Variable, deren Werte aus der Aufzählung „Mentions“ stammen
            Mentions maMention = Mentions.Passable;
            // Anzeige des Variablenwerts
            Console.WriteLine("mention=" + maMention);
            // Test mit einem Wert aus der Aufzählung
            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
            }
            // Liste der Einträge in Form von Zeichenketten
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
            }
            //Liste der Einträge in Form von Ganzzahlen
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
            }
        }
    }
}

Die Ausführungsergebnisse lauten wie folgt:

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

3.9. Übergabe von Parametern an eine Funktion

Wir befassen uns hier mit der Art und Weise, wie Parameter an eine Funktion übergeben werden. Betrachten wir die folgende statische Funktion:


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

In der Funktionsdefinition, Zeile 1, wird „a“ als formaler Parameter bezeichnet. Er dient ausschließlich den Zwecken der Definition der Funktion changeInt. Er hätte genauso gut b heißen können. Betrachten wir nun eine Verwendung dieser Funktion:


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

Hier in der Anweisung in Zeile 3, ChangeInt(age), ist age der effektive Parameter, der seinen Wert an den formalen Parameter a übergibt. Uns interessiert, wie ein formaler Parameter den Wert eines effektiven Parameters übernimmt.

3.9.1. Wertübergabe

Das folgende Beispiel zeigt, dass die Parameter einer Funktion standardmäßig per Wert übergeben werden, d. h., der Wert des effektiven Parameters wird in den entsprechenden formalen Parameter kopiert. Es handelt sich um zwei getrennte Einheiten. Wenn die Funktion den formalen Parameter ändert, bleibt der effektive Parameter davon unberührt.


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);
        }
    }
}

Die Ergebnisse lauten wie folgt:

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

Der Wert 20 des tatsächlichen Parameters age wurde in den formalen Parameter a kopiert (Zeile 10). Dieser wurde anschließend geändert (Zeile 11). Der effektive Parameter blieb dabei unverändert. Diese Übergabemethode eignet sich für Eingabeparameter einer Funktion.

3.9.2. Übergabe per Referenz

Bei einer Übergabe per Referenz sind der effektive Parameter und der formale Parameter ein und dasselbe. Wenn die Funktion den formalen Parameter ändert, wird auch der effektive Parameter geändert. In C# müssen beiden das Schlüsselwort ref vorangestellt werden:

Hier ein Beispiel:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // Beispiel 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);
        }
    }
}

und die Ausführungsergebnisse:

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

Der effektive Parameter hat die Änderung des formalen Parameters übernommen. Diese Übergabemethode eignet sich für Ausgabeparameter einer Funktion.

3.9.3. Übergabe per Referenz mit dem Schlüsselwort „out“

Betrachten wir das vorherige Beispiel, in dem die Variable age2 vor dem Aufruf der Funktion changeInt nicht initialisiert würde:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // Beispiel 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);
        }
    }
}

Beim Kompilieren dieses Programms tritt ein Fehler auf:

    Use of unassigned local variable 'age2'

Dieses Problem lässt sich umgehen, indem man age2 einen Anfangswert zuweist. Man kann auch das Schlüsselwort „ref“ durch das Schlüsselwort „out“ ersetzen. Damit wird ausgedrückt, dass der Parameter ausschließlich ein Ausgabeparameter ist und daher keinen Anfangswert benötigt:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // Beispiel 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);
        }
    }
}

Die Ergebnisse der Ausführung lauten wie folgt:

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