Arbeiten mit .NET: C-Sharp/ Arbeitsablauf/ Anweisungen/ Ein- und Ausgabe

Aus Wikibooks
Zur Navigation springen Zur Suche springen

Notwendigkeit der Ein- und Ausgabe[Bearbeiten]

Ein Algorithmus zur Lösung eines Problems bedarf i.d.R.

  • einer oder mehrerer Eingaben, die das Problem genauer spezifizieren
  • einer oder mehrerer Ausgaben, die die Lösung des spezifizierten Problems darstellen.

Als ein sehr einfaches Beispiel dazu könnte man die Berechnung eines Endkapitals ausgehend von einem Anfangskapital, einer Laufzeit und einem Zinssatz anführen. Um das Endkapital zu berechnen, benötigt der Algorithmus die Werte für das Anfangskapital, die Laufzeit und den Zinssatz als Eingaben. Das berechnete Endkapital würde dann als Ergebnis die Ausgabe des Algorithmus darstellen.

Anfangskapital (EUR)  : 1000

Laufzeit (Jahre)  : 7

Zinssatz (% p.a.)  : 1.2

Endkapital (EUR)  : 2.210,68 EUR

Methoden zur Ein- und Ausgabe[Bearbeiten]

Bei C#-Programmen in Form von Konsolanwendungen erfolgen Ein- und Ausgaben in den allermeisten Fällen auf der Konsole, bestehend aus Bildschirm (Ausgaben) und Tastatur (Eingaben). Dementsprechend gibt es dafür in C# spezielle Methoden, die in der Klasse Console zu finden sind.

Für eine Eingabe stehen in C# folgende Methoden zur Verfügung:

* public static string Console.ReadLine()
* public static int Console.Read()
* public static ConsoleKeyInfo Console.ReadKey()

Zur Ausgabe stehen in C# folgende Methoden zur Verfügung:

* public static Console.Write(<Parameterliste>)
* public static Console.WriteLine(<Parameterliste>)

Die Eingabemethoden liefern allesamt ein Ergebnis zurück, sind also wie eine Funktion zu sehen. Die Ausgabemethoden liefern dagegen kein Ergebnis zurück, sie sind also wie eine Prozedur zu verstehen. Die Eingabemethoden haben keine, die Ausgabemethoden können Parameter aufweisen. Hier kommen zuvörderst die auszugebenden Werte in Betracht. Alle Methoden sind zudem Klassenmethoden (static) und öffentlich (public).

Anwendungsbeispiele für Eingaben[Bearbeiten]

string Console.ReadLine()[Bearbeiten]

Verwendung[Bearbeiten]

Diese Methode verhält sich bei ihrem Aufruf zur Laufzeit wie folgt:

  1. Die Ausführung des Programms wird unterbrochen.
  2. Auf dem Bildschirm wird die Eingabebereitschaft durch einen blinkenden Cursor signalisiert.
  3. Alle durch den Benutzer des Programms eingegebene Zeichen werden auf dem Bildschirm angezeigt.
  4. Die Eingabe wird mittels der Returntaste beendet.
  5. Alle eingegebenen Zeichen werden abschließend über den Rückgabewert der Methode als Zeichenkette zur Verfügung gestellt.
  6. Der Tastaturpuffer wird gelöscht (die Zeichen Return werden entfernt).
  7. Das Programm wird danach mit der auf die ReadLine()- folgenden Methode oder Anweisung fortgesetzt.

Beispiel[Bearbeiten]

string eingabe;
eingabe = Console.ReadLine();
/* usw. */

Nach Durchführung dieser Anweisungen steht die vom Benutzer eingegebene Zeichenfolge in der Variablen eingabe zur Verfügung.

Besondere Problematik der ReadLine()-Methode[Bearbeiten]

Die ReadLine()-Methode ist immer dann ohne Probleme zu benutzen, wenn es sich bei den einzugebenden Daten um Daten der Klasse string handelt. Sollen Daten anderer Typen eingegeben werden, muss nach der Eingabe zwingend eine Typumwandlung stattfinden, bei der die eingegebenen Zeichen in den entsprechenden Zieltyp umgewandelt werden müssen. Dazu stehen in C# verschiedene Methoden zur Verfügung, die in drei Arten unterteilt werden können:

  1. Statische Klassenmethoden der Klasse Convert (ToInt, ToDouble, ToByte, ...)
  2. Statische Klassenmethode Parse der jeweiligen Klasse des Zieldatentyps
  3. Statische Klassenmethode TryParse der jeweiligen Klasse des Zieldatentyps

Alle Arten sind gleichermaßen geeignet, aus der eingegebenen Zeichenkette einen Wert des Zieldatentyps zu erzeugen. Erläutert sei dies an dem bereits eingangs dargestellten Beispiel.

In diesem Beispiel soll als erstes der Wert des Anfangskapitals eingelesen werden. Double ist ein für diesen Wert sinnvoller Datentyp, da er nichtganzzahlige Werte umfasst. Um nun als Ergebnis der Eingabe nicht eine Zeichenkette, sondern einen Doublewert zu erhalten, sind folgende Schritte notwendig:

  1. Einlesen und Speicherung einer numerischen Zeichenkette mittels Console.ReadLine()-Methode.
  2. Umwandlung der eingelesenen Zeichenkette in einen Doublewert (Klassenmethode aus Convert oder aus Double).

Das nachfolgende Beispiel zeigt diese Vorgehensweise.

string eingabe;
eingabe = Console.ReadLine();
/* Klassenmethode ToDouble() der Klasse Convert */
double anfangskapital = Convert.ToDouble(eingabe);
/* alternativ auch Klassenmethode static double Parse(<string> der Klasse Double */
double anfangskapital = Double.Parse(eingabe);

Die gezeigten Anweisungen lassen sich noch weiter vereinfachen, wodurch auf die Variable eingabe verzichtet werden kann:

/* Klassenmethode ToDouble() der Klasse Convert */
double anfangskapital = Convert.ToDouble(Console.ReadLine());
/* alternativ auch Klassenmethode static double Parse(<string> der Klasse Double */
double anfangskapital = Double.Parse(Console.ReadLine());

Somit stehen nach Eingabe und Konvertierung typgerechte Werte zur weiteren Verarbeitung zur Verfügung.

Aus der Notwendigkeit der Typumwandlung nach der Eingabe von numerischen Werten ergibt sich allerdings ein weiteres Problem: Was passiert, wenn sich die vom Benutzer eingegebene Zeichenfolge nicht in einen numerischen Wert umwandeln lässt? Zunächst einmal resultiert eine solche Fehleingabe (z.B. wird auf die Fragen nach den Anfangskapital der Wert abc eingegeben) in einem Laufzeitfehler, wodurch das Programm mit einer Fehlermeldung abgebrochen wird. Dies ist aus Sicht des Benutzers ärgerlich, insbesondere wenn er bereits zuvor viele Eingaben vorgenommen hat, die nun verloren sind. Hier lässt sich aber gewinnbringend die Fehlerbehandlung (exception handling) von C# einsetzen. Die entsprechenden Sprachelemente dazu sind try, except und ggfs. auch finally. Diese markieren innerhalb eines Quelltextes besondere Bereiche (Blöcke), in denen auf das Laufzeitverhalten eines Programms im Fehlerfalle Einfluss genommen werden kann. Im Prinzip sieht eine solche Struktur wie folgt aus:

  1. Schaffung eines geschützten Anweisungsbereichs, in dem Laufzeitfehler nicht in einem Programmabbruch enden (try-Block).
  2. Behandlung des Fehlers in einem sich anschließenden Block (catch-Block).
  3. Tritt in einer Anweisung des try-Blocks ein Fehler auf, so wird unmittelbar in den catch-Block verzweigt.
  4. Tritt in einer Anweisung des try-Blocks kein Fehler auf, so wird an dessen Ende hinter den catch-Block verzweigt.
try
{ 
   /* fehleranfällige Anweisungen */
   /* führen hier nicht zum Programmabbruch */
   /* sondern zur sofortigen Fortsetzung im catch-Block */
}
catch (Exception e)
{
   /* Behandlung möglicher Fehler oder Ausgabe der Fehlermeldung e.Message */
   /* tritt kein Fehler auf, wird der Block übersprungen */
}
/* Fortsetzung des Programms */

Im Zusammenhang mit einer Schleife lässt sich daraus eine Eingabeanweisung erzeugen, die nicht durch Fehleingaben zum Abbruch führen kann. Exemplarisch könnte dies für die Eingabe des Anfangskapitals im Eingangsbeispiel wie folgt aussehen:

bool ok = false;
double anfangskapital;
while (!ok)
{
   try
   {
      Console.Write("Anfangskapital (EUR): ");
      anfangskapital = Convert.ToDouble(Console.ReadLine());
      // Wenn hier angekommen, ist alles gut gegangen
      ok =  true;
   }
   catch (Exception e)
   {
      Console.WriteLine(e.Message);
      // wenn hier angekommen, ist etwas schief gegangen
      ok = false;
   }
}
/* Fortsetzung des Programms */

Es wird also die Eingabeaufforderung und die Eingabe solange wiederholt, bis, angezeigt durch den Wert von ok, kein Eingabefehler mehr aufgetreten ist.

Ein weiteres häufig auftretendes Problem ist die Frage, ob bei der Eingabe nicht ganzzahliger Werte die Dezimaltrennung durch einen Punkt oder durch ein Komma erfolgen muss. Dabei besteht zwischen der Eingabe und der Verwendung nicht ganzzahliger Literale ein wichtiger Unterschied: Bei der Verwendung als Literal ist die Trennung mittels eines Punkts (23.67), bei Verwendung als Eingabe in eine Variable jedoch ein Komma (23,67) zu verwenden. Dieses Problem wäre jedoch einfach zulösen, wenn man nach der Eingabe und vor der Konvertierung den in der Zeichenkette evtl. fälschlicherweise eingegebenen Punkt einfach durch ein Komma ersetzt und erst dann die Konvertierung vornimmt. Dazu kann die Instanzmethode der Klasse String string Replace(oldchar, newchar) zum Einsatz kommen. Diese liefert als Ergebnis die Zeichenkette, in der alle Vorkommen von oldchar durch newchar erstetzt wurden. Aus "23.56".Replace('.', ',') wird damit "23,56". Die Vorgehensweise zeigt folgendes kleines Beispiel:

bool ok = false;
double anfangskapital;
while (!ok)
{
   try
   {
      Console.Write("Anfangskapital (EUR): ");
      anfangskapital = Convert.ToDouble(Console.ReadLine().Replace('.', ','));
      /* Alternativ: anfangskapital = Double.Parse(Console.ReadLine().Replace('.', ',')); */
      // Wenn hier angekommen, ist alles gut gegangen
      ok =  true;
   }
   catch (Exception e)
   {
      Console.WriteLine(e.Message);
      // wenn hier angekommen, ist etwas schief gegangen
      ok = false;
   }
}
/* Fortsetzung des Programms */

Neben den Klassenmethoden der Klasse Convert und der Methoden Parse der jeweiligen Zieldatentypen existiert eine weitere Methode der Klassen der Zieldatentypen, die die Bearbeitung von Eingabefehlern vereinfacht: bool Zieldatentyp.TryParse (string, out <Zieldatentyp>). Über einen out-Parameter wird der eingelesene Wert in der angegebenen Variablen zur Verfügung gestellt. Die Methode selbst liefert je nach erfolgreicher Konvertierung den Wert true oder false zurück.

bool ok = false;
double AK = 0;

do
{
   Console.Write("Anfangskapital (EUR): ");
   ok = Double.TryParse(Console.ReadLine(), out AK);
   if (!ok)
      Console.WriteLine("Fehlerhafte Eingabe. Bitte erneut versuchen...");
}
while (!ok);
/* Fortsetzung des Programms */

int Console.Read()[Bearbeiten]

Verwendung[Bearbeiten]

Diese Methode verhält sich bei ihrem Aufruf zur Laufzeit wie folgt:

  1. Die Ausführung des Programms wird unterbrochen.
  2. Auf dem Bildschirm wird die Eingabebereitschaft durch einen blinkenden Cursor signalisiert.
  3. Das durch den Benutzer des Programms eingegebene Zeichen wird auf dem Bildschirm angezeigt.
  4. Die Eingabe wird nicht mittels der Returntaste beendet.
  5. Das eingegebenen Zeichen wird abschließend als Tastaturcode (ganzzahliger Wert) über den Rückgabewert zur Verfügung gestellt.
  6. Das Programm wird danach mit der auf die Read()- folgenden Methode oder Anweisung fortgesetzt.

Beispiel[Bearbeiten]

int eingabe;
eingabe = Console.Read();
/* usw. */

ConsoleKeyInfo Console.ReadKey()[Bearbeiten]

Verwendung[Bearbeiten]

Die Klassenmethode Console.ReadKey() eignet sich insbesondere für solche Eingaben, die die vom Benutzer gedrückte Taste auswerten sollen. Hier steht über die Struktur ConsoleKeyInfo und dessen Eigenschaft Key eine Enumeration aller Tasten der Tastatur zur Verfügung. Das folgende einfache Beispiel soll das verdeutlichen.

Beispiel[Bearbeiten]

Das kleine Programm soll sich über die ESC-Taste beenden lassen. Für alle gedrückten Tasten wird dabei deren Eigenschaft Key ausgegeben.

ConsoleKeyInfo cki;

do
{
    Console.WriteLine("Willkommen zu ConsoleKeyInfo");
    Console.Write("Beenden mit ESC-Taste ... ");
    cki = Console.ReadKey();
    Console.WriteLine("\nIhre Eingabe: " + cki.Key);
}
while (cki.Key != ConsoleKey.Escape);
/* Fortsetzung des Programms */

Anwendungsbeispiele für Ausgaben[Bearbeiten]

Console.WriteLine(<Parameterliste>)[Bearbeiten]

Verwendung[Bearbeiten]

Diese Methode eignet sich für Ausgaben mit nachfolgendem Zeilenumbruch. Diese Methode ist mehrfach überladen und kann daher mit allen Datentypen verwendet werden. Was konkret auszugeben ist, wird in Form eines Wertparameters angegeben. Solche Parameter können alle Sprachelemente sein, die einen Wert repräsentieren: Literale, Konstante, Ausdrücke und Variable. Die Parameterliste kann ergänzt werden durch einen Stringparameter, der die Formatierung des auszugebenden Wertes spezifiert. Dazu können in diesem Stringparameter Platzhalter für die Formatierung aufgeführt werden.

Beispiel[Bearbeiten]

Im folgenden Beispiel werden einige Möglichkeiten von Ausgaben mit oder ohne Formatierung gezeigt. Neben den gezeigten Formatierungen gibt es eine Vielzahl weiterer Möglichkeiten solcher Formatangaben.

class Program
{
   static void Main(string[] args)
   {
       const string zeichenkette = "Zeichenkette";
       const int ganz = 28;
       const bool stimmt = false;
       const double zahl = 110 / 17.1;

       Console.WriteLine(zeichenkette);
       Console.WriteLine(ganz);
       Console.WriteLine(stimmt);
       Console.WriteLine(zahl);
       
       Console.WriteLine("{0:f2}", zahl);
       Console.WriteLine("{0,10:f2}", zahl);
       Console.WriteLine("{0:N}", zahl);
       Console.WriteLine("{0,10:N}", zahl);

       Console.WriteLine(zeichenkette + zeichenkette);
       Console.WriteLine(ganz + ganz);
       Console.WriteLine(stimmt + " - " + !stimmt);
       Console.WriteLine(zahl + zahl);
       Console.WriteLine("{0:f2}", zahl + zahl);
       Console.WriteLine("{0,10:f2}", zahl + zahl);
       Console.WriteLine("{0:N}", zahl + zahl);
       Console.WriteLine("{0,10:N}", zahl + zahl);
       
       Console.WriteLine(zeichenkette + " - " + ganz);
       Console.WriteLine(ganz + " - " + zeichenkette);
    }
}

Das Beispielprogramm verwendet zur Ausgabeformatierung im Bereich der Doublewerte gängige Formatierungsvorschriften, die zu folgender Konsolausgabe führen:

Zeichenkette
28
False
6,4327485380117
6,43
      6,43
6,43
      6,43
ZeichenketteZeichenkette
56
False - True
12,8654970760234
12,87
     12,87
12,87
     12,87
Zeichenkette - 28
28 - Zeichenkette
Drücken Sie eine beliebige Taste . . .

Zunächst werden die Werte der Variablen ohne weitere Formatierung ausgegeben. Alle Werte werden linksbündig jeweils in einer neuen Zeile ausgegeben. Für die Ausgabe des Doublewerts werden dann Platzhalter (hier 0) verwendet, gefolgt ggfs. von einer Längenangabe, die durch ein Komma dem Platzhalter angefügt wird. Zudem kann dann noch eine genauere Typisierung des auszugebenden Werts erfolgen, indem, getrennt durch einen Doppelpunkt, ein Typ angegeben wird. Hierbei wird exemplarisch der Buchstabe N für numerisch und f für Floatwerte eingesetzt. Schließlich kann bei numerischen nicht ganzzahligen Werten noch eine Anzahl von Nachkommastellen an den Typkennzeichner angehängt werden.

Weiter ist interessant, dass im Rahmen der Ausgabe eine automatische Typumwandlung stattfinden kann. Der (mehrfach überladene) Additionsoperator + verkettet im Falle von Stringoperanden beide Zeichenketten zu einer einzigen, während im Falle von numerischen Operanden eine Addition stattfindet. Gibt man jedoch, wie in den letzten beiden Anweisungen zu sehen, Operanden unterschiedlicher Typen an (String + Zahl bzw. Zahl + String), so wird die jeweilige Zahl automatisch in eine Zeichenkette umgewandelt und mit der Zeichenkette verkettet.

Console.Write(<Parameterliste>)[Bearbeiten]

Verwendung[Bearbeiten]

Die Methode Console.Write(<Parameterliste>) verhält sich exakt wie die Methode Console.WriteLine(<Parameterliste>), die Ausgabe erfolgt allerdings ohne einen nachfolgenden Zeilenumbruch auf der Konsole. Sie eignet sich deshalb besonders zur Verwendung im Rahmen von Eingabeaufforderungen an den Benutzer, weil dort der Cursor hinter der Eingabeaufforderung stehen bleiben soll. Nachfolgendes Beispiel verdeutlich dies.

Beispiel[Bearbeiten]

Console.Write("Anfangskapital (EUR): ");
double AK = Convert.ToDouble(Console.ReadLine());