C#: Welche Bedeutung hat "parse"?

1 Antwort

In deinem Programm hast du eine Integer-Variable (alter) und dieser möchtest du einen Wert zuordnen, den der Nutzer in der Konsole eintippt. Alle Eingaben von der Konsole werden allerdings als Zeichenketten verwertet (denn Zeichenketten können alle notwendigen Zeichen aufnehmen: 0-9, a-z, A-Z, etc.), also in einen String gespeichert, den ReadLine zurückgibt. Bei jedem einzelnen Zeichen handelt es sich um ein char-Wert, der bspw. in der ASCII-Tabelle abgebildet wird.

Die Typdifferenz, die hierbei noch besteht, ist ein ziemliches Problem. In eine Integer-Variable passen nur ganze Zahlen (genau genommen Zahlen im Wertebereich von -2.147.483.648 bis 2.147.483.647). Nichtnumerischen Zeichen gehören logischerweise nicht dazu.

Folgendermaßen braucht es eine Konversion des Strings in eine Zahl. Diese Konversion kann der Computer nicht implizit (bzw. automatisch) für dich übernehmen. Denn sollte er auf ein nichtnumerisches Zeichen stoßen, wüsste er nicht, wie er mit diesem verfahren sollte. Einfach herausfiltern wird er es nicht, denn das würde auch einen Wertverlust bedeuten, der womöglich nicht beabsichtigt ist.

Also muss der Programmierer etwas genauer formulieren, wie er sich eine Konversion vorstellt.

Hierfür gibt es etliche Optionen.

a) Du schreibst eigenen Code, der den String Zeichen für Zeichen durchgeht und je Zeichen bestimmt, wie dieses in eine Zahl konvertiert werden kann.

b) Du verwendest die ToInt32-Methode der Convert-Klasse. Das solltest du allerdings nur tun, wenn du ganz sicher weißt, dass der String nur Zeichen beinhaltet, die letzten Endes in den Wertebereich des Integer hineinpassen. Sollte dem nämlich nicht so sein, wird das Programm mit einer Fehlermeldung abbrechen.

String word = "123";
int number = Convert.ToInt32(word);

String otherWord = "123a";
int otherNumber = Convert.ToInt32(otherWord); // FormatException!

Eine Fehlerbehandlung wäre nur mit try-catch möglich. Dazu später.

c) Du verwendest die Parse-Methode von int. Diese verhält sich ähnlich wie ToInt32, mit dem Unterschied, dass das Programm zusätzlich in einen Ausnahmezustand geraten würde, wäre der String nicht existent. Die ToInt32-Methode würde an der Stelle noch den Wert 0 zurückgeben.

String word = null; // String object does not  exist
int number = Convert.ToInt32(word); // 0
int number2 = int.Parse(word); // ArgumentNullException!

Die numerischen Typen (byte, int, double, float, long) verfügen alle über eine Parse-Methode sowie ein Pendant in der Convert-Klasse (ToDouble, ToSingle, ToInt16, etc.). Über zusätzliche Argumente kann man ihnen noch genauere Anweisungen übergeben, welches Format zu erwarten ist und wie es konvertiert werden soll. Das ist bspw. für Fließkommazahlen praktisch, bei denen das Zeichen für die Kommastelle kulturspezifisch sein kann (in Deutschland verwendet man ein Komma, in den USA einen Punkt). Dies soweit nur als Erwähnung. Mehr dazu kannst du dir in der Dokumentation anlesen.

Zu Maßnahmen der Ausnahmebehandlung:

Ausnahmezustände (Exceptions) treten immer dann auf, wenn der Computer in eine Situation gerät, in der er nicht weiß, wie er weiter verfahren soll. Wenn der Programmierer keinen Plan B formuliert hat, wird die Programmausführung an der Stelle abgebrochen. In der Regel wird dazu noch eine Fehlermeldung ausgegeben.

Bei den Konversionsmethoden von Convert sowie Parse können solche Ausnahmezustände wie schon beschrieben eintreten. Beispielsweise, wenn der zu konvertierende String ein nichtnumerisches Zeichen enthält. Sollte der Computer das Zeichen zu einer 0 umwandeln, zu einer 1, einer 2 oder etwas anderem? Es ist undefiniert.

In bestimmen Fällen mag es notwendig sein, die oben von mir genannte Option a) durchführen zu müssen. Doch bei einer Alterseingabe würde man ja schon erwarten, dass der Nutzer eine Zahl eingibt und individuelle Übersetzungen (alle a werden zu 1, alle b zu 2, o.ä.) ständen damit generell nicht zur Debatte.

Um sicherzustellen, dass der Nutzer eine valide Eingabe getätigt hat, könnte man die Eingabe auf ihre Bestandteile überprüfen, bevor man sie dem Parser/Konverter zum Fraß vorwirft.

String input = Console.ReadLine();

if (StringRepresentsValidNumber(input))
{
  // parse ...
}

Man könnte hierfür den String gegen einen regulären Ausdruck prüfen oder jedes Zeichen (mit einer Schleife) einzeln durchgehen und prüfen, ob es einer Zahl entspricht. Je nach erwarteten Format kann das komplizierter werden. Bei einer Eingabe eines Preis müsste man bspw. darauf achten, dass ein Komma in der Eingabe vorkommen darf, aber nur einmal und nicht an erster Stelle.

Eine andere Möglichkeit wäre eine Konversion auf gut Glück, doch die Exception müsste aufgefangen werden. Das schafft man mit der Kontrollstruktur try-catch.

String word = Console.ReadLine();
int number;

try
{
  number = int.Parse(word);
}
catch (Exception ex)
{
  Console.WriteLine("This input was not valid!");
  Environment.Exit(0);
}

Im try-Block wird die Operation durchgeführt, die einen Ausnahmezustand auslösen könnte. Sollte es dazu kommen, wird implizit stets ein Exception-Objekt kreiert (welches Informationen zur Ausnahme sammelt) und geworfen. Der Wurf löst den Programmabbruch aus - es sei denn, es gibt einen catch-Block, der dies auffangen kann. Innerhalb des catch-Blocks wird dann definiert, was stattdessen passieren soll (Plan B). Im obigen Fall würde eine Fehlermeldung auf der Konsole ausgegeben und danach das Programm beendet werden.

Man könnte es (gerade bei der Altersabfrage) aber noch etwas anders lösen:

int age = -1;

do
{
  String input = Console.ReadLine();
  
  try
  {
    age = int.Parse(input);
  }
  catch (Exception ex)
  {
    Console.WriteLine("This age is not valid! Try again!");
  }
}
while (age < 0 || age > 122);

So lange kein valides Alter eingegeben wird, wird immer wieder zu einer neuen Eingabe aufgefordert.

Zu guter Letzt stelle ich noch eine Verkürzung vor, die mit der TryParse-Methode erreicht werden kann. Diese führt intern eine Konversion mit try-catch durch und gibt dann einen boolschen Wert zurück, der Auskunft darüber gibt, ob die Konversion erfolgreich war oder nicht.

String input;
int age = -1;

do
{
  Console.WriteLine("Please enter your age.");
  input = Console.ReadLine();
}
while (!int.TryParse(input, out age) || age < 0 || age > 122);

Als erstes Argument nimmt sie, so wie Parse, den String entgegen, der geparst werden soll. Das zweite Argument ist ein out-Argument. Das bedeutet, dass nicht eine Kopie des Werts von age übergeben wird (-1), sondern ein Verweis auf die Variable selbst. Somit wird die Variable von der Methode auch geändert (der eingelesene Wert wird in ihr gespeichert).

Ein kurzes Beispiel sollte das Verhalten eines out-Parameter nochmal mehr verdeutlichen:

static void ModifiyValue(int value)
{
  value = 2;
}

static void ModifyValueWithOut(out int value)
{
  value = 2;
}

static void Main()
{
  int number = 1;
  ModifiyValue(number);
  Console.WriteLine(number); // 1

  ModifyValueWithOut(out number);
  Console.WriteLine(number); // 2
}

Mehr zum Schlüsselwort out findest du in der Microsoft Dokumentation.