Arbeiten mit .NET: C-Sharp/ Grundlagen/ Bestandteile/ Anweisungen

Der größte Teil eines Programms sind Anweisungen: Innerhalb eines Programmteils, zum Beispiel einer Klassendefinition, wird der Computer angewiesen, was er in welcher Reihenfolge unter welchen Bedingungen auszuführen hat.

Allgemeine Erläuterungen Bearbeiten

Auch in Zeiten der objektorientierten Programmierung sind die Objekte, mit denen gearbeitet wird, nicht „einfach da“, sondern sie werden benutzt und führen Aufgaben aus. Solche Arbeitsabläufe werden mit Anweisungen gesteuert, die nacheinander ausgeführt werden.

Wichtig ist, dass der Compiler die Befehle erkennen kann. Dafür gibt es eine einfache Regel:

 

Merke
Eine Anweisung ist unter C# ein Befehl, der mit einem Semikolon abgeschlossen wird.
Mehrere Anweisungen können durch die geschweiften Klammern {...} in Blöcke zusammengefasst werden.

Tabulatoren, Leerzeichen und Zeilenumbrüche spielen dabei in der Regel keine Rolle. Die folgenden Codes mit einer ziemlich langen Befehlszeile sind völlig gleichwertig:

// in einer Zeile
FileStream fs = new FileStream(	Path.Combine( Environment.CurrentDirectory, "Test.txt"), FileMode.Create);

// auf mehrere Zeilen aufgeteilt mit verschiedenen Trennzeichen
FileStream                              fs 

	= new FileStream	(	Path.Combine( Environment
	                                .
	                                CurrentDirectory, "Test.txt"), FileMode.Create)
	;

Sie sehen: Viele Leerzeichen, weitere Tabulatoren, der Wechsel auf eine neue Zeile und sogar Leerzeilen haben keine Auswirkungen auf die Funktionsfähigkeit des Codes. Selbst der Punkt als Verbindung zwischen Klasse und Eigenschaft bei Environment.CurrentDirectory kann von beiden Teilbegriffen getrennt werden.

Umgekehrt dürfen mehrere Befehle hintereinander in eine Zeile geschrieben werden:

int i1 = 1; double d2 = 2.345; string s0 = "Dies ist ein Test.";

Es gibt nur zwei Einschränkungen: Ein Bezeichner ist ein fester Begriff und darf nicht unterbrochen werden. Innerhalb eines Strings darf es einen Zeilenumbruch nur dann geben, wenn er zum String gehört.

  Unzulässiger Zeilenumbruch bei "FileStream" und dem Dateinamen
FileStream fs = new File
		Stream(	Path.Combine( Environment.CurrentDirectory, "Test.
		txt"), FileMode.Create);

Achten Sie deshalb bei langen Zeilen in erster Linie auf die Lesbarkeit: Was inhaltlich zusammengehört, sollte nicht getrennt werden; aber scheuen Sie sich nicht vor einer Aufteilung in mehrere Zeilen. Eine Zeile sollte höchstens so lang sein wie die Breite des Bildschirms; bei mehr als 80 Zeichen verliert man leicht den Überblick. Sinnvoll ist deshalb z.B. solch eine Schreibweise:

FileStream fs = new FileStream(	
	Path.Combine( Environment.CurrentDirectory, "Test.txt"), FileMode.Create);

Ergänzend sei noch die leere Anweisung erwähnt: Ein einsames Semikolon ist eine gültige Anweisung, die nichts macht. Sie ist selten sinnvoll (eigentlich nur dann, wenn Sie etwas vorhaben, was noch nicht programmiert wird), aber versehentliche doppelte Semikolons haben keine negative Auswirkung auf den Compiler.

Der Begriff "Literal" Bearbeiten

Von hier an wird Ihnen immer wieder dieser Begriff begegnen. Damit sind konkrete Werte gemeint, die als solche im Quelltext stehen wie in den obigen Beispielen:

Befehl Wert (Literal) Typ
int i1 = 1;
1 Ganzzahliger Wert
double d2 = 2.345;
2.345 Dezimalzahl
string s0 = "Dies ist ein Test.";
"Dies ist ein Test." Zeichenkette
bool isValid = false;
false Wahrheitswert

Diese konkreten Werte werden gerne als „Konstante“ bezeichnet, aber darunter versteht man eigentlich etwas anderes (wie wir noch sehen werden). Der Fachausdruck für solche Werte lautet „Literal“; vor allem bei Zeichenketten gibt es die erweiterte Formulierung „Stringliteral“.

Überblick Bearbeiten

Im folgenden werden die verschiedenen Arten von Anweisungen kurz vorgestellt, zusammen mit Verweisen auf die späteren Kapitel des Buches, in denen dies genauer behandelt wird.

Deklarationen Bearbeiten

Jede Variable, die verwendet wird, muss dem Programm bekanntgegeben werden. Dazu gehören zwei Schritte:

  • Unter Deklarieren versteht man die Nennung des Namens der Variablen, verbunden mit dem Datentyp.
  • Unter Initialisieren versteht man die erstmalige Zuweisung eines Wertes an die Variable.

Beide Schritte können getrennt erfolgen (auch mit einigem Abstand) oder in einem Befehl (Beispiele aus der Program.cs der Anwendung Mein neues Auto):

string cartype, motortype;   // nur die Deklaration der beiden Variablen
// etwas später
cartype = args[0];           // jetzt die erste Zuweisung eines Wertes
motortype = args[1];

// oder beides zusammen
Car mycar = new Car(cartype, motortype, nextid);

Mehr über die Verwendung von Variablen finden Sie im nächsten Kapitel. Die gleichen Hinweise gelten auch für Konstante sowie interne Felder einer Klasse.

Zuweisungen Bearbeiten

Mit Zuweisungen wird ein Wert angegeben oder berechnet und das Ergebnis unter einer Variablen oder einem Feld einer Klasse (einer Eigenschaft oder einem internen Feld) gespeichert.

  • Zuerst wird der Name der Variablen oder des Feldes genannt; das steht also links vom Gleichheitszeichen.
  • Dann folgt das Gleichheitszeichen als Zuweisungsoperator.
  • Dann folgt ein Wert oder ein Ausdruck, der einen Wert berechnen soll; das steht also rechts vom Gleichheitszeichen.
  • Der Datentyp des Wertes muss zum Datentyp der Variablen passen.

Hier ein paar Beispiele:

// direkte Zuweisung eines Wertes an eine Variable
double radius = 2.75;           
// Berechnung eines Wertes und Zuweisung des Ergebnisses an eine neue Variable
double area = Math.Pi * Math.Pow(radius, 2);
// Berechnung eines Wertes und Zuweisung des neuen Ergebnisses an dieselbe Variable
area = Math.Pi * Math.Pow(17, 2);

Andererseits ist so etwas nicht zulässig:

  Ein Ausdruck ohne Zuweisung ist keine Anweisung
Math.Pi * Math.Pow(17, 2);
  Ausgabe
Nur ... Ausdrücke können als Anweisung verwendet werden.

Besonders wichtig ist die „Kompatibilität“ der Datentypen. Das Ergebnis der rechten Seite muss als Datentyp der linken Seite behandelt werden können; wenn dies nicht automatisch gegeben ist, muss das Ergebnis in den gewünschten Typ umgewandelt werden; wenn dies nicht möglich ist, ist eine Zuweisung ausgeschlossen.

  Eine Dezimalzahl ist keine ganze Zahl.
double d1 = 12.34;
int i1 = d1 * 2;
  Ausgabe
Der Typ "double" kann nicht implizit in "int" konvertiert werden. Es ist bereits eine explizite Konvertierung vorhanden. (Möglicherweise fehlt eine Umwandlung.)

Genauso ist es: Es fehlt eine Anweisung, die aus dem double-Ergebnis der Berechnung einen Integer-Wert macht; das kann eine Rundung (mathematisch oder kaufmännisch) oder eine Abrundung sein. Woher soll der Compiler wissen, was genau Sie wollen...

Dieses Problem wird im Abschnitt Datentypen und vor allem in den Kapiteln zu Konvertierungen genauer behandelt. Ansonsten werden Zuweisungen ständig benutzt, sodass keine zusätzliche Behandlung notwendig ist.

Aufruf von Methoden Bearbeiten

Methoden sind Teile eines Programms, bei denen eine Menge von Befehlen zusammengefasst werden, um eine bestimmte Teilaufgabe zu erledigen. Der Aufruf einer Methode ist eine Anweisung, dieses Unterprogramm auszuführen. Es gibt verschiedene Varianten, wie eine Methode benutzt wird.

  • Methoden ohne Rückgabewert werden in ihrer Definition als void deklariert.
    Beispiel: die Main-Methode eines Programms, das kein „Arbeitsergebnis“ verwendet.
    static void Main()
    
    Eine solche Methode wird in einer Anweisung direkt benutzt:
    • Eine einfache Methode wird aufgerufen und ausgeführt, ohne dass ihr Werte übergeben werden oder dass ein Rückgabewert abgefragt und weiterbenutzt wird.
      Beispiel ist im Einstiegsprogramm Mein neues Auto die ShowValues-Methode der Car-Klasse, die unter Bezug auf das aktuelle Auto einfach gestartet wird, alle Ausgaben selbständig vornimmt und nach Erledigung beendet wird.
      mycar.ShowValues();
      
    • Meistens werden einer Methode Argumente übergeben (auch Parameter genannt), mit denen die Methode arbeiten soll.
      Beispiel: Im Einstiegsprogramm sind es die Accelerate- und Delay-Methoden zur Beschleunigung und Verzögerung, denen als Wert die jeweilige Veränderung mitgegeben wird:
      mycar.Accelerate(40);
      
  • Methoden mit Rückgabewert werden in ihrer Definition mit dem Datentyp des Wertes deklariert, den sie zurückgeben.
    Beispiel: die Main-Methode eines Programms mit einem Fehlercode als "Arbeitsergebnis"
    static int Main()
    
    Bei einer solchen Methode wird das Arbeitsergebnis in einer Anweisung fast immer einer Variablen zugewiesen:
    • Jede Klasse, also jedes Objekt verfügt über eine einfache Methode ToString, üblicherweise ohne Argument (wie auch die Car-Klasse), die den Inhalt des Objekts als String beschreibt:
      string output = mycar.ToString();
      
    • Auch dafür gibt es Varianten, die mit Argument die Art der Darstellung steuern.
      Beispiel: Zeige nur den Wochentag an.
      string output = DateTime.Today.ToString("dddd");
      

Sehr oft werden diese Varianten verknüpft. Im Beispielprogramm wird ein Rückgabewert sofort weitergereicht, nämlich der Rückgabewert von ToString als Argument an WriteLine:

Console.WriteLine(mycar.ToString());


 

Merke
  • Unterprogramme in C# heißen einheitlich Methoden.
  • Es gibt Methoden mit und ohne Argument
  • Es gibt Methoden mit und ohne Rückgabewert.
    • Eine Methode ohne Rückgabewert hat den Rückgabetyp void.
    • Bei einer Methode mit Rückgabewert muss dessen Typ festgelegt sein.


Hinweis: In anderen Programmiersprachen wird zwischen Funktionen (Unterprogramm mit Rückgabewert) und Prozeduren (Unterprogramm ohne Rückgabewert) unterschieden. Bei C# sind alles Methoden, und der Rückgabetyp ist ggf. void oder etwas anderes.

Um Missverständnisse zu vermeiden: Die Deklaration und Definition einer Methode selbst wird nicht als Anweisung verstanden; ihr Inhalt dagegen sind eine oder mehrere Anweisungen.

Einzelheiten werden in den Kapiteln des Abschnitts Methoden behandelt.

Erzeugen von Objekten Bearbeiten

Dabei handelt es sich eigentlich um nichts anderes als das, was schon besprochen wurde: Ein Objekt wird durch einen Konstruktor erzeugt, also eine spezielle Methode, die eine Instanz ihrer Klasse zurückgibt; dieses Ergebnis wird einer Variablen dieses Typs zugewiesen.

Car mycar;                              // Deklaration
mycar =                                 // Zuweisung
    new Car("NSU", "688 cm³", 471);     // Aufruf eines Konstruktors

// oder in einer Zeile:
Car mycar = new Car("NSU", "688 cm³", 471);

Dies wird ständig bei allen Situationen benutzt und benötigt deshalb keine ausführlichere Behandlung.

Ein Sonderfall ist die using-Anweisung (bitte nicht verwechseln mit der using-Direktive, die unten wiederholt wird): Diese umfasst Deklaration und Erzeugung eines Objekts, die Arbeit damit und zum Schluss automatisch (ohne ausdrücklichen Befehl) das Auflösen dieses Objekts.

using System.IO;                                              // using-Direktive

using (StreamReader sr = new StreamReader("TestFile.txt"))    // using-Anweisung für sr
{
    string line;
    // Read and display lines from the file until the end of 
    // the file is reached.
    while ((line = sr.ReadLine()) != null) 
    {
        Console.WriteLine(line);
    }
}

Die Variable sr ist nur innerhalb des using-Blocks definiert; nach der letzten darin enthaltenen Anweisung werden automatisch sr.Close() und sr.Dispose() aufgerufen.

Dieses Vorgehen ist bei (fast) allen Klassen dringend zu empfehlen, die eine Dispose-Methode zum ausdrücklichen Löschen des Objekts kennen. In der .NET-Hilfe erkennt man das daran, dass die Klasse die Schnittstelle IDisposable implementiert.

Einzelheiten dazu werden im Kapitel Automatisch zerstören mit using im Zusammenhang mit den OOP-Grundlagen behandelt.

Verzweigungen, Schleifen, Sprünge Bearbeiten

Unter dem Begriff „Kontrollstrukturen“ werden alle Maßnahmen zusammengefasst, die den „normalen“ Programmablauf – ein Befehl nach dem anderen – beeinflussen.

Verzweigungen

Es gibt zwei Verfahren.

Die if-Verzweigung prüft, ob eine Bedingung erfüllt ist. Im Falle true wird der danach folgende Code ausgeführt; andernfalls wird (sofern vorhanden) der Code im else-Zweig ausgeführt.

  • Die Bedingung muss hinter if in Klammern stehen.
  • Die Bedingung kann aus mehreren Teilen bestehen, die durch AND und OR verknüpft und durch weitere Klammern gegliedert werden.
  • Der else-Zweig kann weitere if-Prüfungen enthalten, sodass eine Konstruktion wie else if | else if | else if | if entsteht (wie im Beispielprogramm).
if (DateTime.Now.Hour < 12) 
{
  Console.Write("Guten Morgen, ");
} else if (DateTime.Now.Hour < 18) 
{
  Console.Write("Grüß Gott, ");
} else 
{
  Console.Write("Guten Abend, ");
}

Wie Bedingungen formuliert werden, wird in den Kapiteln des Abschnitts Datentypen verarbeiten bei den Operatoren behandelt. Zu beachten ist vor allem, dass die Gleichheit mit "==" und die Ungleichheit mit "!=" geprüft werden:

if (DateTime.Now.Hour == 12)  { ... }
	else if (DateTime.Now.Hour != 18) { ... }

Weitere Einzelheiten zu if werden im Kapitel if-Verzweigungen behandelt.

Die switch-Verzweigung prüft eine Variable; je nach dem aktuellen Wert wird ein bestimmter Code-Zweig ausgeführt oder übersprungen.

  • Diese Prüfung kann nur für ganzzahlige Datentypen und für Strings vorgenommen werden.
  • Es wird nur auf exakte Gleichheit geprüft; verknüpfte Bedingungen sind nicht vorgesehen.
  • Anstelle eines else-Zweigs gibt es den default-Zweig, der durchlaufen wird, wenn keiner der angegebenen Werte zutrifft; dieser Zweig kann auch entfallen.
  • Jeder Zweig muss mit der Anweisung break; abgeschlossen werden.

Die obige if-Verzweigung kann beispielsweise so durch switch ersetzt werden:

switch(DateTime.Now.Hour)
{
  default: 
    Console.Write("Guten Morgen, ");
    break;
  case 12: case 13: case 14:
  case 15: case 16: case 17:
    Console.Write("Grüß Gott, ");
    break;
  case 18: case 19: case 20:
  case 21: case 22: case 23:
    Console.Write("Guten Abend, ");
    break;
}

In diesem Fall ist if vermutlich übersichtlicher als switch, es gibt aber auch den umgekehrten Fall. Außerdem ist mal nur die eine, mal nur die andere Verzweigung möglich.

Weitere Bedingungen und Einzelheiten werden im Kapitel switch-Verzweigungen behandelt.

Schleifen

Mit Schleifen wird ein Teil des Programms – üblicherweise ein Block mehrerer Anweisungen – wiederholt durchlaufen.

  • Bei foreach wird jedes Element einer Liste benutzt. Die Schleife beginnt mit dem ersten Element und endet mit dem letzten Element. Die tatsächliche Reihenfolge wird innerhalb der Liste festgelegt und kann „von außen“ nicht immer beeinflusst werden; einen fortlaufenden Index gibt es nicht. Beispiel:
    string s = "Test";
    
    foreach(char c in s) { /* Anweisungen u.a. für c */ }
    
    Einzelheiten dazu werden im Kapitel Schleifen mit Listen behandelt.
  • Bei der for-Schleife wird üblicherweise ein Zähler festgelegt – daher auch die Bezeichnungen Zählschleife und Laufvariable. Die Schleife wird bei einem Anfangswert begonnen und so oft wiederholt, bis der Maximalwert überschritten ist. Beispiel:
    string s = "Test";
    
    for( int x1 = 0; x1 < 4; x1++ ) Console.Write(s[x1]);
    
    Einzelheiten dazu sowie viele Varianten werden im Kapitel Zählschleifen behandelt.
  • Bei einer Kopfschleife wird eine Anfangsbedingung geprüft. Die Schleife wird so oft durchlaufen, wie diese Bedingung erfüllt ist; wenn die Anfangsbedingung von vornherein nicht erfüllt ist, wird die Schleife überhaupt nicht durchlaufen. Beispiel:
    while (mycar.Speed > 0) mycar.Delay(4); // sanftes Bremsen
    
    Einzelheiten dazu werden im Kapitel Kopfschleifen behandelt.
  • Bei einer Fußschleife wird eine Schlussbedingung geprüft. Die Schleife wird mindestens einmal durchlaufen; dann wird die Bedingung geprüft und die Schleife ggf. wiederholt und so weiter. Beispiel (eine Konsolenanwendung wird solange bearbeitet, bis 'n' eingegeben wird):
ConsoleKeyInfo input;
	do
	{
		Console.WriteLine("nochmals");
		input = Console.ReadKey();
	} while(input.KeyChar != 'n');
Einzelheiten dazu werden im Kapitel Fußschleifen behandelt.

Nebenbei: Es gibt keine if-Schleifen, sondern nur if-Abfragen! Eine Schleife hat nichts mit if zu tun, aber auch gar nichts.

Sprungbefehle

Mit den folgenden Anweisungen wird der „normale“ Arbeitsablauf abgebrochen.

  • Mit return wird die aktuelle Methode sofort beendet und zum vorherigen Programmteil zurückgekehrt.
    Innerhalb der Main-Methode bedeutet dies, dass das Programm sofort beendet wird.
  • Mit break wird – wie bei switch verwendet – die aktuelle Verzweigung oder Schleife abgebrochen.
  • Mit continue wird innerhalb einer Schleife der aktuelle Durchgang beendet und ein neuer Durchgang begonnen.
  • Mit goto kann zu einer anderen Stelle im Programm gesprungen werden.
    Warnung: Dies stört (zerstört) die Programmstruktur der OOP endgültig und ist fast niemals sinnvoll.

Einzelheiten werden im Abschnitt Kontrollstrukturen im Kapitel Sprungbefehle behandelt.

Der Vollständigkeit halber sei noch throw erwähnt, das zum nächsten Abschnitt Fehlerprüfungen gehört.

Fehlerprüfungen Bearbeiten

Im „Lebenszyklus“ eines Programms können verschiedene Fehler auftreten, für die der Programmierer Vorkehrungen treffen muss:

  • Schreibfehler, Syntaxfehler oder falsche Zuordnungen von Datentypen stellt in der Regel der Compiler fest. Das Programm kann nicht kompiliert werden, ist also nicht lauffähig und kann nicht gestartet werden; der Anwender bekommt solche Fehler nicht zu sehen.
  • Bestimmte Problemsituationen, die zu Fehlern führen können, sind vorhersehbar. Solche Situationen hat der Programmierer abzufangen; das Programm soll dann mit einer Ersatzmaßnahme oder Fehlermeldung fortgeführt werden.
    Beispiele: Der Anwender soll eine Zahl eingeben und tippt stattdessen einen Buchstaben (Fehlermeldung). Der Anwender will eine Datei öffnen, die gesperrt ist (Meldung und Ersatzmaßnahme).
  • Andere Problemsituationen sind nicht vorhersehbar. Der Programmierer muss dafür sorgen, dass das Programm trotzdem kontrolliert und sinnvoll fortgesetzt werden kann.
    Beispiel: Die Datei des letzten Beispiels war gerade eben noch frei; in der Zehntelsekunde seit der Prüfung wurde sie gesperrt.

Die ersten beiden Fälle gehören zu den „einfachen“ Aufgaben von Programmierern. Für die letzte Situation, die zugegebenermaßen schwieriger zu behandeln ist und komplexer ist, gibt es mit try-catch-finally vor allem eine Art von Anweisungen mit jeweils einem Block. Im Zusammenhang mit dem Einstiegsprogramm hatten wir bereits das folgende Beispiel gebracht:

int input;
    try
    {
        // eine Zeile einlesen und versuchen ("try"), 
        // daraus eine int-Zahl zu machen ("konvertieren")
        input = Int32.Parse( Console.ReadLine() );
    }
    catch(Exception ex)
    {
        // falls keine Zahl eingegeben wurde, dann Fehlermeldung und Ersatzwert
        Console.WriteLine(ex.Message);
        input = -1;
    }
    finally
    {
        Console.WriteLine(input);
    }

Je nach Situation kann man sich auf die Kombination try-catch oder auf try-finally beschränken oder mehrere catch-Blöcke unterscheiden. All das wird unter Ausnahmen und Fehler behandelt.

Verschachtelung Bearbeiten

Jede Variante bei den Anweisungen kann verschachtelt werden. Ein if-Zweig kann eine switch-Verzweigung benutzen, darin kann es wieder if und using geben. Damit das übersichtlich und logisch korrekt bleibt, sollten Sie alles, was zusammengehört, als Block zusammenfassen und im Quelltext einrücken. Das obige Beispiel zur if-Verzweigung könnte auch so geschrieben werden:

  Sachlich richtig, aber ziemlich unverständlich
if (DateTime.Now.Hour < 12) Console.Write("Guten Morgen, "); else 
	if (DateTime.Now.Hour < 18) 
Console.Write("Grüß Gott, "); else Console.Write("Guten Abend, ");

Wenn innerhalb eines dieser if-Zweige noch eine weitere Prüfung eingefügt würde, würde es endgültig unübersichtlich; Fehler sind fast unvermeidlich. Deshalb gilt:

 

Merke
  • Code muss übersichtlich und lesbar sein; das vermeidet Fehler.
  • Verzweigungen, Schleifen und Fehlerbehandlung können verschachtelt sein.
  • Die verschiedenen Ebenen sollten durch die geschweiften Klammern {...} in Blöcke zusammengefasst werden auch dann, wenn es nicht erforderlich ist.

Teile des Codes können als Block vom Rest einer Methode abgesetzt werden oder als eigene Methode ausgelagert werden. Achten Sie darauf, dass nicht zuviel in einem Arbeitsablauf zusammengepresst wird.

Keine Anweisungen Bearbeiten

Der Vollständigkeit halber erläutern wir die folgenden Bestandteile eines Programms, die nicht als Anweisungen gelten.

using-Direktive Bearbeiten

Diese dient überwiegend dazu, dem Programmierer die Schreibarbeit zu erleichtern:

using System.Windows.Forms;

Dann muss er die Namespace-Bezeichnung nicht bei jeder Klasse schreiben; fast immer findet der Compiler den Klassennamen. In Sonderfällen (vor allem wenn gleichnamige Klassen in verschiedenen Namespaces vorkommen) kann auch ein Alias-Name für einen Namespace definiert werden. Einzelheiten werden unter Referenzen und using behandelt.

  Die using-Direktive ist nicht mit der using-Anweisung zu verwechseln, siehe oben unter Erzeugen von Objekten.

Namespace-Deklaration Bearbeiten

Ein Namespace ist „nur“ ein Ordnungsmerkmal. Er gibt an, unter welchem Namen und in welchem Zusammenhang eine bestimmte Klasse zu finden ist. Eine Klasse ist grundsätzlich unter ihrem vollständigen Namen – einschließlich Namespace – anzusprechen.

Der Bereich eines Namespace wird (wie ein Block von Anweisungen) durch die geschweiften Klammern {...} angegeben; darin sind eine oder mehrere Klassendefinitionen enthalten.

namespace Wikibooks.CSharp.Mein_neues_Auto
{
    class Program
    {
    }
}

Ein Namespace kann auf mehrere Quelldateien verteilt werden. (Das ist auch die Regel, weil meistens jede Klasse eine eigene Quelldatei erhält.) Mehr dazu wird unter Was ist ein Namespace behandelt.

Klassendefinition Bearbeiten

Die Definition einer Klasse ist (natürlich) die Grundlage der OOP. Grundsätzlich wird sie (wie im vorigen Beispiel) innerhalb eines Namespace deklariert; die Definition steht wiederum innerhalb eines Bereichs, der durch die geschweiften Klammern {...} angegeben ist. Je nach Zielsetzung und Verwendung erhält die Definition weitere Festlegungen und steht teilweise auch in mehreren Quelldateien (dann aber immer im selben Namespace).

Einzelheiten werden unter Die Definition einer Klasse behandelt.

Kommentare Bearbeiten

Die Kommentierung des Quelltextes ist nicht nur in einem großen Team von Entwicklern wichtig, sondern auch für „Einzelkämpfer“. Wenn Sie einen Programmteil nach einiger Zeit überarbeiten wollen oder müssen – sei es nach einem Monat oder nach einem Jahr –, wollen und sollen Sie sich nicht erst lange (erneut) einarbeiten müssen, sondern die Problemstellen sofort erkennen können. Dafür gibt es mehrere Arten von Kommentar.

  • Am wichtigsten sind sprechende Bezeichner. Wenn Sie die Namen von Klassen, Eigenschaften, Methoden und Variablen sinnvoll wählen, sparen Sie sich einen Großteil von erläuterndem Text und verstehen trotzdem den Sinn eines Codes. Richten Sie sich deshalb von Anfang an nach den .NET-Namenskonventionen.
  • Einzeilige Kommentare benutzen wir ständig: Sie beginnen mit einem doppelten Schrägstrich und enden am Ende einer Zeile.
ConsoleKeyInfo input;    // hier steht ein Kommentar
// diese Zeile besteht nur aus einem Kommentar
// dies ist eine weitere Kommentarzeile
input = Console.ReadKey();
  • Blockkommentare können über mehrere Zeilen gehen. Sie beginnen mit /* und enden mit */. Sie sind vor allem nützlich, um Code-Teile vorübergehend für ungültig zu erklären.
ConsoleKeyInfo input;    
/* dies ist ein mehrzeiliger Kommentar;
   input = Console.ReadKey();
   der darin enthaltene Code wird beim Kompilieren übergangen */
  • Mit XML-Kommentaren kann halbautomatisch eine Dokumentation erstellt werden. Solche Kommentare beginnen mit drei Schrägstrichen am Anfang einer jeden Zeile und enthalten Tags, die beim Erstellen der Dokumentation ausgewertet werden. In unserer Car-Klasse sind sie teilweise schon enthalten:
/// <summary>
/// Das Auto soll beschleunigt werden.
/// </summary>
/// <param name="diff">Betrag, um den die Geschwindkeit erhöht wird</param>

Einzeilige und Blockkommentare werden in Kommentare behandelt, Xml-Kommentare unter Dokumentation.

Zusammenfassung Bearbeiten

Anweisungen steuern den eigentlichen Programmablauf. Neben der Konzeption eines Programms, zu dem vor allem die Gestaltung der Klassen und ihrer Elemente gehört, sind Anweisungen entscheidend für die Funktionalität. Dazu gehören:

  • Deklarationen
  • Zuweisungen
  • Aufruf von Methoden
  • Erzeugen von Objekten
  • Kontrollstrukturen wie Verzweigungen, Schleifen und Sprungbefehle
  • Fehlerprüfungen

Das alles wird in die Definitionen von Klassen eingebaut und in Namespaces gegliedert.

Übungen Bearbeiten

Übung 1 Definitionen Zur Lösung

Welche der folgenden Aussagen sind wahr, welche sind falsch?

  1. Eine Anweisung wird mit einem Komma beendet.
  2. Wenn eine Anweisung in einer weiteren Zeile fortgesetzt werden soll, muss dies am Ende der Zeile durch einen Unterstrich _ deutlich gemacht werden.
  3. Eine Anweisung kann durch Leerzeichen oder Tabulatoren gegliedert werden.
  4. Mehrere Anweisungen dürfen zusammen in eine Zeile geschrieben werden.
  5. Wenn ein Klassenname mit dem Namespace zusammen angegeben wird, dürfen diese beiden Bestandteile nicht in verschiedenen Zeilen stehen.
  6. Eine Befehlszeile darf nicht länger als 255 Zeichen werden.

Übung 2 Einfache Anweisungen Zur Lösung

Finden Sie in den folgenden Codezeilen die Fehler und berichtigen Sie. Es geht nur um Fehler, die sich auf dieses Kapitel beziehen.

string = cartype
cartype := args[0];           
double brutto = 17.90
brutto / 1.19;
DateTime.Today.AddDays(-7);

Übung 3 Methoden Zur Lösung

Erstellen Sie für die folgenden Situationen jeweils den Aufruf und die Verwendung einer Methode. Geben Sie für eine "eigene" Methode außerdem an, wie sie innerhalb ihrer Klasse deklariert sein muss.

  1. Ein Auto (also ein Objekt der Car-Klasse) soll gestartet werden.
  2. Ein Auto soll gestartet werden; die Methode soll angeben, ob der Versuch erfolgreich war.
  3. Die Dezimalzahl 417.83 soll in einen String konvertiert werden.
  4. Die Dezimalzahl 417.83 soll in einen String konvertiert werden und gleichzeitig an der Console ausgegeben werden.
  5. Ein Auto soll mit der Methode Delay gestoppt werden.
  6. Holen Sie sich das Datum, das von heute an 10 Tage in der Zukunft liegt. Hinweis: Jedes Datum kennt die Methode AddDays mit einem passenden Argument.
  7. Zu diesem Datum soll der Wochentag verwendet werden.

Übung 4 Erzeugen von Objekten Zur Lösung

Was ist an den folgenden Anweisungen falsch, mit denen jeweils ein Objekt der Car-Klasse erzeugt wird?

Car firstCar = Car("NSU", "688 cm³", 471);
Car secondCar = new Car();
Car thirdCar = new Car("NSU Prinz", 942);
new Car("NSU Ro 80", "1988 cm³", 26);

Übung 5 Verzweigungen Zur Lösung

Definieren Sie in der Car-Klasse eine Methode GetSpeedRegion mit folgendem Inhalt:

  • Sie soll als String eine Angabe darüber machen, wie schnell das Auto aktuell ist: bei 0 km/h "Stillstand", bei 1–9 km/h "Schrittgeschwindigkeit", bis zu 30 km/h "Wohnbereich", bis zu 50 km/h "innerorts", dann "Landstraße", "Bundesstraße", "Autobahn" und "unbegrenzt".
  • Dieser String ist zunächst als Variable result zu deklarieren.
  • Dividieren Sie dann die Geschwindigkeit mit '/' durch 10 und beachten Sie die sogenannte Integer-Division, bei der der Rest abgeschnitten wird: "10 / 10" liefert 1, "11 / 10" liefert 1, "19 / 10" liefert 1, "20 / 10" liefert 2 usw.
  • Dieser Zwischenwert temp wird in einer switch-Anweisung ausgewertet; jeder Zweig muss eine if-Anweisung enthalten, ob die Geschwindkeit einem bestimmten Wert entspricht.
  • Die Variable result wird als Rückgabewert der Methode verwendet. Hinweis: Dieser Schritt wird ebenso in der Methode ShowSignals der Car-Klasse benutzt.

Übung 6 Verzweigungen Zur Lösung

Verbessern Sie die Lösung der vorigen Aufgabe mit folgendem Trick: Wenn der aktuelle Wert der Geschwindigkeit zuerst um 1 verringert wird und dann die Division durch 10 ausgeführt wird, landet der Grenzwert in derselben Gruppe wie die nachfolgenden Werte. Beachten Sie die Regel "Punkt-vor-Strich" und den Hinweis zur Integer-Division.

Übung 7 Schleifen Zur Lösung

Berechnen Sie, wie viele Weizenkörner der Erfinder des Schachs der Legende nach zu erhalten hat (siehe Weizenkornlegende). Bauen Sie für die erste Formel, die dort im Abschnitt Mathematische Berechnung genannt wird, eine Schleife nach der folgenden Anleitung:

  • Deklarieren Sie zwei Variablen vom Typ UInt64, die jeweils mit 0 initialisiert werden.
  • Die eine Variable soll das Ergebnis enthalten.
  • Die zweite Variable ist für die aktuelle 2er-Potenz gedacht, also den Wert, der addiert werden soll.
  • Die Schleife geht von Feld 1 bis 64.
  • Innerhalb der Schleife wird zuerst der Additionswert zugewiesen: bei Feld 1 mit dem Startwert 1, bei allen anderen Feldern durch Multiplikation mit 2.
  • Dieser Additionswert wird dem (bisherigen) Ergebnis hinzu addiert.

Hinweis: Andere Typen als UInt64 führen zu einem Laufzeitfehler, ebenso wie ein anderer Ablauf der Teilrechnungen.

Lösungen

Lösung zu Übung 1 Definitionen Zur Übung

Die Aussagen 3, 4 sind wahr, die Aussagen 1, 2, 5, 6 sind falsch.

Lösung zu Übung 2 Einfache Anweisungen Zur Übung
  1. Zu einer Deklaration gehören Datentyp und Name der Variablen direkt hintereinander (ohne Gleichheitszeichen). Diese Anweisung ist mit Semikolon zu beenden.
    string cartype;
    
  2. Die Zuweisung erfolgt mit einem einfachen Gleichheitszeichen (ohne Doppelpunkt).
    cartype = args[0];
    
  3. Es fehlt wieder das Semikolon am Ende. Dadurch glaubt der Compiler, dass die Anweisung in der nächsten Zeile fortgesetzt wird, und bringt verwirrende Fehlermeldungen.
    double brutto = 17.90;
    
  4. Das ist eine Rechenvorschrift, die nur in einer Zuweisung stehen darf, und zwar rechts vom Gleichheitszeichen.
    double netto = brutto / 1.19;
    
  5. Das ist formal nicht falsch, nämlich als Aufruf einer Methode. Aber das Ergebnis wird nicht benutzt, also ist dieser Aufruf sinnlos.
    DateTime oneWeekAgo = DateTime.Today.AddDays(-7);
    

Lösung zu Übung 3 Methoden Zur Übung
  1. mycar.Start();                                 // Aufruf
    
    public void Start() { ... }                    // Deklaration
    
  2. bool successful = mycar.Start();               // Aufruf
    
    public bool Start() { ... }                    // Deklaration
    
  3. string s = 417.83.ToString();                  // Aufruf
    
  4. Console.WriteLine( 417.83.ToString() );        // Aufruf
    
  5. mycar.Delay(mycar.Speed);                      // Aufruf
    
  6. DateTime future = DateTime.Today.AddDays(10);  // Aufruf
    
  7. string s = future.ToString("dddd");            // Aufruf
    

Lösung zu Übung 4 Erzeugen von Objekten Zur Übung
  1. Das Schlüsselwort new fehlt, mit dem ein neues Objekt erzeugt wird.
  2. Der einfache Konstruktor kann wegen der Deklaration private nicht verwendet werden. Dies wurde unter OOP - Ein erster Einblick erläutert.
  3. Es gibt nur (zulässige) Konstruktoren mit 3 oder 6 Argumenten, aber keinen mit 2 Argumenten.
  4. Das Objekt wird zwar erzeugt, aber keiner Variablen zugewiesen und ist deshalb sinnlos.

Lösung zu Übung 5 Verzweigungen Zur Übung
public string GetSpeedRegion()
	{
		string result;
		int temp = Speed / 10;
		switch(temp) {
			case 0:			// Geschwindigkeit 0 bis 9
				if(Speed == 0)
					result = "Stillstand";
				else
					result = "Schrittgeschwindigkeit";
				break;
			case 1:			// Geschwindigkeit 10 bis 29
			case 2:
				result = "Wohnbereich";
				break;
			case 3:			// Geschwindigkeit 30 bis 49
			case 4:
				if(Speed == 30)
					result = "Wohnbereich";
				else
					result = "innerorts";
				break;
			case 5:			// Geschwindigkeit 50 bis 79
			case 6:
			case 7:
				if(Speed == 50)
					result = "innerorts";
				else
					result = "Landstraße";
				break;
			case 8:			// Geschwindigkeit 80 bis 99
			case 9:
				if(Speed == 80)
					result = "Landstraße";
				else
					result = "Bundesstraße";
				break;
			case 10:		// Geschwindigkeit 100 bis 129
			case 11:
			case 12:
				if(Speed == 100)
					result = "Bundesstraße";
				else
					result = "Autobahn";
				break;
			default:		// Geschwindigkeit ab 130
				if(Speed == 130)
					result = "Autobahn";
				else
					result = "unbegrenzt";
				break;
		}
		return result;
	}

Lösung zu Übung 6 Verzweigungen Zur Übung
public string GetSpeedRegion()
	{
		string result;
		int temp = (Speed-1) / 10;
		switch(temp) {
			case 0:			// Geschwindigkeit 0 bis 10
				switch(Speed) {
					case 0:
						result = "Stillstand";
						break;
					case 10:
						result = "Wohnbereich";
						break;
					default:
						result = "Schrittgeschwindigkeit";
						break;
				}
				break;
			case 1:			// Geschwindigkeit 11 bis 30
			case 2:
				result = "Wohnbereich";
				break;
			case 3:			// Geschwindigkeit 31 bis 50
			case 4:
				result = "innerorts";
				break;
			case 5:			// Geschwindigkeit 51 bis 80
			case 6:
			case 7:
				result = "Landstraße";
				break;
			case 8:			// Geschwindigkeit 81 bis 100
			case 9:
				result = "Bundesstraße";
				break;
			case 10:		// Geschwindigkeit 101 bis 130
			case 11:
			case 12:
				result = "Autobahn";
				break;
			default:		// Geschwindigkeit ab 131
				result = "unbegrenzt";
				break;
		}
		return result;
	}

Der Zweig für case 0 kann weiterhin mit if dargestellt werden:

// switch-Verschachtelung durch if ersetzt
			case 0:			// Geschwindigkeit 0 bis 10
				if(Speed == 0) 
					result = "Stillstand";
				else if(Speed == 10) {
					result = "Wohnbereich";
				else
					result = "Schrittgeschwindigkeit";
				break;

Lösung zu Übung 7 Schleifen Zur Übung
static void Main()
{
	UInt64 result = 0;
	UInt64 diff = 0;
	for ( int x1 = 1; x1 <= 64; x1++ ) 
	{
		if (x1 > 1)
		{
			diff = diff * 2;
		}
		else
		{
			diff = 1;
		}
		result = result + diff;
	}
	Console.WriteLine(result);
	Console.ReadKey();
}

Siehe auch Bearbeiten

Wikipedia hat die folgenden Artikel.

In den folgenden Kapiteln der Buchreihe gibt es weitere Erläuterungen.