Arbeiten mit .NET: C-Sharp/ Grundlagen/ Der Aufbau eines Programms/ Ein Programm zum Einstieg

Aus Wikibooks

Auch wenn wir bereits etwas Code gesehen und bearbeitet haben, wird es Zeit, uns ernsthaft und gezielt mit dem Programmieren in C# zu befassen.

Vorbereitungen[Bearbeiten]

Für diesen Einstieg und die Erläuterungen in den nächsten Kapiteln benutzen wir das Projekt Mein neues Auto, das wir bereits früher erstellt hatten:

  • bei der Besprechung der verschiedenen Programme (Notepad, Microsoft Visual Studio, SharpDevelop, MonoDevelop)
  • bei der Einführung in die OOP im vorigen Kapitel

Wenn Sie dieses Projekt nicht zur Verfügung haben (noch nicht oder nicht mehr), dann erledigen Sie dies bitte jetzt.

Bitte wundern Sie sich nicht, wenn Ihr Code mit den folgenden Beispielen nicht vollständig übereinstimmt. Durch Fehlerbeseitigung, eigene Versuche und Vorarbeiten der IDE kann es immer wieder einmal Unterschiede geben.

Hello World[Bearbeiten]

Natürlich können Sie auch mit dem Klassiker beginnen, der in keinem Programmierbuch seit 1970 fehlt. Erstellen Sie mit Ihrer IDE ein neues Projekt als Konsolenanwendung und schreiben Sie die folgenden Zeilen in den Bereich der Main-Methode:

Hello World als Einstieg
C++-Quelltext
System::Console::WriteLine("Hello World");
System::Console::WriteLine("Ende mit beliebiger Taste");
System::Console::ReadKey();
C#-Quelltext
System.Console.WriteLine("Hello World");
System.Console.WriteLine("Ende mit beliebiger Taste");
System.Console.ReadKey();
Delphi.Prism-Quelltext
System.Console.WriteLine('Hello World');
System.Console.WriteLine('Ende mit beliebiger Taste');
System.Console.ReadKey();
VB.NET-Quelltext
System.Console.WriteLine("Hello World")
System.Console.WriteLine("Ende mit beliebiger Taste")
System.Console.ReadKey()

Kompilieren Sie den Quelltext und starten Sie das Ausführen des Programms.

Aber wir wollten ja etwas anspruchsvoller vorgehen.

Bestandteile eines Programms[Bearbeiten]

Öffnen Sie also das Projekt Mein neues Auto; dies wollen wir genauer untersuchen. Wir beginnen mit der Auflistung im Projekte-Explorer.

Vorerst nicht so wichtig[Bearbeiten]

Mit den folgenden Teilen des Programms befassen wir uns zunächst nicht; sie werden von der IDE für spezielle Arbeitsweisen oder Einstellungen benutzt:

  • Die csproj-Datei enthält sämtliche Dateien, die für das Projekt benötigt werden; diese Datei wird von der IDE selbständig verwaltet. Änderungen durch den Programmierer gibt es fast niemals.
  • Ressourcen enthalten Bilder, feststehende Texte u.a. Wir benötigen sie (noch) nicht.
  • AssemblyInfo.cs enthält Angaben über das Programm, den Hersteller usw. – nämlich diejenigen Informationen, die man im Windows-Explorer über Dateieigenschaften lesen kann.

Referenzen[Bearbeiten]

Der Compiler muss wissen, welche Teile der .NET-Klassenbibliothek benutzt werden sollen. Es wäre viel zu aufwändig und würde deshalb zu lange dauern, wenn immer alles durchsucht würde. Um dies zu beschleunigen, werden die verwendeten Assemblies ausdrücklich angegeben. Standardmäßig sind es diese:

  • System wird schon wegen der einfachen Datentypen wie String oder Int32 immer benötigt.
  • System.Data wird bei der Zusammenarbeit mit Datenbanken u.ä. benötigt, könnte also bei unserer Anwendung entfernt werden.
  • System.Xml wird für die Verarbeitung von xml-Dateien benötigt, wir könnten also ebenfalls darauf verzichten.

Bereits beim Erstellen unserer Anwendung hatten wir eine Referenz hinzufügen müssen:

  • System.Drawing gehört zu Dingen, die beim Zeichnen z.B. auf dem Bildschirm benutzt werden. Bei uns ist die Color-Struktur wichtig.

Vor allem Anfänger erhalten beim Kompilieren häufig eine Fehlermeldung ähnlich dieser:

CS0234 / CS0246 - Der Typ- oder Namespacename "Foo" konnte nicht gefunden werden.

Dieser Fehler hat zwei mögliche Ursachen; für beide muss man in der .NET-Hilfe nachschauen, zu welcher Assembly dieser Typ bzw. Namespace gehört. Wichtig ist, dass eine Referenz (Verweis) zu dieser Assembly eingetragen ist. (Die zweite Ursache kommt etwas später.)

Quelldateien[Bearbeiten]

Alles andere wird in einer oder mehreren Textdateien gespeichert, die Code enthalten nach den Regeln der Programmiersprache, also von C#. Dieser Code kann von der IDE vorgegeben werden (wie es SharpDevelop bei einer Konsolenanwendung macht), von der IDE selbständig erstellt und verwaltet werden (wie bei allem, was der Designer bei Formularen macht), von anderen Code-Generatoren stammen oder vom Programmierer geschrieben werden.

Für C# gelten die folgenden Empfehlungen, und es gibt keinen Grund, jemals davon abzuweichen:

  • Die Endung (Extension) heißt cs.
  • Der Name der Datei sollte mit dem Namen der Klasse übereinstimmen (schon allein wegen der Übersichtlichkeit).
  • Jede Klasse sollte in einer eigenen Datei gespeichert werden (aus dem gleichen Grund).
  • Wenn eine Datei sehr groß wird, kann die Definition der Klasse auf mehrere Dateien aufgeteilt werden – siehe das Schlüsselwort partial.
  • Wenn ein Code-Generator (z.B. der Formular-Designer der IDE) beteiligt ist, wird das auch im Namen deutlich: Die Datei MainForm.cs enthält den eigenen Code für das Hauptformular, die Datei MainForm.Designer.cs den von der IDE verarbeiteten Code.

Auf der Grundlage dieser Empfehlungen enthält unsere Anwendung zwei cs-Dateien: Program.cs mit der Program-Klasse und Car.cs mit der Car-Klasse.

Bestandteile einer Quelldatei[Bearbeiten]

Für alle Bestandteile gilt:

  • Eine Anweisung wird mit einem Semikolon abgeschlossen:
    using System;
  • Eine Anweisung kann über mehrere Zeilen gehen; kürzere Zeilen sind einfach besser lesbar. Erst das Semikolon beendet eine Anweisung.
  • Die geschweiften Klammern {...} fassen immer einen Block mehrerer Anweisungen zusammen.

Ein "sauberer" Code enthält auch immer Einrückungen, damit sofort die Zusammenhänge erkennbar werden:

  • Die öffnende Klammer und ihre Entsprechung stehen vorzugsweise übereinander an derselben Stelle.
  • Für eine Klasse und ihre Member wird immer eine Stufe eingerückt.
  • Für die Kontrollstrukturen (Verzweigungen und Schleifen) wird ebenfalls eingerückt.

Ob die Einrückung mit der Tab-Taste oder mit 2, 3, 4 Leerzeichen erfolgt, ist Geschmackssache; das können Sie in den Optionen Ihrer IDE einstellen.

Die using-Direktive[Bearbeiten]

Untersuchen wir jetzt die Program.cs.

Jede C#-Quelldatei beginnt mit einer oder mehreren Anweisungen der folgenden Art:

using System;
using System.Drawing;

Dabei handelt es sich nur um eine Vereinfachung der Schreibweise. Zu einer Bezeichnung unter .NET gehören immer die Namen von Namespace, Klasse und Einzelheit. Der Anfang des Codes, der die Klassen DateTime, Console und Environment benutzt, müsste eigentlich so geschrieben werden.

if (System.DateTime.Now.Hour < 12) {
  	System.Console.Write("Guten Morgen, ");
   } /* usw. */
	System.Console.Write("Guten Abend, ");
   System.Console.WriteLine(System.Environment.UserName);

Die ständige Wiederholung von System macht den Code unübersichtlich. In einem Beispiel der .NET-Hilfe stünde dieser Code:

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(PurchaseOrder));

Manche Zeilen würden nur aus diesem Grund überlang und völlig unverständlich. Dann ist es viel einfacher, den Namespace einmalig am Anfang der Quelldatei zu schreiben und später nur noch die Klasse und ihre Einzelheit zu benutzen:

using System.Xml.Serialization;
   // später:
   XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder));

Beide Informationen zusammen – der Name des Typs im Befehl und der Name des Namespace bei using – sagen dem Compiler, was mit diesem Typ gemacht werden kann.

Merke
Der Compiler erkennt die Definition eines Typs nach dem Typnamen und dem Namespace; beides gehört zusammen.
Im Code kann der Name des Namespace entfallen, muss dann aber in einer using-Direktive genannt werden.


Damit kommen wir zur zweiten Ursache des o.g. Fehlers:

CS0234 / CS0246 - Der Typ- oder Namespacename "Foo" konnte nicht gefunden werden.

In der .NET-Hilfe steht, zu welchem Namespace ein Typ gehört. (Bei eigenen Typen müssen Sie in der eigenen Dokumentation oder im Quelltext nachschauen.) Wenn der Compiler den Namen eines Typs nicht erkennt, dann fehlt sehr oft die passende using-Direktive mit Angabe des Namespace.

Namespace[Bearbeiten]

Mit diesem Begriff wird eine Art Katalog bezeichnet, zu dem eine Klasse gehört. (Namensraum als deutsche Übersetzung wird auch benutzt, ist aber nicht so gebräuchlich.) Wie schon gesagt, wird eine Klasse durch die Kombination Namespace + Bezeichnung genau bestimmt. Im Code geschieht dies dadurch, dass die Definition der Klasse in die Angabe des Namespace eingebunden wird:

namespace Wikibooks.CSharp.Mein_neues_Auto
   {
   }

Dazu gehören die folgenden Angaben:

  • das Schlüsselwort namespace
  • der Name des Namespace
  • die geschweiften Klammern {...} als Zeichen dafür, was zu diesem Namespace gehört

Ein Namespace kann mehrfach auftreten; unsere Klasse Car wird demselben Namespace zugeordnet. Der Name kann grundsätzlich frei gewählt werden, sollte aber den Namenskonventionen entsprechen.

Mehr Informationen stehen gibt es in den Kapiteln über Referenzen, using und Namespace.

Eine Klasse[Bearbeiten]

Es wurde schon mehrfach erwähnt: Alles unter .NET sind Klassen. Folglich ist die nächste Ebene in der Struktur einer Quelldatei die Klasse:

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

Die Definition einer Klasse gehört in ihren Namespace und enthält die folgenden Angaben:

  • optional die „Sichtbarkeit“ der Klasse für andere Programmteile, z.B. public
  • das Schlüsselwort class
  • der Name der Klasse
  • die geschweiften Klammern {...} als Zeichen dafür, was zu dieser Klasse gehört

Der Name einer Klasse muss innerhalb eines Namespace eindeutig sein. In verschiedenen Namespaces kann es ihn wiederholt geben; Sie sollten aber bei mehrfacher Verwendung sehr zurückhaltend und vorsichtig sein. Ansonsten kann der Name grundsätzlich frei gewählt werden, sollte aber den Namenskonventionen entsprechen.

Regionen[Bearbeiten]

Diese dienen nur der Übersichtlichkeit innerhalb einer Quelldatei. Die IDE bietet eine Möglichkeit, sie zu „falten“ (mit dem Plus- bzw. Minuszeichen neben der Zeilennummer), sodass nur die Überschrift zu sehen ist:

namespace Wikibooks.CSharp.Mein_neues_Auto
   {
      public class Program
      {
         #region Felder
         #endregion
      }
   }

Eine Region wird durch die Direktive #region begonnen und mit #endregion abgeschlossen. Hinter #region gehört eine kurze Beschreibung; diese kann hinter #endregion wiederholt werden.

Hinweis: Für den Zusammenhang des eigentlichen Codes haben Regionen keinerlei Bedeutung. Sie dienen ausschließlich der besseren Übersicht.

Einzelheiten[Bearbeiten]

In der Klasse (innerhalb oder außerhalb von Regionen) wird alles definiert, was für die Klasse selbst – also für jedes Objekt dieser Art – von Bedeutung ist. Diese Einzelheiten werden getrennt erläutert.

Bestandteile des Codes[Bearbeiten]

Die Code-Auszüge stammen sowohl aus Program.cs als auch Car.cs; es gibt auch Varianten, die bisher nicht verwendet wurden.

Anweisungen[Bearbeiten]

Eine Anweisung regelt eine einzelne Maßnahme. Es gibt vor allem folgende Arten von Anweisungen:

  • Deklaration von Variablen, Feldern und Konstanten
// in Main der Program-Klasse
    string input;
    Car test = new Car();
    // als Felder der Car-Klasse
    private int numberOfSeats;
    private bool leftSignal = false;
    // in ShowSignals der Car-Klasse
    string result;
    // der folgenden Variablen kann kein anderer Wert zugewiesen werden
    public const string Language = "C#";
  • Zuweisung von Werten an Variable – siehe unter Variable und Konstante
  • Aufruf von Methoden mit und ohne Rückgabewerte – siehe unter Methoden
  • Anweisungen zur Steuerung des Programmablaufs – siehe unter Kontrollstrukturen
  • Kommentare, die den Code erläutern
    Ein Kommentar, der mit (mindestens) zwei Schrägstrichen // beginnt, endet am Zeilenende. Ein Kommentar, der mit /* beginnt und mit */ endet (bisher nicht verwendet), kann über mehrere Zeilen gehen.
  • Fehlerprüfungen
    Darauf haben wir bisher keinen Wert gelegt, aber für ein „normales“ Programm gehört das dazu. Dabei wird auch geregelt, was im Fehlerfall zu erledigen ist. (Wir weisen aber schon darauf hin: Guter Programmierstil versucht, Fehler zu vermeiden bzw. zu umgehen; für ein Verfahren wie im folgenden Beispiel gibt es schönere Lösungen.)
int input;
    try
    {
        // eine Zeile einlesen und versuchen ("try"), 
        // daraus eine int-Zahl zu machen ("konvertieren")
        input = Convert.ToInt32( Console.ReadLine() );
    }
    catch(Exception ex)
    {
        // falls keine Zahl eingegeben wurde, dann Fehlermeldung und Ersatzwert
        Console.WriteLine(ex.Message);
        input = -1;
    }
    finally
    {
        Console.WriteLine(input);
    }

Mehrere Anweisungen können durch geschweifte Klammern als Block zusammengefasst werden, in vielen Fällen ist dies notwendig wie im vorigen Beispiel – siehe auch unter Gültigkeitsbereiche.

Variable und Konstante[Bearbeiten]

Bei einem konkreten Objekt handelt es sich zwangsläufig um einen Teil des Arbeitsspeichers. Um damit zu arbeiten, müssen wir dem Programm sagen, um welchen Teil es sich handelt. Dazu werden Variable verwendet:

string input;
    Car test = new Car();
    private bool leftSignal = false;

Zur Festlegung einer Variablen gehören diese Teile:

  • Ein Bezeichner sagt, unter welchem Namen wir das betreffende Objekt ansprechen wollen.
    In den Beispielen handelt es sich um input, test, leftSignal.
  • Der Datentyp bestimmt, welche Art von Objekt im Arbeitsspeicher angelegt werden soll.
    In den Beispielen handelt es sich um string, Car, bool.
  • Die Initialisierung sorgt für die tatsächliche Zuweisung eines Wertes; erst danach kann mit dem Objekt gearbeitet werden.
    Die Variable input wird (noch) nicht initialisiert. Die Variable test wird durch new als neues Objekt vom Typ Car erzeugt. Die Variable leftSignal wird sofort als false festgelegt.
  • Zusätzlich kann der Gültigkeitsbereich der Variablen gesteuert werden: für die ganze Anwendung, für eine Assembly, für eine Klasse oder zwangsläufig nur innerhalb eines Blocks.
    Standardmäßig ist eine Variable nur lokal gültig, d.h. in dem Bereich, in dem sie deklariert wird.

Normalerweise kann einer Variablen später ein anderer Wert zugewiesen werden. In Einzelfällen kann das durch das Schlüsselwort const verhindert werden; dann sind Variable und Wert unveränderlich verbunden zu einer Konstanten wie oben:

public const string Language = "C#";

Kontrollstrukturen[Bearbeiten]

Jede Programmiersprache bietet verschiedene Möglichkeiten an, um den Ablauf innerhalb eines Programms zu steuern.

Verzweigungen durch if

In der Program.cs ist eine mehrteilige if-Verzweigung enthalten:

if (DateTime.Now.Hour < 12) {
		Console.Write("Guten Morgen, ");
	} else if (DateTime.Now.Hour < 18) {
		Console.Write("Grüß Gott, ");
	} else {
		Console.Write("Guten Abend, ");
	}

Es wird genauso gearbeitet, wie es gelesen wird. Wenn Fall 1 gilt, dann bearbeite Teil 1; andernfalls: wenn Fall 2 gilt, dann bearbeite Teil 2; andernfalls bearbeite Teil 3.

Die runden Klammern für die Bedingung sind immer notwendig; bei komplexeren Bedingungen sind weitere Klammern sinnvoll oder notwendig. Wenn der zu bearbeitende Teil nur aus einer einzigen Anweisung besteht, können die geschweiften Klammern entfallen. Einzelheiten werden in einem späteren Kapitel behandelt.

Verzweigungen durch switch

Für ganze Zahlen und Strings übernimmt die switch-Anweisung vielfache Verzweigungen, nämlich Fallunterscheidungen. Einzelheiten werden in einem späteren Kapitel behandelt.

Schleifen – while, do-while, for, foreach

Natürlich gibt es die üblichen Schleifen:

Die while-Schleife prüft die Bedingung für einen „Durchgang“ bereits am Anfang („Kopfschleife“). Es kommt vor, dass sie überhaupt nicht durchlaufen wird:

while( <bedingung> )
{
   <anweisungen>
}

Die do-while-Schleife prüft die Bedingung für einen „Durchgang“ erst am Ende („Fußschleife“). Sie wird mindestens einmal durchlaufen:

do
{
   <anweisungen>
} while( <bedingung> )

Die for-Schleife benutzt eine Zählvariable: Diese wird vor dem ersten „Durchgang“ initialisiert; am Ende eines jeden Durchgangs wird sie weitergesetzt (nicht unbedingt um 1 erhöht oder erniedrigt); vor dem nächsten Durchgang wird die Bedingung geprüft, ob ein neuer Durchgang gewünscht wird.

for( <initialisierung>; <bedingung>; <veränderung> )
{
   <anweisungen>
}

Beispiel für eine Standardschleife:

for( int x1 = 0; x1 < 10; x1 = x1 + 2 )
	{
		Console.WriteLine(x1);
	}

Eine Besonderheit ist die foreach-Schleife, bei der alle Elemente einer Auflistung nacheinander behandelt werden. Beispielsweise können wir alle Zeichen eines Strings einzeln prüfen:

string input = Console.ReadLine();
	foreach(char c in input) {
		Console.Write(c);
	}

Die runden Klammern für die Bedingungen bzw. den Bereich der Schleife sind immer notwendig. Die geschweiften Klammern dürfen bei for/foreach entfallen, wenn sie nur eine einzelne Anweisung enthalten; der Klarheit wegen wird empfohlen, diese Klammern immer zu setzen.

Alle diese Schleifen, ihre Varianten und Verwendung behandeln wir in späteren Kapiteln.

Sprunganweisungen – return, break, continue, throw, goto

Diese Anweisungen haben die unterschiedlichste Bedeutung je nach der Situation, in der sie verwendet werden. goto kann und sollte vermieden werden; es zeugt (fast) immer von einem schlecht strukturierten Programm. Wir haben bisher nur return verwendet, nämlich in Car.cs:

public string ShowSignals()
	{
		string result;
		// die if-Verzweigungen unterscheiden, welcher Wert
		// der Variablen result zugewiesen wird
		return result;
	}

Wenn eine Methode (siehe dort) den Rückgabetyp void hat, beendet return die Methode (auch wenn der Befehl ganz am Anfang der Methode steht). Wenn eine Methode einen speziellen Rückgabetyp – hier string – hat, dann übergibt return gleichzeitig den dahinterstehenden Wert – hier den aktuellen Inhalt der Variablen result – an den Aufrufer als Ergebnis der Methode.

Diese Anweisungen besprechen wir in einem eigenen Kapitel.

Methoden[Bearbeiten]

Nachdem wir Methoden bisher eher „intuitiv“ benutzt hatten, wollen wir etwas genauer darauf eingehen.

Eine Methode ist eine Menge von Anweisungen, die eine bestimmte Aufgabe erledigen sollen. In der OOP, also auch unter .NET und C#, gehören sie immer zu einer Klasse. Methoden werden je nach Situation unterschiedlich benutzt:

  • nur innerhalb einer Klasse oder mit Zugriff „von außen“
    • Die Methode speedChange in Car.cs dient der Vereinheitlichung des Arbeitsablaufs innerhalb der Klasse, sie kann nicht von der Main-Methode aus aufgerufen werden.
    • Die Methode Accelerate dagegen hat nur Sinn für ein bestimmtes Objekt, wird also von der Main-Methode aus unter Bezug auf das Objekt mycar benutzt.
  • mit oder ohne Parameter
    • Bei Accelerate und Delay wird ein Wert übergeben, um den sich die Geschwindigkeit ändern soll.
    • Bei ToString oder ShowSignals wird der aktuelle Zustand abgefragt; dieser muss nicht genauer festgelegt werden.
  • mit oder ohne Rückgabewert
    • Bei ToString oder ShowSignals wird ein String gewünscht.
    • Bei Accelerate, Delay oder ShowValues wird eine Arbeit ausgeführt, ohne dass die Main-Methode beeinflusst wird.
  • mit Bezug auf die Klasse oder auf ein bestimmtes Objekt
    • Alle Methoden der Car-Klasse können nur benutzt werden, wenn dazu ein bestimmtes Auto existiert.
    • Für die Main-Methode muss die Klasse nur definiert sein, damit sie ausgeführt wird; eine bestimmte Instanz der Program-Klasse ist überflüssig. Dazu dient das Schlüsselwort static.

Der Vollständigkeit halber sei erwähnt, dass es sich bei den Konstruktoren sowie bei get und set der Eigenschaften um spezielle Methoden handelt:

  • Ein Konstruktor erzeugt eine Instanz der Klasse und gibt dieses Objekt zurück.
  • Mit get wird ein Wert (z.B. des internen Feldes) als Eigenschaft zurückgegeben.
  • Mit set wird ein Wert der Eigenschaft genommen und z.B. als (internes) Feld registriert.

Allgemein wird eine Methode wie folgt definiert, wobei sowohl die runden Klammern (für die Parameter) als auch die geschweiften Klammern (für die Anweisungen) obligatorisch sind.

<Zugriffsmöglichkeit>  <Rückgabewert>  Methodenbezeichner ( <Parameter> )
{ <Anweisungen> }

Die Festlegung „kein Rückgabewert“ erfolgt durch void. Ein Parameter wird durch Datentyp plus Bezeichner angegeben; dieser Name gilt nur innerhalb der Methode, sollte aber „nach außen“ die Bedeutung deutlich machen. Mehrere Parameter werden durch Kommata getrennt.

Schon diese kurze Übersicht zeigt die Komplexität des Themas. Dafür haben wir mehrere spätere Kapitel vorgesehen.

Gültigkeitsbereiche[Bearbeiten]

Wie schon gesagt, wird durch die Deklaration und Initialisierung einer Variablen eine Stelle im Arbeitsspeicher festgelegt; der Garbage Collector (GC) räumt auf, wenn die Variable nicht mehr benötigt wird. Woher weiß aber das .NET Framework, wann dies der Fall ist?

Grundsätzlich gilt: Eine Variable ist nur in dem Bereich gültig, in dem sie deklariert wird. So sind die Variablen input und mycar nur innerhalb der Main-Methode von Program.cs bekannt, die Variable id nur innerhalb der Car-Klasse und die Variable result nur innerhalb deren ShowSignals-Methode.

Es gibt noch weitere Einschränkungen. Bei der o.g. for-Schleife ist x1 weder davor nach danach bekannt; Gleiches gilt für c bei der foreach-Schleife. Auch kann eine Variable innerhalb eines Blocks, also gezielt für einige Anweisungen innerhalb von {...} deklariert und nur dort verwendet werden:

try
    {
        // eine Zeile einlesen und versuchen ("try"), 
        // daraus eine int-Zahl zu machen ("konvertieren")
        int input = Convert.ToInt32( Console.ReadLine() );
        Console.WriteLine(Math.Pow(input, 2));   // das Quadrat von input
    }

Aber dann ist die Variable außerhalb dieses Blocks nicht bekannt:

 Fehler CS0103: Der Name input ist im aktuellen Kontext nicht vorhanden.
C#-Quelltext
try
    {
        // eine Zeile einlesen und versuchen ("try"), 
        // daraus eine int-Zahl zu machen ("konvertieren")
        int input = Convert.ToInt32( Console.ReadLine() );
        Console.WriteLine(Math.Pow(input, 2));   // das Quadrat von input
    }
    catch(Exception ex)
    {
        // falls keine Zahl eingegeben wurde, dann Fehlermeldung und Ersatzwert
        Console.WriteLine(ex.Message);
        input = -1;
    }
    finally
    {
        Console.WriteLine(input);
    }

Deshalb wurde die Variable input oben bei Fehlerprüfungen vor der try-Anweisung deklariert.

Schließlich ist noch zu erwähnen, dass eine lokale Variable eine außerhalb bekannte Variable gleichen Namens überdeckt. In den Konstruktoren der Car-Klasse werden die gleichen Namen verwendet wie für die Felder. Innerhalb der Konstruktoren steht das einfache type für den übergegebenen Parameter; weil dieser Wert dem Feld type der (neuen) Instanz zugewiesen werden soll, muss this ausdrücklich angegeben werden, nur dadurch ist das Feld type auch im Konstruktor bekannt.

Sie sehen: Auch die Gültigkeit einer Variablen ist immer wieder von Bedeutung; wir werden darauf wiederholt eingehen.

Zusammenfassung[Bearbeiten]

In diesem Kapitel lernten wir die grundlegenden Bestandteile des Quellcodes kennen:

  • In der Regel enthält jede Datei eine Klasse. Klassen werden in Namespaces (Namensräumen) zusammengefasst.
  • Der Code besteht aus Anweisungen.
  • Zu den Anweisungen gehören Deklaration und Initialisierung von Variablen, Zuweisungen, Verzweigungen und Schleifen sowie Methoden.
  • Regionen und Kommentare dienen zur Gliederung und Erläuterung des Codes, haben aber für den Inhalt des Programms keine Bedeutung.

Große Bedeutung haben die Lesbarkeit und Übersichtlichkeit des Codes.

Übungen[Bearbeiten]

Übung 1 Definitionen Zur Lösung

Welche der folgenden Aussagen sind richtig, welche sind falsch?

  1. Über die "Referenzen" werden Klassenbibliotheken genannt, die der Compiler beim Erstellen des Programms benutzt.
  2. Jede DLL, die bei den Referenzen eingetragen ist, muss über using angegeben werden.
  3. Eine Code-Datei von C# hat in der Regel die Endung cs, aber das ist nicht verpflichtend.
  4. Jede Code-Datei muss genau eine vollständige Klassendefinition enthalten.
  5. Code muss durch #region gegliedert werden.
  6. Einrückungen dienen der Lesbarkeit, sind aber nicht erforderlich.

Übung 2 Definitionen Zur Lösung

Nennen Sie die vollständigen Namen der folgenden Typen:

  1. DateTime
  2. string
  3. Car
  4. Program

Übung 3 Definitionen Zur Lösung

Suchen Sie aus den Erläuterungen sowie ggf. aus der .NET-Hilfe heraus, für welche Art (Namespace, Typ, Objekt, Feld usw.) die folgenden Angaben stehen:

  1. System
  2. System.DateTime
  3. System.DateTime.Now
  4. System.DateTime.Now.Hour

Übung 4 Fehler CS0234 / CS0246 Zur Lösung

Nennen Sie die Schritte und möglichen Lösungen bei der folgenden Fehlermeldung:

CS0234 / CS0246 - Der Typ- oder Namespacename "Foo" konnte nicht gefunden werden.

Übung 5 Anweisungen Zur Lösung

Schreiben Sie Anweisungen, die die folgenden Aufgaben erledigen.

  1. Deklarieren Sie eine Integer-Variable output.
  2. Weisen Sie der Variablen einen festen Wert zu.
  3. Fassen Sie beide Anweisungen zu einer zusammen.
  4. Deklarieren Sie eine Variable audi vom Typ Car.
  5. Weisen Sie der Variablen ein neues Objekt vom Typ Car zu; benutzen Sie dazu eine zulässige Variante.
  6. Deklarieren Sie eine String-Variable und weisen ihr das Ergebnis der ToString-Methode zu, die zum Objekt audi gehört.
  7. Weisen Sie dieser String-Variablen den aktuellen Zustand der Blinklichter zu.
  8. Starten Sie ein Verfahren, mit dem für das Objekt audi der aktuelle Zustand der Blinklichter abgefragt und direkt auf der Console ausgegeben wird.

Übung 6 Verschiedenes Zur Lösung

Der folgende Code kann am Schluss der Program.cs in unserem Einstiegsprogramm eingefügt werden. Nennen und erläutern Sie die dort enthaltenen Fehler. Sie sollten einen Teil der Fehler bereits durch „scharfes Hinschauen“ erkennen können. Für weitere Fehlersuche können Sie den Compiler benutzen, aber die Erklärungen sollten darüber hinausgehen.

	string motor
	if DateTime.Now.Year < 2010 {
		input = "VW 2009";
		motor = "Diesel 1000cm³";
	} else 
		input = "VW 2010";
		motor = "Diesel 1200cm³";
	Car vw = new Car(input, motor);
	output = vw.ToString();
	vw.speedChange(25);
	vw.ShowSignals();

Übung 7 Methoden Zur Lösung

Schreiben Sie für die Car-Klasse eine Methode Rebuild mit folgendem Inhalt: Sitze und Lenkung werden umgebaut, der Wagen wird neu lackiert. Alle dafür nötigen Angaben sind anzugeben.

Lösungen[Bearbeiten]

Lösung zu Übung 1 Definitionen Zur Übung

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

Lösung zu Übung 2 Definitionen Zur Übung
  1. System.DateTime
  2. System.String
  3. Wikibooks.CSharp.Mein_neues_Auto.Car
  4. Wikibooks.CSharp.Mein_neues_Auto.Program

Lösung zu Übung 3 Definitionen Zur Übung
  1. System ist ein Namespace.
  2. System.DateTime ist ein Datentyp, der für Datum plus Uhrzeit steht.
  3. System.DateTime.Now ist ein konkretes Objekt des Typs System.DateTime, das die aktuelle Datum/Uhrzeit angibt.
  4. System.DateTime.Now.Hour ist eine Eigenschaft dieses konkreten Objekts mit Angabe der Stunde.

Lösung zu Übung 4 Fehler CS0234 / CS0246 Zur Übung
  1. Suchen Sie im Hilfe-Index oder der eigenen Dokumentation nach der entsprechenden Klasse.
  2. Kontrollieren Sie, ob die entsprechende Klassenbibliothek (Assembly) bei den Referenzen eingetragen ist; fügen Sie ggf. den Verweis hinzu.
  3. Anschließend ist (soweit noch erforderlich) der Namespace per using einzutragen.

Lösung zu Übung 5 Anweisungen Zur Übung
int output;
output = 23;
int output = 23;
Car audi;
audi = new Car("Audi A6", "1548 cm³, 115 kW", 114835);
string audistring = audi.ToString();
audistring = audi.ShowSignals();
Console.WriteLine(audi.ShowSignals());

Lösung zu Übung 6 Verschiedenes Zur Übung
  • Zeile 1: Semikolon erwartet.
  • Zeile 2: Syntaxfehler. ')' erwartet. '(' erwartet.
  • Zeile 5: Geschweifte Klammern {...} für den else-Zweig fehlen.
    Achtung: Der Compiler erkennt keinen Fehler. Aber zum else-Zweig gehört "für ihn" nur die input-Zuweisung. Die zweite motor-Zuweisung gehört zum Hauptteil dahinter und wird deshalb immer ausgeführt. Das ist einer der Gründe, warum wir dafür plädieren, immer Klammern zu setzen.
  • Zeile 8: Keine Überladung für die Methode Car erfordert 2-Argumente.
    Es gibt nur zulässige Konstruktoren mit 3 oder 6 Argumenten.
  • Zeile 9: Der Name output ist im aktuellen Kontext nicht vorhanden.
    Es bedeutet: diese Variable wurde nicht deklariert.
  • Zeile 10: Auf Car.speedChange(int) kann wegen der Sicherheitsebene nicht zugegriffen werden.
    Diese Methode wurde als private deklariert; die Geschwindigkeit kann nur über die Methoden Accelerate und Delay verändert werden.
  • Zeile 11: Der Rückgabewert der Methode ShowSignals wird nicht benutzt.
    Das ist zwar kein Fehler, aber meistens nicht sinnvoll. Wozu holt man sich das Ergebnis einer Methode, wenn man nichts damit macht?

Eine korrekte Lösung könnte so aussehen:

	string motor;
	if( DateTime.Now.Year < 2010 ) {
		input = "VW 2009";
		motor = "Diesel 1000cm³";
	} else {
		input = "VW 2010";
		motor = "Diesel 1200cm³";
	}
	Car vw = new Car(input, motor, 4189360);
	string output = vw.ToString();
	vw.Accelerate(25);
	Console.WriteLine( vw.ShowSignals() );

Lösung zu Übung 7 Methoden Zur Übung
	public void Rebuild(int newnumber, bool value, Color newpaint)
	{
		NumberOfSeats = newnumber;
		isLeftSteered = value;
		Paint = newpaint;
	}

In diesem speziellen Fall ist es gleichgültig, ob die neuen Werte den Eigenschaften NumberOfSeats und Paint zugewiesen werden oder den Feldern numberOfSeats und paint.

Siehe auch[Bearbeiten]

In den folgenden Kapiteln gibt es weitere Informationen:

Bei Wikipedia gibt es Artikel mit einem Überblick: