Script Csharp
Script Csharp
Version 3.0
2
1 Ziel
Diese Einführung zeigt Ihnen grundlegende Dinge im Umgang mit dem Microsoft- Development-Environment
Version 8.0 (= Visual Studio 2005) und dem Microsoft-.NET- Framework Version 2.0.
Während Sie ein kleines Programm erstellen, lernen Sie unter Anwendung der Programmiersprache C# drei
Werkzeuge des Development-Environments kennen :
• den Source-Code-Editor,
• den Compiler und
• den Debugger.
Neben der Programmiersprache C# können Sie mit dieser Programmierumgebung auch andere
Programmiersprachen verwenden, z.B. C++ oder Visual Basic.NET, die Nachfolge-Sprache der inzwischen
abgekündigten Visual Basic - Version 6.0.
Alternativ können Sie für die Lösung der Vorlesungsbeispiele und Studienarbeiten auch andere
Programmierumgebungen für C# verwenden, z.B. die von Borland. Aus Ressourcen- Gründen können diese
allerdings nicht vom Bauinformatik- Labor unterstützt werden.
Sie lernen einen typischen Arbeitsablauf kennen (Quellcodedatei erstellen - Quellcode kompilieren -
Syntaxfehler beseitigen - Quellcode erneut kompilieren - C# Programm ausführen und testen) und Sie sehen
auch, wie man sich selbst helfen kann, wenn der Compiler einen Syntaxfehler beanstandet.
2 Hintergrund-Informationen
2.1 Einleitung
Wer sich heute das Ziel setzt, Programme für das Internet und für das Betriebssystem Windows zu schreiben,
für den ist die noch junge Programmiersprache C# (sprich "C sharp") zu einer interessanten Option geworden.
Wer sich mit C# beschäftigt, bekommt es bald mit Begriffen wie .NET (sprich "Dot Net") oder "Common
Language Runtime System" zu tun.
Dieser Abschnitt will Hintergrund-Informationen vermitteln, und damit die Furcht vor einigen dieser "Buzz-
Words" nehmen.
Außerdem soll C# vorgestellt und anderen Programmiersprachen gegenübergestellt werden.
JIT Compiler
Native Code
Auch die Unterstützung verschiedener Rechnertypen, wie Desktops, Tablett-PCs, Mobile Computer und
Smartphones wird mit .NET wesentlich vereinfacht.
Nicht zuletzt kann auch die Internet- Programmierung mit dem .NET- Framework sehr wirtschaftlich
umgesetzt werden. Dazu steht als Erweiterung ASP.NET (Active Server Pages) zur Verfügung. Häufig wird
im Zusammenhang mit dieser Technologie auch der Begriff "Web Matrix" verwendet.
ASPNET_WP.EXE
form1.aspx form1_aspx.dll
<%@ WebService language="c
language="c#"
#" class
class=
=
using System.Web.Services
System.Web.Services;;
[WebService(Description="Provides a v
public class Simple
{ [WebMethod(Description=„
[WebMethod(Description
Returns a nice greeting
greeting")]
")]
=„ Page Class
public string Hello
{
Hello()
return "Hello
()
Hello";
";
Assembly
}
}
Kompilieren Instanzieren
3
JIT Compiler
Native Code
In der obigen Darstellung wird zwischen "Managed" und "Unmanaged" Code unterschieden. Managed Code
ist ein Byte-Code, der über die CLR verbunden und übersetzt werden kann. Unmanaged Code ist Maschinen-
spezifischer Code, der sich nicht den Regeln der CLR unterwirft, und direkt auf das Betriebssystem zu greifen
kann.
In der Regel wird man für Anwendungs-Programme immer Managed Code verwenden und Unmanaged Code
nur dann, wenn man System-nah entwickeln muss.
Der Grund für diese radikale Änderung liegt darin, dass in Visual Basic.NET nun Strukturen eingeführt
worden, die in anderen Programmiersprachen schon längst zum Alltag gehören. Visual Basic.NET ist jetzt
auch eine objektorientierte Sprache geworden und in ihrem inneren Aufbau praktisch identisch zu C#.
Visual Basic.NET musste diesem Umbau deswegen unterzogen werden, damit man Programmkomponenten,
die damit geschrieben werden, ohne Aufwand über das .NET- Framework und die CLR mit Komponenten
verbinden kann, die mit einer anderen Sprache programmiert werden, z.B. mit C#.
Dieser Schritt sollte von "traditionsbewssten" Programmierern als deutlicher Fingerzeig verstanden werden,
dass die Zeiten der reinen strukturierten Programmierung nun endgültig der Vergangenheit angehören, und die
objektorientierte Programmierung der Stand der Technik ist.
Während Visual Basic 6.0 also eindeutig ein "Auslaufmodell" ist, spielen bei der Wahl der Nachfolgesprache
Geschmacksfragen oft eine entscheidende Rolle. Kann man sich mit einer C- Syntax anfreunden oder will man
lieber doch bei einem vertrauten optischen Erscheinungsbild bleiben?
Wenn man also schon so viel neues lernen muss, und es rein technisch gesehen keine Unterschiede zwischen
Visual Basic.NET und C# gibt, liegt der Gedanke nahe, bei diesem Schritt auch gleich auf die C-Syntax zu
wechseln. Auf alle Fälle gewinnt man dadurch erheblich mehr persönliche Kompatibilität mit anderen
Programmiersprachen und letztlich Freiheit durch den Abschied von einer Nicht-Standard- Sprache.
3 Begleit-Literatur
Eine Auswahl möglicher Begleit-Literatur.
Diese Auswahl ist als Vorschlag und nicht als verbindlich zu verstehen. Wer ein C#-Buch entdeckt, das ihn mehr
anspricht, als die hier vorgeschlagenen, kann das ebenso gut verwenden. Alle Bücher beschreiben letzt endlich
die gleiche Technologie, sie wird nur in jedem Buch etwas anders verpackt.
Für den Anfang, zum Nachschlagen und zum Selbst-Studium:
• Visual C#.NET
• Walter Doberenz, Thomas Kowalski
• Grundlagen und Profiwissen
• Hanser Verlag
• Schritt-für-Schritt-Anleitungen, zusätzlicher sehr guter "Kochbuch"-Teil, entstanden und erprobt im
Studienbetrieb
• Microsoft Visual C#, Schritt für Schritt
• John Sharp, Jon Jagger
• Microsoft Press
• Schritt-für-Schritt Anleitung für den Anfänger und Profi
Wenn jemand tiefer einsteigen möchte:
• Windows-Programmierung mit C#
• Charls Petzold
• Microsoft Press
• Eines der Standard-Bücher für die Windows- Programmierung
Für erfahrene Programmierer:
• Programmieren mit C#
• Jesse Liberty
• O'Reilly
• Gut zu lesender kompakter Einstieg
• C#
• Eric Gunnerson
• Galileo Computing
• Kompakter Einstieg und Referenz
• C# in a Nutshell
• Peter Drayton u.A.
• O'Reilly
• Sehr ausführliche Referenz zum Nachschlagen, weniger als Einstieg gedacht.
Microsoft Visual Studio.NET ist eine Programmierumgebung, die folgende Komponenten enthält:
• Common Language Runtime
• Projektverwaltung
• Zur Verwaltung der Dateien für Quell- und Maschinencode verschiedener Programmierprojekte
• Assistenten
• Unterstützt bei der Erstellung von verschiedenen Programmtypen wie Windows- oder Internet-
Anwendungen
• Editor
• Zur komfortablen Eingabe des Quell-Codes
• Compiler
• Zum Übersetzen in den Byte- oder Maschinen-Code
• Debugger
• Zur Fehlersuche in Programmen
Achtung Verwechslungsgefahr:
Bitte beachten Sie, dass die Vorgänger-Version Microsoft Developer Studio hieß, die sich evtl. ebenfalls noch
auf dem von Ihnen verwendeten Rechner befindet, mit der Sie aber keinen C#-Code erzeugen können:
und rufen Sie das Menue Datei - Neu - Projekt auf, bzw. File - New -Project, falls die Englische Version
installiert ist:
Wir wollen als erstes ein Programm schreiben, das auf der Konsole, also auf dem schwarzen System-
Bildschirm läuft, und dort einen Text ausgibt:
• Klicken Sie in der Liste der Projekttypen auf Visual C#-Projekte
• Klicken Sie im Vorlagen-Feld auf das Symbol "Konsoleanwendung"
• Geben Sie bei Name: einen Namen für Ihr Projekt ein, z.B. HalloKonsole . I.d.R. ist dieser Projektname
identisch mit dem Namen Ihrer Anwendung. Bitte verwenden Sie keine Zahlen als erstes Zeichen des Namens
und keine Sonderzeichen. Eine Folge von Worten unterscheidet man übelicher Weise durch Groß- und
Kleinbuchstaben.
• Wählen Sie unter Speicherort: Ihren persönlichen Ordern für Programmieren, den Sie im Schritt 0
angelegt haben. Klicken Sie dazu auf den Knopf "Durchsuchen..."
• Klicken Sie dann auf OK
Es öffnet sich jetzt ein neues Fenster, das im nächsten Schritt erklärt wird.
Außerdem wurde unter Ihrem Ordner für das Programmieren automatisch ein neuer Ordner mit dem Namen
"HalloKonsole" angelegt (das können Sie mit dem File- Explorer überprüfen). Dieser neue Ordner enthält
neben ihrer Quell-Code-Datei eine Reihe von weiteren Dateien und Unterordnern, die für die Integration in die
CLR und die Projektverwaltung erforderlich sind.
Dieses Fenster enthält bereits die wichtigsten C#-Quellcode-Kommandos, die für eine Konsole- Anwendung
erforderlich sind. Man muss jetzt diesen Quellcode nur noch auf seine Bedürfnisse anpassen.
Sie sehen, dass der Mehraufwand, der bei einer modernen Programmiersprache wie C# gegenüber früheren
Basic-Dialekten erforderlich ist, von einer modernen Programmierumgebung zumindest zum Teil wieder
ausgeglichen wird. Es muss also letztendlich kaum mehr editiert werden.
Erklärung des ersten automatisch generierten Quell-Codes:
• Kommandos, die blau angezeigt werden, sind reservierte, C#-eigene Kommandos und dürfen nur für
ihren bestimmten Zweck, nicht aber z.B. für eigene Variablen-Namen verwendet werden.
• Zeilen in grün, die mit // eingeleitet werden sind Kommentarzeilen. Es ist ratsam, seinen Quell- Code
ausreichend mit Kommentaren zu versehen, die beschreiben, was man sich beim Programmieren gedacht hat.
Das hilft einem selber, wenn man den Quell-Code nach einiger Zeit wieder verstehen muss, und hilft einem
Kollegen, der evtl. einmal mit dem Quell- Code arbeiten muss.
• Zeilen in grün, die mit /// eingeleitet werden sind spezielle Kommentarzeilen, die zur automatischen
Generierung einer Dokumentation von Programmen verwendet werden.Hierzu wird XML verwendet und mit
speziellen XML-Tags (= XML-Kommandos), können bestimmte Teile einer Dokumentation gesteuert werden.
So beschreibt z.B. der Kommentar zwischen den <summary> ... </summary> Tags eine kurze, einzeilige
Beschreibung einer Klasse oder Methode. Daneben gibt es noch weitere XML-Tags, die die Generierung einer
Dokumentation steuern. Später kann der XML-Code aus dem Programmtext herausgefiltert und daraus eine
Dokumentation generiert werden, die man mit Internet-fähigen Viewern oder Browsern anzeigen kann.
• using System; sagt aus, dass dieses Programm auf Systembefehle zugreifen möchte, z.B. die
Ausgabe von Text auf den Bildschirm. Damit nicht jeder Programmierer diese Programmieraufgabe von neuem
lösen muss, wird der Code für solche häufig vorkommenden Befehle in einer Bibliothek mit C# mitgeliefert.
Neben der System-Bibliothek gibt es noch eine Reihe weiterer Bibliotheken. Der Compiler braucht nun einen
Hinweis, wo er mitgelieferten Code suchen soll, um diesen dann beim Übersetzten dem Programm hinzufügen
zu können. In diesem Fall wird dem Compiler mitgeteilt, dass er in der System- Bibliothek suchen soll.
Methoden enthalten den eigentlichen ausführbaren Programmcode. Jede Klasse muss mindesten eine Methode
enthalten. Der erste Einstiegspunkt einer Anwendung ist die Methode "Main". Jede Anwendung muss deswegen
eine Main-Methode enthalten. Besteht eine Anwendung aus nur einer Klasse mit nur einer Methode, muss diese
deswegen den Namen Main tragen.
static void Main (string[] args) ist der Kopf für einer Main- Methode. Diesem folgt ein
Rumpf, der wieder mit { } eingefasst wird. D.h., auch der Kopf einer Methode wird nicht mit einem ;
abgeschlossen, sondern auch ihm folgt ein Paar von geschweiften Klammern. Nach dem Namen einer Methode
folgt ein Paar runder Klammern ( ), das die Liste der Parameter einfaßt. Auch eine leere Parameter-Liste muss
mit ( ) eingefaßt werden.
Weiter Erklärungen zu static , void , string[] args später, wenn Methoden detailliert erklärt
werden.
• Die Klasse "Console" enthält eine Reihe von Methoden. Wir wollen mit unserem Programm einen Text
ausgeben und benötigen deswegen eine passende Funktion aus der Klasse "Console". Die Namen der
Methoden einer Klasse werden beim Aufruf duch einen "." vom Klassennamen getrennt.
Diese Entwicklungsumgebung enthält sog. IntelliSense-Listen, die einem bei der Auswahl geeigneter
Methoden aus einer Klasse helfen:
Sobald Sie hinter Console einen Punkt eingeben, öffnet sich eine Liste der möglichen Methoden. Scrollen
Sie nach unten und doppelklicken sie auf WriteLine.
• Ihr Programm-Code sollte jetzt so aussehen:
static void Main( string [] args)
{
Console.WriteLine
}
• Geben Sie jetzt eine Runde Klammer auf ein (
• Wieder meldet sich IntelliSense, denn in der Klasse Console gibt es mehrere Methoden mit dem Namen
WriteLine. Diese unterscheiden sich jedoch nur auf Grund ihrer unterschiedlichen Parameterliste.
(Man spricht hier von sog. überladenen Methoden, mehr dazu später, wenn Methoden detailliert erklärt
werden)
• Schließen die jetzt die Runde Klammer ) und schließen Sie diese normale, ausführbare Progammierzeile mit
einem ; ab.
• Tipp: Wann immer sie eine Klammer öffnen, geschweift oder rund, sollten Sie sie immer auch gleich
wieder schließen { } ( ), und dann erst den Inhalt einfügen. Dadurch vermeidet man Klammer- Fehler.
• In die runden Klammern setzten Sie "Hello World!"
• Jetzt sollte Ihr Programm wie folgt aussehen:
static void Main( string [] args)
{
Console.WriteLine("Hello World!");
}
• Ergänzen Sie noch ein Kommando, mit dem eine Eingabe von der Tastatur abgefragt mit. Wir werden die
Eingabe nicht weiter auswerten, halten damit aber das Programm an, so dass man das man sich die Ausgabe
ansehen kann. Nach Drücken der Enter-Taste läuft das Programm weiter und wird beendet, das
Ausgabefenster wird geschlossen.
static void Main( string [] args)
{
Console.WriteLine("Hello World!");
Console.ReadLine();
}
• Wenn Sie auf eine beliebige Taste klicken, schließt sich die Konsole wieder.
MethodenName2 (Parameterliste)
{
...;
...;
}
}
}
4.7.1 Namespaces
Wie bereits erwähnt, sorgen Namspaces dafür, dass bestimmte Namen im Kontext des Namespaces eindeutig
sind. Sie sind praktisch ein Behälter (in der Fachsprache verwendet man hier den Begriff Container) für andere
Bezeichner von Klassen und Methoden.
Der im Beispielprogramm verwendete Aufruf:
Console.WirteLine("Hello World");
müsste ganz korrekt und ausführlich eigentlich so ausehen:
System.Console.WirteLine("Hello World");
Durch die Angabe von
using System;
wird jedoch der Bezug zu einem Namespace "System" hergestellt (die Bibliothek mit diesem Namespace ist
bereits im Lieferumfang von C# enthalten). Durch diesen Bezug ist es nicht mehr notwendig, bei jedem Aufruf
von Console.WirteLine("Hello World") auch noch System davorzusetzen, sondern es ist klar,
dass es sich nur um diesen bestimmten Console.WirteLine-Befehl handeln kann. Die Verwendung des
using-Kommandos erspart also Tipp- Arbeit.
Kleine Übung:
Sie können die Wirkung von using ausprobieren:
Setzten Sie dazu vor using System und [STAThread] ein Kommentarzeichen:
// using System;
...
//[STAThread]
Damit sind diese beiden Programmzeilen jetzt unwirksam und werden vom Compiler übersprungen. Das
Programm kennt jetzt also nicht mehr den Kontext zum Namespace "System".
Wenn Sie jetzt die Projektmappe neu erstellen und damit das Programm neu übersetzen, werden Sie eine
Meldung bekommen, dass 'Console' nicht gefunden werden konnte. Setzen Sie jetzt vor
Console.WirteLine("Hello World"); noch System., so dass die komplette Zeile so aussieht:
System.Console.WirteLine("Hello World");
und erstellen die Projektmappe noch einmal. Jetzt kann das Programm wieder fehlerfrei übersetzt werden.
4.8 Gratulation
Gratulation, Sie haben die wichtigsten Schritte der Programmierung mit C# erfolgreich durchlaufen!
Sie ...
• können jetzt schon einigen C#-Code zumindest im Ansatz verstehen
• haben die wichtigsten Strukturierungsmöglichkeiten (Klassen und Methoden) kennengelernt
• wissen, wie Programmcode für den Kopf und Rumpf einer Klasse und einer Methode aussieht und wie
dieser mit { } gekennzeichnet wird
• wissen, dass
• Klassen Methoden enthalten, und immer mindestens eine Methode enthalten sein muss,
• die zuerst aufgerufene Methode Main heißen muss,
• Methoden immer eine Parameterliste haben, die mit ( ) gekennzeichnet wird, selbst wenn die Liste leer
ist.
• wissen, dass normale ausführbare Programmzeilen mit ; abgeschlossen werden
• haben das IntelliSense kennegelernt
• können ein Programm übersetzen
• könne ein von Ihnen erstelltes Programm ausführen
• haben gesehen, dass selbst ein sehr einfaches C# Programm zwar einiges an "Overhead" benötigt, dass
aber mit einer komfortablen Entwicklungsumgebung der Aufwand dafür minimal ist.
und rufen Sie das Menue Datei - Neu - Projekt auf (evtl. ist die Englische Version installiert, hier finden Sie
den Aufruf unter File - New - Project ):
Achten Sie darauf, dass Sie ein C#-Project erstellen. Sollten Sie voreingestellt eine andere Sprache sehen,
finden Sie Visual C# unter dem Eintrag "Other Languages"
Wir wollen jetzt ein Programm schreiben, das unter Windows läuft und in einem Windows- Formular (= sog.
Form) einen Text ausgibt und eine Texteingabe erlaubt:
• Klicken Sie in der Liste der Projekttypen auf Visual C#-Projekte
• Klicken Sie im Vorlagen-Feld auf das Symbol "Windows-Anwendung" bzw. "Windows Application"
• Geben Sie bei Name: einen Namen für Ihr Projekt ein, z.B. WindowsEinAusgabe. I.d.R. ist dieser
Projektname identisch mit dem Namen Ihrer Anwendung und damit Ihres Namespaces. Zur Erinnerung: bitte
verwenden Sie keine Zahlen als erstes Zeichen des Namens und keine Sonderzeichen. Eine Folge von Worten
unterscheidet man üblicher Weise durch Groß- und Kleinbuchstaben.
• Wählen Sie unter Speicherort: Ihren persönlichen Ordern für Programmieren, den Sie im bereits vor
Ihrem ersten C#-Programm angelegt haben. Klicken Sie dazu auf den Knopf "Durchsuchen..."
In den nächsten Schritten wird mit Hilfe des Visual Designers eine kleine Windows- Benutzschnittstelle
entworfen und erzeugt. Dazu werden in das Formular drei Steuerelemente eingefügt.
• Klicken Sie in der Toolbox auf Label und klicken Sie dann in die linke obere Ecke des Formulars
• Sie benötigen jetzt den Projektmappen-Explorer. Falls er noch nicht angezeigt wird, öffnen Sie ihn auf
der Symbolleiste mit der Schaltfläche Solution-Explorer
• Der Solution-Explorer sollte bei Ihnen folgendes anzeigen:
• Der für Ihre Windowsanwendung grundsätzlich erforderliche und automatisch generierte Programmcode
wurde in den Dateien Programm.cs und Form1.cs abgelegt, die sie auch im Solution-Explorer sehen.
• Klicken Sie im Solution-Explorer auf Program.cs und dann auf die Schaltfläche Code anzeigen
Die Quelldatei Program.cs wird im Text- und Editor-Fenster angezeigt.
• Am oberen Rand des Editor-Fensters sehen Sie Registerkarten. Damit können Sie zwischen dem
Entwurfsmodus des Designers und dem Editor hin- und herschalten.
Wie jedes C#-Programm besteht auch Ihre Anwendung aus einem namespace, der den Namen trägt, den Sie
für das Projekt gewählt haben. Als nächste Unterstruktur folgt die class die in diesem Fall automatisch mit
Program benannt wurde. Klassen enthalten eine oder mehr Methoden, in denen die ausfürbaren
Programmanweisungen programmiert werden. Der Einstiegspunkt jeder Anwendung ist die Methode Main,
d.h. in jeder Anwendung muss mindestens die Methode Main enthalten sein.
Die Klasse Program ist in diesem Fall sehr kurz: sie initialisiert einige grundsätzlichen Dinge, die jedes
Windowsprogramm benötigt und startet mit dem Befehl
Application.Run(new Form1()); eine weitere Klasse mit dem Namen Form1().
• Das Starten und Beenden einer Windows-Anwendung erledigt die aufgerufene Methode Run() in der Klasse
Application. Diese Klasse ist Teil des Klassenbaumes System.Windows.Forms und wird vom Compiler
gefunden, weil dieser Namespace mit using ganz am Anfang angekündigt wurde. Wenn Sie im Quelltext
den Maus-Cursor auf Application oder auf Run legen (ohne zu klicken!), werden entsprechende Erklärungen
eingeblendet.
• Den zur Klasse Form1() gehörigen Programmcode können Sie sehen, wenn Sie im Solution-Explorer auf
Form1.cs klicken und dann auf die Schaltfäche für den Quellcode.
• Forms1.cs enthält den gesamten C#-Code, der durch Ihren Entwurf der Benutzerschnittstelle automatisch
generiert wurde.
• Ab der Version Microsoft Visual Studio 2005 werden sog. partielle Klassen unterstützt. Partielle Klassen
wirken wie eine einzige Klasse, können aber auf verschiedene Quelldateien aufgeteilt werden. In unseren
einfachen Beispielen, wird der Nutzen noch nicht ersichtlich und scheint eher zu verwirren. Bei Projekten
aber, an denen viele Programmierer gleichzeitig arbeiten, ist diese Möglichkeit der zusätzliche Aufteilung
sehr hilfreich.
Während in der Vorgängerversion von Visual Studio 2005 in der Datei Form1.cs bereits alle für eine kleine
Windowsanwendung erforderlichen Kommandos enthalten waren, werden diese ab der Version 2005 auf
partielle Klassen aufgeteilt. Der Quellcode von Form1.cs dient jetzt also nur noch dazu, die Klasse Form1()
zu initialisieren.
• Schauen Sie sich den Quelltext an:
• Am Anfang wurden eine Reihe using-Direktiven eingefügt. Dadurch kann Ihr Programm Klassen
und Methoden verwenden, die in verschiedenen Bibliotheken mitgeliefert werden. Der erhebliche
Programmieraufwand, um Windows-Programme zu starten, zu beenden und um Steuerelemente in
einer grafischen Benutzerschnittstelle zu programmieren, ist damit schon vorweggenommen. Der
Compiler wird beim Übersetzten aus den Bibliotheken den Code mit dazu laden, der für die
Steuerelemente erforderlich ist.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
• Als oberste Organisations-Ebene wurde wieder der Namespace angelegt, dessen Name vom
Projektnamen übernommen wurde.
namespace WindowsEinAusgabe
{
...
}
• Die nächste Organisationsebene ist die Klasse, der hier der Name Form1 zugewiesen wurde. (Es hätte
auch irgend ein anderer Name sein können.). Außerdem wird festgelegt, dass es sich hier um eine
partielle Klasse handelt.
namespace WindowsEinAusgabe
{
public partial class Form1 : Form
{
...
}
}
Klassen werden später ausführlich behandelt. Dann wird auch erklärt werden, wozu das Kommando
public erforderlich ist. Nehmen Sie es momentan einfach hin.
• Die Klasse Form1 enthält die Methode Form1 und ist damit ein sog. Konstruktor:
...
public partial class Form1 : Form
{
...
public Form1()
{
...
}
...
}
Ein Konstruktor ist eine spezielle Methode, die den gleichen Namen wie die Klasse trägt. Sie wird
ausgeführt, wenn das Formular erzeugt werden soll und kann Code enthalten, der das Formular
initialisiert. Man sagt in der Fachsprache: Der Konstruktor erzeugt eine Instanz einer Klasse im
Speicher (genauer im Heap).
Bildlich kann man sich das wie das Kuchen-Backen vorstellen: Der Konstruktor ist der Bäcker, die
Kuchenform entspricht der Klasse und die Instanz entspricht dem einzelnen gebackenen Kuchen. Die
Initialisierung entspricht den speziellen Zugaben für einen bestimmten Kuchen, also etwa Rosinen
oder Nüsse.
In der Methode Form1 wird die Methode InitializeComponent() aufgerufen, die weiter unten im
Programmcode programmiert ist und im Laufe der nächsten Punkte erklärt werden wird.
...
public Form1()
{
...
InitializeComponent();
...
}
...
• Den weiteren Programmcode, der zu dieser Klasse Form1 gehört und für ihre Anwendung automatisch
generiert wurde, finden sie in der Datei Form1.Designer.cs, deren Inhalte Sie sehen können, wenn
sie im Solution-Explorer darauf klicken und den Quellcode öffnen.
• Dieser Programmcode gehört, wie alle bisher vorgestellten Komponenten, zum Namespace
WindowsEinAusgabe und ist partieller Teil der Klasse Form1. Er initialisiert und enthält die
Strukturen, die für das Windowsformular und die auf ihm abgesetzten Oberflächenelemente
erforderlich sind.
• In C# wird der erforderliche Speicherplatz für verwendete Ressourcen automatisch wieder an das
Betriebssystem freigegeben, sobald diese nicht mehr benötigt werden. Dies erledigt der sog.
Garbage Collector. Allerdings weiß man nicht im voraus, wann der Garbage Collector diese
Bereinigung durchführen wird. Diese Prozedur, die erhebliche Systemleistung beansprucht, kann
also zu einem unerwünschten Zeitpunkt erfolgen und dem Anwender als unangenehmes "Hängen"
des Programms vorkommen. Um wertvolle Ressourcen bald wieder freizugeben und um den
Zeitpunkt der Freigabe selbst bestimmen zu können, kann man eine Dispose- Methode einbauen.
Diese erledigt dann die Bereinigung und verhindert, dass der Garbage Collector zu einem späteren,
evtl. ungünstigen Zeitpunkt noch einmal versucht, diese Ressourcen wieder freizugeben.
• In der Klasse wurden komplexe Variablen in Form von sog. Feldern erzeugt. Felder speichern Daten
für eine Klasse. Hier sehen Sie, dass solche Variablen für die vorhin von Ihnen abgesetzten
Oberflächenelemente automatisch angelegt wurden.
namespace WindowsEinAusgabe
{
public class Form1 : System.Windows.Forms.Form
{
...
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button button1;
...
}
}
Diese Felder enthalten die Datenstrukturen für die Steuerelemente, die Sie dem Formular im
Entwurfsmodus hinzugefügt haben. Die Felder tragen die Namen label1 und button1 und verweisen
über obige Codierung auf entsprechende, bereits mitgelieferte Datenstrukturen.
Die Bedeutung von Feldern, die obige Codierung und die Erklärung, wozu das Kommando private
dient, werden später genauer erklärt.
• Die nächste Methode ist evtl. im Editor-Fenster ausgeblendet. Dies sieht dann so aus:
Öffnen Sie diesen Code-Bereich, indem Sie auf das kleine "+" klicken.
Sie werden sehen, dass jetzt ein Code-Bereich geöffnet wird, der durch zwei region- Kommandos
eingerahmt wird. Mit Region kann man einen Code-Bereich strukturieren, um ihn im Editor ein- und
ausblenden zu können.
In diesem Fall wurde der in diesem Bereich enthaltene Code deshalb in eine Region strukturiert und
ausgeblendet, weil man ihn im Editor nicht verändern sollte. Dieser Programmcode wird durch die
interaktive Arbeit mit dem Designer im Entwurfsmodus des Formulars komplett generiert, und wird
automatisch überschrieben, sobald man im Entwurfsmodus Änderungen vornimmt. Das
Zusammenstellen eines Formulars im Entwurfsmodus ersetzt also die wesentlich aufwendigere
händische Programmierung.
• Nachdem sie die Region geöffnet haben, können Sie die Methode InitializeComponent sehen.
...
partial class Form1
{
...
private void InitializeComponent()
{
this.Label1 = ...
...
}
...
}
Die Anweisungen in dieser Methode legen die Eigenschaften der Steuerelemente fest und wurden
automatisch auf Grund des Designs aus dem Entwurfsmodus erzeugt. Wie schon gesagt, sollten Sie
diesen Programmcode nie direkt im Editor verändern, da er bei Änderungen im Design automatisch
neu erzeugt und überschrieben wird und Ihre Änderungen dabei verloren gingen. Falls Sie
Änderungen im Design vornehmen wollen, machen Sie das immer im Entwurfsmodus des Designers.
• Abschließende Anmerkung:
In der Regel werden Sie Ihren eigenen Programmcode in die sehr einfach aufgebaute Datei Form1.cs
eingeben. Alle Änderungen und Ergänzungen am Aussehen der Oberfläche werden automatisch im
Quellcode der Datei Form1.Designer.cs generiert. Die Veränderung diese Datei sollte also Visual Studio
vorenthalten bleiben. Bis auf seltene Ausnahmen (z.B. bei falscher Anwendung von Visual Studio) ist es
nicht erforderlich, in der Datei Form1.Designer.cs irgendetwas von Hand etwas zu programmieren.
Da der Programmcode als partielle Klasse auf die Dateien Form1.cs und Form1.Designer.cs aufgeteilt
wurde und damit letztlich wie eine einzige Klasse Form1() wirkt, haben sie auch im Quellcode der Datei
Form1.cs Zugriff auf alle Oberflächenstrukturen, die im Quellcode der Datei Form1.Designer.cs definiert
wurden.
• Sie sind wieder im Entwurfsmodus des Designers. Klicken Sie auf die Schaltfläche Eigenschaften .
Jetzt wird rechts unten das Eigenschaftsfenster angezeigt.
• Klicken Sie im Formular auf das von Ihnen plazierte Steuerelement für die Schaltfläche, auf der
automatisch der Name "button1" geschrieben wurde.
• Das dazugehörige Eigenschaftsfenster wird darauf hin eingeblendet. Klicken Sie den Eintrag Text an
• Überschreiben Sie den Eintrag button1 mit OK und drücken Sie dann die Enter-Taste
• Im Formular wird jetzt auch der Name button1 auf dem Steuerelement für die Schaltfläche durch OK
ersetzt.
• Klicken Sie jetzt das Steuerelement für das Beschriftungsfeld an (label1), und klicken im dazugehörigen
Eigenschaftsfenster wieder auf den Eintrag Text
• Überschreiben Sie hier label1 mit Geben Sie Ihren Namen ein und drücken die Enter- Taste
• Falls der neue Text nicht ganz in den vorgesehenen Platz für das Beschriftungs-Steuerlement passen
sollte, können Sie dessen Rahmen an den weißen Anfassern (=Handels) entsprechend anpassen.
• Erzeugen Sie auf dem Formular auch noch eine Textbox und wiederholen sie die letzten Schritte auch
noch für das Steuerelement des Texteingabefeldes. Überschreiben Sie hier den Namen textBox1 mit hier im
Eigenschaftsfenster.
Es erscheint jetzt ein neues Dialogfenster, das Sie mit Ihrem Namen begrüßt und das von der Methode
MessageBox.Show("Grüß Gott "+textBox1.Text)
erzeugt wird. Das Text-Attribut vom Objekt textBox1 liefert Ihren Namen.
5.8 Gratulation
Herzlichen Glückwunsch, Sie habe Ihr erstes Windows-Programm geschrieben mit dem man Text ein- und
ausgeben kann.
Dabei haben Sie folgendes gelernt:
• Es wurden zwar wesentlich mehr Programmzeilen erzeugt, als beim ersten Beispiel, was vielleicht
zunächst verwirrend war, der prinzipielle Aufbau ist jedoch so wie schon beim ersten Programm. Auch dieses
Windows-Programm gliedert sich in
• Namespace
• Klasse
• Felder
• mehrere Methoden
• Neu hinzugekommen ist, dass eine Klasse neben Methoden auch Felder für Daten enthalten kann
• Auch neu war, dass man Bereiche des Codes mit einer Region einfassen kann, um sie im Editor zur
besseren Übersicht ein- und aus-blenden zu können.
• Die Benutzerschnittstelle für ein Windows-Programm kann man grafisch und interaktiv
zusammenstellen, der dazugehörige Programmcode wird automatisch generiert. Man kann also eine Windows-
Benutzerschnittstelle durch Zeichnen programmieren, ohne selbst Programmcode schreiben zu müssen.
• Zwar ist das Programmieren unter Windows im Vergleich zu früher aufwändiger und komplizierter
geworden, eine moderne Programmierumgebung kann aber einen Großteil des Aufwandes abnehmen.
• Der größte Teil dieses Programmes wurde automatisch erzeugt, selbst musste man in diesem Beispiel
nur eine einzige Programmzeile eingeben.
6 C#-Bausteine
6.1 C#-Syntax
• C# ist Case-sensitiv, d.h. es wird zwischen Groß- und Kleinschreibung unterschieden. Dies gilt auch für
Bezeichner von Klassen, Methoden und Variablen. So beschreiben etwa die Bezeichner LängeDesBalkens und
Längedesbalkens zwei unterschiedliche Dinge
• Ausführbare Anweisungen befinden sich überwiegend in Methoden. Ausführbare Anweisungen müssen
mit ; abgeschlossen werden.
z.B.:
Console.WriteLine("Hello World!");
• Bezeichner dürfen nicht mit Zahlen und Sonderzeichen beginnen
• Bezeichner dürfen keine Leerzeichen enthalten
• Setzt sich ein Bezeichner aus mehreren Worten zusammen, so werden diese mit Groß- und
Kleinbuchstaben von einander getrennt
• Keine Sonderzeichen in Bezeichner-Namen
• Wie schon früher erläutert, gibt es neben den ausführbaren Anweisungen Kopfzeilen von Strukturen
wie Klassen und Methoden. Der den Kopfzeilen folgende Rumpf wird in {} eingefaßt. Nach Kopfzeilen wird
deshalb kein ; gesetzt.
• C# ist eine sog. formatfreie Sprache, d.h. Leerzeichen und Zeilenumbrüchen können fast beliebig und
sehr frei verwendet werden. Es ist allerdings sehr ratsam, Zeilenumbrüche und Tabulatoren so zu verwenden,
dass der Programmcode optisch gut strukturiert und damit begreifbar wird.
6.3 Variablen
Eine Variable ist ein Platzhalter für eine Adresse, die auf einen Speicherplatz bestimmer Größe verweist, der
einen Wert aufnehmen kann. Man kann sich Variablen wie Namen für verschieden große Schubladen in einem
großen Schrank vorstellen. In einem Programm muss jede Varaible einen eindeutigen Namen tragen. Der
Varaiblenname wird verwendet, um auf den Wert zuzugreifen, der in der Variablen gespeichert ist.
Regeln zur Benennung von Variablen:
• Keine Unterstriche
• Verwenden Sie für verschiedene Varaiblen keine Namen, die sich nur durch Groß- und Kleinschreibung
unterscheiden, etwa meineVariable und MeineVariable.
• Man sollte Namen von Variablen mit einem Kleinbuchstaben beginnen lassen, z.B. laengeTraeger
• Setzt sich der Bezeichner aus mehreren Worten zusammen, setzten Sie die Worte aneinander und
beginnen das zweite und die folgenden Worte mit einem Großbuchstaben. Das ist die sog. "Kamel-
Schreibweise", wenn man damit den Anblick einer Kamelherde assoziiert, bei der man Höcker an Höcker sieht.
Noch weiter unten können Sie in der Methode, die durch den Doppel-Klick im Formular-Editor erzeugt wurde,
folgendes finden:
private void button1_Click( object sender, System.EventArgs e)
{
MessageBox.Show("Grüß Gott "+ textBox1.Text);
}
Hier wird eine Methode Show der Klasse MessageBox aufgerufen, der als Parameter ein String übergeben wird,
der sich aus den Teilen "Grüß Gott " und dem Text zusammensetzt, der im Attribut Text der Variablen
textBox1 abgespeichert wurde. Ursprünglich stand in diesem Attribut ja der String "hier" der dann von dem
String des Namens überschrieben wird, den der Anwender eingeben kann.
6.3.4 Felder
In dem eben genanten Code-Beispiel:
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button1;
werden die Variablen nicht innerhalb einer Methode, sonder unmittelbar am Beginn der Definition einer
Klasse und damit außerhalb aller darin enthaltenen Methoden deklariert. Wie schon früher erwähnt, bezeichnet
man Varaiblen, die an dieser besonderen Stelle deklariert werden, als Felder.
Zur Erinnerung: Felder haben in der Klasse Gültigkeit, in der sie deklariert wurden. Damit können ihre Werte
in allen Methoden der Klasse gelesen oder beschrieben, und damit geändert werden.
Dies kann man sehr schön an der Variablen textBox1 beobachten. In der Methode InitializeComponent() wird
dem Attribut textBox1.Text zunächst der Wert hier zugewiesen, der dann durch die Eingabe des Namens eines
Anwenders überschrieben wird. Später kann die Methode button1_Click(...) den Wert der Variablen mit
textBox1.Text auslesen.
Anmerkung:
Die Bezeichnung Felder kann bei Programmierern, die andere Sprachen kennen, zu einem Missverständnis
führen, denn dort wird dieser Begriff manchmal für mehrdimensionale Variablen verwendet. Die Verwirrung
entsteht, weil das englische Wort für mehrdimensionale Variablen, array, im deutschen ebenfalls oft mit Feld
übersetzt wird.
(Es wäre also doch überlegenswert, zumindest in der IT nicht alle englischen Fachbegriffe zwanghaft
einzudeutschen)
6.3.6 Typ-Umwandlungen
Mitunter ist es notwendig den Typ einer Variablen in einen anderen Typ umzuwandeln, um diese Variable
dann konsistent mit anderen Variablen weiterbehandeln zu können, die den anderen Typ haben.
Text -> Zahl:
Soll z.B. eine Zahl, die der Anwender ein einem Text-Formular eingibt, und die dadurch erst einmal den Typ
String hat, weiter als Zahl verarbeitet werden, muß die entsprechende Variable in einen Zahlentyp
umgewandelt werden. Folgendes Beispiel zeigt, wie dies mit der Methode Parse der Klasse int erfolgen kann:
int zahl1 = int.Parse(textBox1.Text);
Der Wert vom Typ string, der im Attribut Text der komplexen Variablen textBox1 abgespeichert ist, wird
durch diese Anweisung in einen neuen Wert vom Typ int umgewandelt, der in der Variablen zahl
abgespeichert wird.
Zahl -> Text:
Umgekehrt, kann man auch aus einer Variablen mit einem Zahlen-Typ eine Umwandlung in einen String-Typ
erreichen. Dazu gibt es die Methode ToString(), die in allen Objekten der Basistypen enthalten ist. Im Code
könnte das so aussehen:
int zahl2 = 30;
textBox1.Text = zahl2.ToString();
6.4.1 Vorrang
Sämtliche Operatoren in C# sind in Vorrangstufen eingeteilt. Dabei haben die Operatoren *, /, % eine höhere
Priorität als + und - .
Die Rechnung
2+3*4
wird also so berechnet:
2 + (3 * 4)
Siehe auch
6.4.2 Orientierung/Assoziativität
Neben den Vorrangsstufen besitzen die Operatoren eine Orientierung (bzw. Assoziativität) und damit ein
Richtung in der sie ausgewertet werden.
Der "="-Operator ist rechtsassoziativ und hat damit die Orientierung rechts nach links. In folgender
Anweisung wir der Variablen zahl der Wert 30 zugeordnet:
int zahl = 30;
Die arithmetischen Operatoren +, -, *, /, % sind linksassoziativ und haben damit die Orientierung links nach
rechts. Folgende Rechnung:
2/3*4
wird also wie folgt ausgewertet:
(2 / 3) * 4
Siehe auch
6.4.3 Verbundzuweisungs-Operatoren
Neben dem Zuweisungsoperator "=" und den arithmetischen Operatoren gibt es außerdem noch eine
Mischform, bei der Zuweisung und arithmetische Operation in einer sehr kompakten Schreibweise
zusammengefasst werden. Das sind sog. Verbundzuweisungs-Operatoren, die von Profis aus Gründen der
Arbeitsersparnis immer anstelle der ausführlichen Schreibweise verwendet werden.
Anstelle der Anweisung:
a = a + 4;
kann man auch verkürzt schreiben:
a += 4;
Diese Regel gilt auch für alle anderen arithmetischen Operatoren:
Ausführlich Verkürzt
variable = variable * zahl; variable *= zahl;
Vorrang und Assoziativität von *=, /=, %=, +=, -= entsprechen dem =, d.h. auch sie sind rechtsassoziativ
(siehe).
Wie schon der + -Operator, kann auch += auf Zeichenketten angewandt werden:
string name = "Sepp";
string gruss = "Servus ";
gruss += name; // anstelle von gruss = gruss + name;
Console.WriteLine(gruss); // auf dem Bildschirm erscheint: Servus Sepp
6.4.4 Inkrement-/Dekrement-Operatoren
Da das In- bzw. Dekrementieren von Variablen um den Wert 1 beim Programmieren sehr häufig vorkommt,
wurde auch für diesen Fall eine verkürzte Schreibweise eingeführt, die von Profis immer verwendet wird.
Anstelle von:
zaehler = zaehler +1;
bzw.:
zaehler += 1;
kann man verkürzt schreiben:
zaehler++;
Das selbe gilt für das dekrementieren um 1:
Ausführlich Verkürzt
variable = variable + 1; variable++;
variable += 1; variable++;
variable = variable - 1; variable--;
variable -= 1; variable--;
Präfix- Postfix-Schreibweise
Im Zusammenhang mit Inkrement-/Dekrement-Operatoren muss noch darauf hingewiesen werden, dass es hier
zwei Möglichkeiten gibt, die sich unterschiedlich auswirken:
• zaehler++; das ist eine Postfixinkrementation
• ++zaehler; das ist eine Präfixinkremantation
Dito für zaehler--; und --zaehler;
Beide Schreibweisen erhöhen die Variable "zaehler" um 1 (bzw. vermindern sie um 1).
Allerdings ist der Wert von ...
• ... zaehler++ der Wert der Variablen vor der Inkrementierung
• ... ++zaehler der Wert der Variablen nach der Inkrementierung
Deshalb liefert folgendes Programm ein zunächst vielleicht überraschendes Ergebnis:
int x;
x = 50;
Console.WriteLine(x++);
// x hat den Wert 51, aber x++ den Wert 50,
// d.h. 50 wird ausgegeben
x = 50;
Console.WriteLine(++x);
// x hat den Wert 51 und ++x ebenfalls,
// d.h. 51 wird ausgegeben
Dieses Verhalten wird meist in while- und do-Schleifen verwendet (siehe).
6.5 Übung 1
6.5.1 Variablen auf Konsole anzeigen
Erstellen Sie ein C#-Programm, das auf Konsole läuft und in dem je eine int, long, float, double, char, string
Variable deklariert und mit einem Wert belegt wird.
Anschließend soll das Programm diese Werte auf dem Bildschirm anzeigen.
7 Programm-Steuerungen
7.1 Grundlagen
Zur Steuerung des Programmflusses des funktionalen Quell-Codes kennt jede Programmiersprache
Steueranweisungen. Dazu zählen sog. bedingte Anweisungen (z.B. if, switch), mit denen man logische
Entscheidungen programmieren kann, und Programmschleifen (z.B. while, do, for), mit denen man Teile des
Programm-Codes wiederholt durchlaufen kann.
7.2 if
Häufig muss man auf Grund irgendwelcher Ereignisse alternative Programmzweige vorsehen und eine
Verzweigung im Programmfluss einbauen. Dies kann man am einfachsten mit dem if-Statement erreichen.
Optional kann man nach einem if auch noch ein else einbauen.
if prüft einen bestimmten logischen Sachverhalt und führt nach positiver Prüfung die nächste im Quell- Code
folgende Anweisung aus. Bei negativer Prüfung wird die übernächste Anweisung ausgeführt. Sollen nach
einem if-Statement mehrere Anweisungen ausgeführt werden, so müssen diese in einem {}-Block eingefasst
werden. Gleiches gilt für else.
Man kann nach einem if-Statement auch grundsätzlich {} setzen, auch wenn nur eine Anweisung ausgeführt
werden soll, und die Klammern damit nicht zwingend erforderlich sind. Diese Praxis hat den Vorteil, dass
optisch ganz klar zum Ausdruck kommt was gemeint ist, und dass später ggf. leicht noch weitere Zeilen
hinzugefügt werden können, ohne dass man dann die {} vergisst.
Syntax:
if (Ausdruck)
Anweisung;
else
Anweisung;
oder
if (Ausdruck)
{
Anweisung1;
Anweisung2;
}
Der Typ des Ausdruckes ist bool, d.h. if prüft nur auf TRUE oder FALSE
Logische Prüfungsmöglichkeiten (Operatoren) (siehe auch):
• == Prüfung auf Gleichheit
• >=
• <=
• >
• <
• != Prüfung auf Ungleichheit
z.B.:
...
if (a==0)
MessageBox.Show("Achtung, a=0");
else
MessageBox.Show("Der Kehrwert von a = " + 1/a);
...
Achtung:
if (last = 50) // Das Erzeugt einen Compiler-Fehler
if (last == 50) // So ist es richtig
7.3 switch
Soll eine Fallunterscheidung von mehreren Fällen erfolgen, kann man dies mit einer Reihe von if und else if
Anweisungen durchführen. z.B.:
if (tag == 0)
tagName = "Sonntag";
else if (tag ==1)
tagName = "Montag";
... usw.
Eleganter ist in so einem Fall das switch-Statement, dessen Syntax nach folgenden Regeln funktioniert:
switch (kontrollAusdruck)
{
case konstanterAusdruck :
Anweisungen;
break;
case konstanterAusdruck :
Anweisungen;
break;
case konstanterAusdruck :
Anweisungen;
break;
...
default :
Anweisungen;
break;
}
kontrollAusdruck muss vom Typ Integer oder String sein. Er wird einmal ausgewertet und das Programm
springt dann in den Fall, dessen konstanterAusdruck mit dem kontrollAusdruck übereinstimmt. Sollte es keine
Übereinstimmungen geben, wird der default- Fall angesprungen. Die Berücksichtigung eines default-Falles ist
optional.
Das break-Statement bewirkt, dass der switch-Block an dieser Stelle abgebrochen und verlassen wird.
Obiges Beispiel würde mit switch so aussehen:
switch (tag)
{
case 0 :
tagName = "Sonntag";
break;
case 1 :
tagName = "Montag";
break;
case 2 :
tagName = "Dienstag";
break;
...
default :
tagName = "unbekannt";
break;
}
7.4 break
Wie schon beim switch (siehe) erklärt, kann man die break-Anweisung einsetzen, um die Ausführung einer
bestimmten Programmsteuerung schlagartig zu beenden, ohne sie noch einmal auszuführen
7.5 continue
Im Gegensatz zum break, wird durch die Anweisung continue veranlaßt, dass das Programm weiterläuft und
eine übergeordnete Programmsteuerung sofort ausgeführt wird.
break und alternativ continue werden meist zur Steuerung von Schleifen-Durchläufen eingesetzt. (siehe)
7.6 Programm-Schleifen
7.6.1 Schleifen für Wiederholungen
Häufig stellt sich einem beim Programmieren die Aufgabe, einen Teil des Programmcodes wiederholt
auszuführen zu müssen. Dazu dienen Programmanweisungen für Schleifen.
Schleifen führen einen bestimmten Programmcode so lange aus, bis eine Abbruchbedingung erreicht wurde.
Die Überprüfung, ob die Abbruchbedingung erreicht wurde, kann, je nach Typ, am Anfang oder am Ende der
Schleife durchlaufen werden.
Erfolgt die Überprüfung am Schleifenanfang, kann das dazu führen, dass die Schleife evtl. auch überhaupt
nicht durchlaufen wird. Falls das Abbruchkriterium gleich zu Beginn erfüllt wird, spricht man von einer sog.
abweisenden Schleife.
Erfolgt die Überprüfung erst am Schleifenende, führt das dazu, dass der betreffende Programmcode auf alle
Fälle wenigstens einmal durchlaufen wird.
Neben diesem Unterscheidungsmerkmal, gibt es Schleifentypen die auf ein bestimmtes Ereignis warten, bis
sie abgebrochen werden. Typischer Weise, weiß man beim Programmieren noch nicht, wie oft diese Schleife
durchlaufen wird, da das Abbruchkriterium evtl. durch das Verhalten des Anwenders bestimmt wird.
Weiß man jedoch schon beim Programmmieren, wie oft eine Schleife ausgeführt werden soll, wird man einen
Typ verwenden, bei dem man die Anzahl der Durchläufe vorherbestimmen kann. Typische Anwendungsfälle
dafür sind z.B. Algorithmen oder der Zugriff auf Matrizen.
Folgende Tabelle gibt eine Übersicht der verfügbaren Schleifentypen und ihrer Eigenschaften.
7.6.2 while-Schleife
while-Schleifen werden verwendet um eine oder mehrere Anweisungen so lange zu wiederholen, solange ein
Ausdruck vom typ bool den Wert true besitzt.
Syntax:
while (boolescherAusdruck)
Anweisung;
Sollen mehrere Anweisungen wiederholt ausgeführt werden, so sind diese in { } einzuklammern:
while (boolescherAusdruck)
{
AnweisungX;
AnweisungY;
...
AnweisungZ;
}
Im folgendem Beispiel werden in einem Programmfragment die Zahlen von 0 bis 9 am Bildschirm dargestellt:
int i = 0;
while (i != 10)
{
ConsoleWriteLine(i);
i++;
}
Die Überprüfung (i != 10) ergibt so lange den Wert true, solange die Variable i von 0 aufwärts zählend kleiner
gleich 9 bleibt. Erreicht die Variable i den Wert 10, ergibt die Überprüfung (i != 10) den Wert false und die
Ausführung der Schleife wird abgebrochen.
7.6.3 do-Schleifen
Die do, besser die do-while-Schleife, ist eine while-Schleife, bei der die Abbruchbedingung erst am
Schleifenende getestet wird. D.h., der Programmcode in der Schleife wird auf alle Fälle einmal durchlaufen
und erst dann wird mit einer while- Anweisung geprüft, ob weitere Durchläufe erfolgen sollen.
Syntax:
do
Anweisung;
while (boolescherAusdruck);
Auch hier müssen mehrere Anweisungen in { } Klammer eingefaßt werden:
do
{
AnweisungX;
AnweisungY;
...
AnweisungZ;
{
while (boolescherAusdruck);
Beispiel:
int i = 0;
do
{
ConsoleWriteLine(i);
i++;
{
while (i != 10);
7.6.4 for-Schleifen
Weiß man schon zum Zeitpunkt der Programmerstellung, wie oft eine Schleife durchlaufen werden
soll, verwendet man die for- Schleife.
Syntax:
for (Initialisierung; boolescherAusdruck; aktualisiereKontrollVariable)
Anweisung;
Auch hier gilt, dass mehrere zu wiederholende Anweisungen in { } einzuklammern sind:
for (Initialisierung; boolescherAusdruck; aktualisiereKontrollVariable)
{
AnweisungX;
AnweisungY;
...
AnweisungZ;
}
In folgender Reihenfolge werden die einzelnen Komponenten ausgewertet und ausgeführt:
• Initialisierung findet nur einmal zu Beginn statt
• Überprüfung des booleschen Ausdrucks
• Ausführung der Anweisungen
• KontrollVariable wird aktualisiert
• Überprüfung des booleschen Ausdrucks
• usw.
Folgendes Beispiel eines Programmfragmentes errechnet in Einer- Schritten das Quadrat einer Zahl zwischen
0 und 10:
doublex=0.0;
Folgendes wäre eine umständlich und merkwürdig formulierte while-Schleife (vergleiche mit dem Beispiel im
Kapitel der while-Schleife):
int i = 0;
for(; i != 10 ;)
{
Console.WriteLine(i);
i++;
}
So würde das Beispiel aus dem Kapitel der while-Schleife aussehen, wenn man es als for- Schleife
implementiert:
for(inti=0; i != 10 ; i++)
{
Console.WriteLine(i);
}
Anmerkung:
Eine for-Schleife kann man auch als while- Schleife implementieren:
Statt:
for (Initialisierung; boolescherAusdruck; aktualisiereKontrollVariable)
Anweisung;
könnte man auch (umständlich) programmieren:
Initialisierung;
while (boolescherAusdruck)
{
Anweisung;
aktualisiereKontrollVariable;
}
while (true)
{
Console.WriteLine(i);
i++;
if (i == 0)
continue;
else
Console.WriteLine("Kehrwert = " + 1/i);
if (i != 10)
continue;
else
break;
}
Kritische Anmerkung:
Finden Sie den obigen Programmcode nicht etwas unübersichtlich? Der Eindruck täuscht nicht, denn die
Steuerung von Schleifendurchläufen mit continue führt leider meist zu unübersichtlichen und schwer
wartbarem Programmcode. Erfahrene Programmierer versuchen deshalb möglichst ohne continue
auszukommen. Falls es sich nicht vermeiden läßt, wir der entsprechende Programmcode gut kommentiert.
7.7 Übung 4
Ein Einfeldträger sei mit einer Einzellast belastet, die sich vom linken zum rechten Auflager bewegt.
Erstellen Sie ein Programm, das die Auflagerkräfte der beiden Auflager in Abhängigkeit von Last und ihrem
Angriffspunkt errechnet. Die Ergebnisse sollen in 1/10-Schritten der wandernden Last ausgegeben werden.
Vom Anwender soll die Größe der Last sowie die Länge des Einfeldträgers eingeben können.
Die Ergebnisse sind auf dem selben Form darzustellen, auf dem der Anwender die Eingabewerte eintippen
kann. Verwenden Sie dazu eine List-Box. Die Ergebniswerte können in der Listbox nach folgendem Muster
aufgelistet werden:
meinListBoxName.Items.Add( "...." + meineVariable);
Nachdem Sie Ihr Programm erfolgreich zum Laufen gebracht haben, setzten Sie den Cursor in den Teil des
Programmcodes, den Sie erstellt haben, starten Sie den Debugger und lassen Sie dann den Debugger Schritt
für Schritt laufen. Beobachten Sie, wie sich Ihr Programm verhält.
8 Methoden
8.1 Grundlagen
In den bisherigen Beispielen haben wir Methoden bereits intuitiv kennengelernt. Jetzt sollen sie detailliert
erklärt werden:
Methoden gehören (neben Klassen und Namespaces) zu den Sprachelementen in C#, mit denen man
Quellcode strukturieren kann. Siehe auch Architektur eines C#-Programmes.
Programmierer, die nicht- objektorientierte Sprachen kennen, liegen mit der Vorstellung, dass Methoden so
etwas wie "Unterprogramme" oder "Funktionen" sind, nicht ganz falsch. Dennoch wird für diese Struktur in
objektorientierten Sprachen der Begriff "Methode" verwendet, um auszudrücken, dass sie doch nicht ganz
dasselbe sind, denn übergeordnet gibt es ja außerdem noch Klassen und Namespaces.
In C# enthalten die Methoden sämtlichen ausführbaren Quellcode, also alle Programmanweisungen, die mit ;
abgeschlossen werden.
Wenige Ausnahmen von dieser Regel sind die Deklarationsanweisungen von Feldern, also Variablen, deren
Gültigkeit sich über eine ganze Klasse, und damit über mehrere Methoden erstrecken, und die using-
Anweisungen, über die eine Referenz auf andere Namespaces hergestellt wird.
Methoden sind also eine Sammlung von Anweisungen, die unter einem Namen zusammengefasst werden.
Eine besondere Methode ist die Main-Methode: sie ist die erste Methode, die in einer Anwendung ausgeführt
wird. Jeder Anwendung muss eine Main-Methode enthalten.
8.2 Syntax
Wie schon früher erläutert, bestehen Methoden aus einer Kopfzeile, die den Namen mit einer Parameterliste
enthält, und einem Rumpf, der in geschweiften Klammern eingeschlossen wird. Der Rumpf enthält die
ausführbaren Programmanweisungen einer Methode.
rückgabeTyp methodenName(parameterliste) // Die Paramerterliste kann leer
sein
{
// Methodenkörper, hier folgen die Anweisungen
...;
...;
}
Falls die Parameterliste leer ist, müssen nach dem Namen trotzdem die () Klammern folgen
Aufruf von Methoden:
Methoden werden aufgerufen, indem man ihren Namen aufruft. Da Methoden Werte zurückliefern können,
können sie auch rechts vom = -Zeichen stehen oder selbst wieder als Parameter in einem Methodenaufruf
eingesetzt werden. z.B.:
eineMethode(a,b,c);
int a = eineAndereMethode(x, y);
eineMethode(eineAndereMethode(x, y) , b, c);
Rufende und gerufene Methode greifen auf den selben Speicherbereich zu.
Hierzu werden später bei passender Gelegenheit Beispiele gezeigt werden.
8.6 Merke
• Der Typ der Methode muss dem Typ des Rückgabewertes entsprechen
• Methoden geben mit der return-Anweisung Werte zurück
• Gibt eine Methode keinen Wert zurück, muss ihr Typ void sein
• Mit einer return-Anweisung ohne Wert, kann eine Methode vom Typ void an jeder Stelle im
Programmablauf verlassen werden
• Methoden mit gleichem Namen und gleichem Typ, aber mit unterschiedlichen Parameterlisten, sind
jeweils andere Methoden. Parameterlisten können sich in Typ und Anzahl der einzelnen Parameter
unterschieden. In solchen Fällen sagt man, dass ein Bezeichner überladen wird.
• Die Methode, die als erste in einer Anwendung durchlaufen wird, muss Main heißen. Ihr Typ ist void
• In Parameterlisten von Methoden werden ...
• ... Varaiblen mit einfachen Datentypen als Wert übergeben (= call by Value)
• ... Variablen mit komplexen Datentypen als Referenz übergeben (= call by Reference)
• siehe auch Gültigkeit von Variablen
8.7 Übung 2
8.7.1 Rechner unter Windows mit Methoden
Erstellen Sie ein C#-Programm für Windows, das über ein Formular zwei Zahlen einliest, diese dann auf
Knopfdruck addiert und das Ergebnis in einem Fenster ausgibt. Erstellen Sie für die verschiedenen Funktionen
des Rechners je eine Methode.
9 Auswahl-Anweisungen
9.1 Steuerung von Alternativen
Komplexe Problemlösungen bedingen, dass es mehrere Lösungsalternativen gibt und deshalb der
Programmfluss auf Grund von Fallunterscheidungen in verschiedene Äste aufgespaltet und dann entsprechend
gesteuert werden muss. Dazu dienen sog. Auswahlanweisungen.
In C# gibt es zu diesem Zweck:
• if-Anweisungen (siehe)
• switch-Answeisungen (siehe)
9.3.1 Negation
Der einfachste boolesche Operator ist die Negation, die eine logische Aussage in ihr Gegenteil umkehrt.
Dieser Operator wird in C# mit ! codiert.
z.B.:
!false entspricht true
!true entspricht false
!= entspricht ungleich
Anmerkung:
Operatoren mit nur einem Operanden, wie das !, nennt man unäre Operatoren. Operatoren, die zwei
Operanden besitzen nennt man binäre Operatoren
UND-Operator
Im folgenden Beispiel soll die Belastung für einen Träger von einem Textfeld aus einer Eingabeoberfläche
geliefert werden. Die Last soll nur für Werte zwischen 0 kN und 50 kN gültig sein. Eine logische Variable soll
das Ergebnis dieser Untersuchung speichern:
double last = double.Parse(textBox1.Text);
bool gültigeLast;
gültigeLast = (last >= 0) && (last <= 50);
if (gültigeLast)
{
//Berechne Träger
...
MessageBox.Show("Das Ergebnis ... =" + ...);
}
else
MessageBox.Show("Berechnung nicht möglich, Last ungültig!");
Nur in den Fällen, wo die Variable last einen Wert zwischen 0 und 50 trägt, wird die logische Variable
gültigeLast den Wert true zugewiesen bekommen, in allen anderen Fällen bekommt sie den Wert false
zugewiesen.
Achtung: die folgende Zeile würde einen Compiler-Fehler verursachen:
gültigeLast = (last >= 0 && <= 50); // erzeugt Compiler-Fehler
ODER-Operator
Im folgenden Beispiel soll eine Lasteingabe für einen Zweifeldträger erfolgen, und nur dann gültig sein, wenn
wenigstens eines seiner Felder mit einer Last größer Null belastet ist. Auch das Ergebnis dieser Untersuchung
wird in einer logischen Varaiblen gespeichert:
double lastFeld1 = double.Parse(textBox1.Text);
double lastFeld2 = double.Parse(textBox2.Text);
bool gültigeLast;
gültigeLast = (lastFeld1 > 0) || (lastFeld2 > 0);
if (gültigeLast)
{
//Berechne Zweifeldträger
...
MessageBox.Show("Das Ergebnis ... =" + ...);
}
else
MessageBox.Show("Berechnung nicht möglich, Last ungültig!");
Die Variable gültigeLast bekommt den Wert true bereits zugewiesen, wenn entweder lastFeld1 oder lastFeld2
größer als Null ist. Natürlich ist das Ergebnis auch für den Fall true, wenn beide Felder mit einer positiven
Last belastet werden.
Anmerkung:
Alternativ kann man die logische Untersuchung auch gleich direkt in das if- Statement einbauen. Das
Programm würde dann so aussehen:
double lastFeld1 = double.Parse(textBox1.Text);
double lastFeld2 = double.Parse(textBox2.Text);
if ((lastFeld1 > 0) || (lastFeld2 > 0))
{
//Berechne Zweifeldträger
...
MessageBox.Show("Das Ergebnis ... =" + ...);
}
else
MessageBox.Show("Berechnung nicht möglich, Last ungültig!");
9.3.4 Vorrang
Zum Vorrang und Assoziativität von logischen Operatoren siehe Übersicht.
9.4 Übung 3
9.4.1 Ermittlung der Schnittkräfte für Träger mit Kragarm
Für einen Träger mit Kragarm sollen für die Belastung mit einer Einzellast die Auflagerkräfte A und B, sowie
das Biegemoment im Feld (Mf) und über der Stütze (Mk) berechnet werden.
Erstellen Sie ein Windows-Programm, in dem der Anwender die erforderlichen Eingaben machen kann, und
das die Ergebnisse berechnet und ausgibt.
10 Arrays
10.1 Grundlagen
Arrays sind mehrdimensionale Variablen. Sie werden in C# als Objekte angelegt, d.h. sie liegen im Heap, und
ihre Namen (Bezeichner) stehen stellvertretend für Referenzen auf sie. Werden Arrays als Parameter beim
Aufruf einer Methode übergeben, werden demnach Referenzen übergeben (siehe Call by Reference).
Diese Eigenschaft kann man sich zu Nutze machen, wenn eine Methode mehr als einen Wert zurückgeben soll.
Über die return-Anweisung kann immer nur ein Wert zurückgegeben werden. Will man aber aus einer
Methode mehrere Ergebnisse zurückliefern, kann man ein Array an sie übergeben. Die gerufene Methode
verändert dann die Inhalte der Array-Elemente, die dann danach auch der rufenden Methode zur Verfügung
stehen.
Die Tatsache, dass Arrays keine einfachen Werttypen, sondern komplexe Objekttypen sind, verleiht ihnen die
angenehme Eigenschaft, dass sie über einen ganzen Satz von praktischen Methoden verfügen, die den Zugriff
auf sie, und ihre Verwaltung erleichtern.
Es gibt eindimensionale und mehrdimensionale Arrays. In der Mathematik verwendet man eindimensionale
Arrays für Vektoren und mehrdimensionale für Matrizen.
Achtung ProgrammiererInnen aus andern Sprachen:
Während in der englischen Literatur auch bei anderen Programmiersprachen in diesem Kontext immer von
Arrays gesprochen wird, wurde in der deutschen Literatur bei anderen Sprachen (z.B. Java) für diese
Datenstrukturen der Begriff "Felder" eingeführt. Die deutsche C#-Literatur verwendet den Begriff "Felder"
jedoch für Varaiblen, deren Geltungsbereich sich über eine ganze Klasse erstreckt. Auf Grund dieser
Inkonsistenz können also leicht Irrtümer und Missverständnisse entstehen.
// Array auslesen
for (int i =0; i<meinIntArray.Length; i++)
{
Console.WriteLine(meinIntArray[i].To String());
}
10.3.2 foreach-Anweisung
Da sehr häufig über Schleifen auf Arrays zugegriffen wird, hat man in C# die foreach-Anweisung aus VB
übernommen.
Das Fragment von vorhin sieht damit so aus:
int[] meinIntArray = new int[5];
// Array füllen
for (int i =0; i<meinIntArray.Length; i++)
{
meinIntArray[i] = i;
}
// Array auslesen
foreach (int i in meinIntArray)
{
Console.WriteLine(i.To String());
}
//Array füllen
for (int i = 0; i<zeilen;i++)
{
for (int j = 0; j<spalten;j++)
{
rechteckArray[i,j] = i+j;
}
}
//Array lesen und ausgeben
for (int i = 0; i<zeilen;i++)
{
for (int j = 0; j<spalten;j++)
{
Console.WriteLine("rechteckArray["+i+","+j+"]="+rechteckArray[i,
j]);
}
}
}
}
}
Ausgabe:
Alternativ hätte man das Array nicht über die geschachtelte for-Schleife, sondern auch wie folgt füllen können:
int[,] rechteckArray=
{
{0,1,2},{1,2,3},{2,3,4},{3,4,5}
};
10.5 Übung 5
Erstellen Sie ein Programm, bei dem aus dem Hauptprogramm eine Methode aufgerufen wird, die eine
quadratische Gleichung lösen kann. Die beiden möglichen Lösungen sollen über ein Array übergeben und im
Hauptprogramm ausgedruckt werden.
10.7 Übung 10
Nehmen Sie das Programm aus Übung 8 und entwickeln Sie es weiter, so dass es nicht mehr mit einem
statischen Array arbeitet sondern mit einer dynamischen ArrayList. Dadurch kann das Programm Dateien
beliebiger Länge lesen und sortieren, ohne dass die Länge des Arrays angepaßt werden muss.
11 Sortieren
11.1 Übung 6
Erstellen Sie eine allgemeine Methode, die ein eindimensionales Feld von Integer-Zahlen der Größe nach
sortieren kann. Diese Methode soll beliebig lange Felder empfangen können.
Binden Sie diese Sortiermethode in eine Klasse ein, und rufen Sie sie aus der Hauptmethode auf. In der
Hauptmethode definieren sie das zu sortierende Feld. Zeigen Sie das Feld vor und nach dem Sortieren am
Bildschirm an.
12.2 Streams
Sollen Daten aus einer Datei, durch das Netzwerk oder über das Internet bewegt werden, müssen sie in einen
Stream (Datenstrom) verwandelt werden. Daten fließen in einem Stream als Paket hintereinander, ähnlich wie
Luftblasen in einem Strohhalm.
Im weiteren sollen nur die Streams behandelt werden, die für den Zugriff auf Dateien zur Verfügung stehen.
Darüber hinaus gibt es noch weitere, mit deren Hilfe man Daten über das TCP/IP-Protokoll in Netzwerken und
im Internet bewegen kann.
Der Ausgangspunkt eines Streams wird als Backing-Store(Zusatzspeicher) bezeichnet, der die Quelle für den
Stream darstellt, so wie ein See für einen Bach.
Dateien und Verzeichnisse werden im .NET-Framework, und damit auch in C#, als Klassen dargestellt, die
Methoden zur Verfügung stellen, mit denen man Dateien und Verzeichnisse erzeugen, benennen, manipulieren
und löschen kann.
Es gibt ungepufferte und gepufferte Streams, wodurch die Geschwindigkeit beim Lesen einer Datei günstig
beeinflusst werden kann. Wir betrachten im weiteren nur ungepufferte Streams.
Darüber hinaus gibt es synchrone und asynchrone Streams: asynchrone Streams können Daten Bit für Bit
aus einer Datei von der Festplatte holen, während das Programm etwas anderes tut. Asynchrone Streams
melden, wenn sie fertig sind. Wir betrachten im weiteren nur synchrone Streams, die bewirken, dass das
Programm, während der Stream fließt, wartet, bis die Stream- Behandlung angeschlossen ist.
Objekte, die in C# angelegt und abgespeichert werden sollen, müssen serialisiert, d.h. in Bitfolgen zerlegt
werden, bevor sie über Streams bewegt werden können. Dazu stehen im Namensraum System.IO Klassen zur
Verfügung, die entsprechende Funktionen anbieten.
So stellt die Klasse Directory z.B. folgende Funktionen zur Verfügung: CreateDirectory, Delete, Exist,
GetDirectoryRoot, GetFiles, GetLastAccessTime, ... usw.
Die Klasse File bietet z.B. folgende Funktionen an: AppendText, Copy, Create, CreateText, Delete, Move,
OpenRead, OpenWrite, ... usw. Die letzten beiden Befehle etwa öffnen, bzw. erzeugen einen Stream zum
Lesen, bzw. Lesen und Schreiben einer Datei.
Die Stream-Klassen sind übervoll mit Methoden, die wichtigsten zur Behandlung von Text-Dateien werden
wir jetzt kennenlernen.
Hier ein vollständiges Programm, das eine Textdatei anlegt und beschreibt:
using System;
using System.IO;
namespace TextDateiSchreiben
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
//Erzeugt eine Instanz dieser Klasse
//und startet ihre erste Methode
Class1 t = new Class1();
t.Run();
}
//Text-Datei beschreiben
schreiben.WriteLine("Meine erste Zeile in einer Text- Datei");
schreiben.WriteLine("Meine zweite Zeile in einer Text- Datei");
//Aufräumen
schreiben.Close();
}
}
}
Ausgabe:
Hier ein vollständiges Programm, dass eine Textdatei öffnet und liest:
using System;
using System.IO;
namespace TextDateiLesen
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
//Erzeugt eine Instanz dieser Klasse
//und startet ihre erste Methode
Class1 t = new Class1();
t.Run();
}
//Aufräumen
lesen.Close();
}
}
}
Ausgabe:
12.4 Übung 7
Verändern Sie das Programm aus Übung 6 so, dass es seine Ergebnisse nicht nur am Bildschirm, sondern auch
in eine Text-Datei ausgibt.
12.5 Übung 8
Verändern Sie das Programm aus Übung 7 (bzw. Übung 6) so, dass es die Ausgangsdaten, die sortiert werden
sollen, aus einer Text-Datei liest, die Sie vorher mit einem Text-Editor erstellt haben.
12.6 Übung 9
Erstellen Sie mit einem Text-Editor eine Text-Datei. Erstellen Sie dann ein Programm, das dieses Datei öffnet,
liest und eine Kopie davon erzeugt. Überprüfen Sie mit einem Text-Editor, ob die Kopie Fehler-frei ist.
13 2D-Grafik
13.1 Einführung
Interaktionen zwischen Programm und Benutzer werden über sogenannte Ereignisse gesteuert. Aber auch das
Zeichnen von Grafik wird über ein Ereignis verwaltet. Der Zugriff auf Ereignisse erfolgt über sogenannte
Handler (oder auf Englisch handles).
Das Paint-Ereignis ist für die Steuerung von Grafik vorgesehen. Der Zugriff auf den Handler eines Paint-
Ereignisses erfolgt über folgende Methode:
protected override void OnPaint(PaintEventArgs pea)
{
//Befehle der Zeichenroutine
}
Damit der Compiler damit etwas anfangen kann, muss folgendes using-Kommando vorhanden sein, das der
Forms- Editor ohnehin schon automatisch anlegt:
using System.Drawing;
Der 0-Punkt der Koordinatensystems ist immer der linke obere Eckpunkt des Ausgabefensters, wobei die
Positive x- Achse nach rechts und die positive y-Achse nach unten weist.
13.2 Pen
Gezeichnet wird mit einem Stift, der verschiedene Eigenschaften wie Farbe und Dicke (gemessen in Pixel)
haben kann. Bevor ein Pen (Stift) verwendet werden kann, muss man ihn einrichten. Pen ist eine Klasse, von
der man eine Instanz bilden muss.
z.B.:
protected override void OnPaint(PaintEventArgs pea)
{
Pen stift = new Pen(Color.Red);
...
stift.Color = Color.Green;
stift.Width = 3;
...
}
13.6 Beispiel-Programm
Das folgende Programm ist eine Sammlung der oben besprochenen Grafik-Befehle, und zeigt wie sie
aufgerufen werden können:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace Zeichnen
{
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Erforderlich für die Windows Form-Designerunterstützung
//
InitializeComponent();
//
// TODO: Fügen Sie den Konstruktorcode nach dem Aufruf von
InitializeComponent hinzu
//
}
/// <summary>
/// Die verwendeten Ressourcen bereinigen.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
}
#endregion
/// <summary>
/// Der Haupteinstiegspunkt für die Anwendung.
/// </summary>
[STAThread]
grfx.Clear(Color.BlanchedAlmond);
grfx.DrawString("Test1", Font, Brushes.DarkRed, 10,10);
grfx.DrawLine(pen, 0,0, ClientSize.Width -1, ClientSize.Height - 1);
Rectangle rect = new Rectangle(60, 60, 200, 200);
grfx.DrawArc(pen, rect, 15, -180);
//Mögliche Anti-Aliasingwerte:
//HighQuality und AntiAlias für on
//u.A. None, Standard, HighSpeed für off
//using System.Drawinf.Drawing2D; muss definiert sein!
grfx.SmoothingMode = SmoothingMode.HighQuality;
pen.Color = Color.Blue;
grfx.DrawString("Test2", Font, Brushes.DarkOrchid, 10,100);
grfx.DrawLine(pen, 0,ClientSize.Height - 1, ClientSize.Width -1, 0);
pen.Width = 4;
grfx.DrawRectangle(pen, 20, 20, 50, 50);
grfx.DrawArc(pen, rect, 15, 180);
pen.Color = Color.Green;
pen.Width = 0;
float d;
float dVorher = 0.0F;
float yVorher = 0.0F;
for (int i=0; i<=100; i++)
{
d=i/10;
grfx.DrawLine(pen, dVorher*10, yVorher*2, d*10, d*d*2);
dVorher = d;
yVorher = d*d;
}
}
}
13.7 Übung 11
Erstellen Sie ein Programm, das den Funktionsgrafen für die Funktion
f(x) = SQRT(x+a) / (x+b)
darstellt.
Die Eingangsgrößen a und b, sowie der Wertebereich von x und die Teilung der Berechnungsschritte sollen
aus einer Datei eingelesen werden, die vorher mit Hilfe eines Text-Editors beschrieben wurde.
Die Koordinatenachsen sollen schwarz, die Polstelle durch eine vertikale grüne Linie und der Graph in rot
dargestellt werden.
14 Verschiedene Themen
14.1 Escape-Sequenzen
Escape-Sequenzen werden für die Ausgabe von Texten benötigt. Ihre Bezeichnungen stammen noch aus der
Zeit, als Ausgaben vorwiegend auf Druckern erfolgten. Eine Escape-Sequenz entspricht dem Typ char und
wird intern in Unicode Zeichen dargestellt. In einem String werden Escape-Sequenzen mit einfachen
Anführungszeichen eingerahmt.
Gebräuchliche Escape-Sequenzen sind:
Zeichen Bedeutung
\' Einfaches Anführungszeichen
\" Doppeltes Anführungszeichen
\\ Backslash
\0 Null
\a Alert
\b Rücktaste
\f Seitenvorschub
\n Newline
\r Carriage Return (= neue Zeile)
\t Horizontaler Tabulator
\v Vertikaler Tabulator
In der Textausgabe werden diese Escape-Sequenzen heute sinngemäß verwendet, auch wenn die Ausgabe
nicht mehr unbedingt auf einem Drucker, sondern etwa auf dem Bildschirm, in einen String oder in eine Datei
erfolgt. So versteht man heute unter "Carriage Return" (wörtlich übersetzt: "Druckwagen zurück") eine "neue
Zeile".
z.B.:
System.Console.WriteLine("Spalte A" + '\t' + "Spalte B");
14.2 Mathematik-Klasse
Math ist eine sog. versiegelte Klasse, die Naturkonstanten und Methoden für viele trigonometrische,
logarithmische und andere mathematische Operationen enthält. Der Aufruf einer Methode erfolgt über die
Klasse Math.
z.B. Wurzel-Funktion:
double a = 4.0;
double z = Math.Sqrt(a);
Jede Methode der Mathematik-Klasse liefert einen Wert von einem bestimmten Typ zurück, häufig double.
Wichtig ist auch, dass man darauf achtet, den richtigen Typ als Parameter zu übergeben. Einige Methoden
liegen mehrfach, d.h. mit überladenem Bezeichnern vor, so dass man mit dem selben Bezeichner
unterschiedliche Typen bearbeiten kann.
Math.E liefert 2.7182818..... als Typ double
Math.PI liefert 3.141592653.... als Typ double
*= Verbundzuweisungs-
/= Operatoren
%=
+=
-=
16 Debugger
Das Wort "bug" ist die englische Bezeichnung von Käfer oder Ungeziefer. Wenn Programmierer in ihren
Programmen Fehler suchen, sagen sie, dass sie ihr Programm "debuggen", man könnte übersetzten "entlausen".
Der Compiler deckt syntaktische Fehler auf, nicht jedoch logische Ablauffehler. Die bemerkt man erst, wenn das
Programm nicht so funktioniert, wie man sich das eigentlich vorstellt. Mitunter kann die Suche nach den
Gründen logischer Programmfehler oder gar Programmabstürzen sehr aufwändig sein.
Moderne Programmierumgebungen liefern deshalb Werkzeuge zur Fehlersuche mit, sog. Debugger. Damit kann
man:
• Das Programm Zeile für Zeile, also Schritt für Schritt ablaufen lassen.
• Nach der Ausführung jeder einzelnen Zeile kann man sich den Inhalt der Variablen ansehen und überprüfen,
ob der Wert richtig ist.
• Außerdem kann man durch die schrittweise Ausführung entdecken, ab wann und unter welchen
Bedingungen ein Programm falsch läuft.
Vorgehen:
• Verkleinern Sie das Fenster mit der Entwicklungsumgebung, so dass Sie einen Teil des Desktops sehen
• Klicken Sie den Textcursor an die Stelle des Programmcodes, ab der Sie den Programmablauf beobachten
wollen
• Öffnen Sie an dieser Stelle mit der rechten Maustaste das Kontext-Menü
• Wählen Sie: Ausführen bis Cursor
• Ihr Programm wird geöffnet und Ihr Form oder das Konsole Fenster wird geöffnet.
• Legen Sie dieses Fenster neben das Fenster Ihrer Entwicklungsumgebung
• Machen Sie in Ihrem Programm die erforderlichen Eingaben, damit es startet
• Das Programm stoppt an der oben festgelegten Stelle
• Eine zusätzliche Menüleiste wurde eingeblendet, über die der Debugger gesteuert werden kann (z.B.
Einzelschritt, Prozedur-Schritt, Haltepunkte setzten usw.)
• Im unteren Fenster kann man beobachten, welche Werte den Variablen zugewiesen wurde.
• Gehen Sie nun Schritt für Schritt durch Ihr Programm und beobachten Sie den Ablauf, sowie den Inhalt der
Varaiblen.
18 Musterlösungen
18.1 Übung 4
Das Ergebnis mit dem Form-Editor könnte so aussehen:
namespace Schleifen
{
/// <summary>
/// Zusammendfassende Beschreibung für Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
public Form1()
{
//
// Erforderlich für die Windows Form- Designerunterstützung
//
InitializeComponent();
//
// TODO: Fügen Sie den Konstruktorcode nach dem Aufruf von
InitializeComponent hinzu
//
}
/// <summary>
/// Die verwendeten Ressourcen bereinigen.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
this.button1.TabIndex = 2;
this.button1.Text = "Berechnen";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Ergebnisse
//
this.Ergebnisse.Location = new System.Drawing.Point(216, 32);
this.Ergebnisse.Name = "Ergebnisse";
this.Ergebnisse.Size = new System.Drawing.Size(232, 212);
this.Ergebnisse.TabIndex = 4;
//
// label1
//
this.label1.Location = new System.Drawing.Point(8, 176);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(88, 16);
this.label1.TabIndex = 5;
this.label1.Text = "Einzellast in kN";
//
// label2
//
this.label2.Location = new System.Drawing.Point(112, 176);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(96, 16);
this.label2.TabIndex = 5;
this.label2.Text = "Trägerlänge in m";
//
// label3
//
this.label3.Font = new System.Drawing.Font("Microsoft Sans Serif",
12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
((System.Byte)(0)));
this.label3.Location = new System.Drawing.Point(8, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(440, 32);
this.label3.TabIndex = 6;
this.label3.Text = "Auflagerkräfte für wandernde Einzellast auf
Einfeldträger";
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// pictureBox1
//
this.pictureBox1.Image =
((System.Drawing.Bitmap)(resources.GetObject("pictureBox1.Image")));
this.pictureBox1.Location = new System.Drawing.Point(8, 32);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(192, 136);
this.pictureBox1.TabIndex = 7;
this.pictureBox1.TabStop = false;
//
// button2
//
this.button2.Location = new System.Drawing.Point(160, 224);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(40, 24);
this.button2.TabIndex = 8;
this.button2.Text = "Ende";
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(464, 259);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.button2,
this.pictureBox1,
this.label3,
this.label1,
this.Ergebnisse,
this.button1,
this.textBox1,
this.textBox2,
this.label2});
this.Name = "Form1";
this.Text = "Beispiel für \"for- Schleife\"";
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// Der Haupteinstiegspunkt für die Anwendung.
/// </summary>
[STAThread]
C#-Script © Prof. Rasso Steinmann
Version 3.0 Hochschule München
91
// +++++++++++++++++++++++++++++
// Ab hier beginnt der Quellcode
// der eigenen Applikation
// +++++++++++++++++++++++++++++
private void button1_Click(object sender, System.EventArgs e)
{
// Dieser Code wurde absichtlich ausfühlich geschrieben,
// um die einzelnen Komponenten zu veranschaulichen.
// Man könnten dieses Progamm wesentlich kompakter Schreiben,
// indem man die Formeln ineinander einsetzt, allerdings wird
// der Programmcode dann auch unleserlich.
double fP = double.Parse(textBox1.Text);
double l = double.Parse(textBox2.Text);
double a = 0.0;
double b = 0.0;
double fA = 0.0;
double fB = 0.0;
// ListBox freimachen, wichtig für wiederholte Berechnungen
Ergebnisse.Items.Clear();
// Kopfzeile in ListBox
// \t ist die Escape-Sequenz für einen Tabulator
Ergebnisse.Items.Add("Abstand:" + '\t'+ " Auflagerkräfte:");
fA = fP * b/l;
fB = fP * a/l;
18.2 Übung 5
Dieses Programm wurde als Console-Anwendung programmiert:
using System;
namespace QuadratGleichg
{
/// <summary>
/// Zusammendfassende Beschreibung für Class1.
/// </summary>
class Class1
{
/// <summary>
/// Der Haupteinstiegspunkt für die Anwendung.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// Dieser Konstruktor ist erforderlich, damit man Methoden
dieser
// Klasse aufrufen kann. Mit new wird eine Instanz q der
Class1
// eingerichtet.
Class1 q = new Class1();
// Durch die Zuordnung zur Instanz c und damit zu einem
Objekt
// kann weiter unten die Methode loesQuadratGleichg
aufgerufen werden
double a = 2.0;
double b = 23.0;
double c = 7.0;
if (q.loeseQuadratGleichg(a, b, c, loesungsArray))
{
Console.WriteLine("Reelle Lösung:");
Console.WriteLine("x1= " + loesungsArray[0]);
Console.WriteLine("x2= " + loesungsArray[1]);
C#-Script © Prof. Rasso Steinmann
Version 3.0 Hochschule München
94
}
else
Console.WriteLine("Komlexe Lösung");
}
18.3 Übung 6
Das Hauptprogramm "Main" und die Methode "zeigeWerte" wurden hier nicht als ein Windows- Programm,
sondern als Console-Programm gestaltet, also ein Programm, das im System- Fenster läuft. Die Methode
"bubble", die das Sortieren des Feldes übernimmt, ist hingegen so neutral gestaltet, dass man sie, wie hier, in
einem Console-Programm verwenden kann, oder aber auch in einem Windows-Programm. Dies wurde
erreicht, indem die Methode "bubble" von allen Umgebungs- spezifischen Befehlen, wie Ein-/Ausgabe
freigehalten wurde.
using System;
namespace BubbleSort
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Class1 c = new Class1();
int[] explizitesArray = new int[5] {7,13,14,8,6};
c.zeigeWerte(explizitesArray);
c.bubble(explizitesArray);
c.zeigeWerte(explizitesArray);
}
}
}
}
}
}
}
Ausgabe:
18.4 Übung 7
Das Programm aus der Übung 6 wurde durch die Methode "schreibeWerte" erweitert, in der die sortierten
Zahlenwerte in die Datei "SortOut.txt" geschrieben werden:
using System;
using System.IO;
namespace BubbleErgebnisInFile
{
class Class1
{
[STAThread]
staticvoid Main(string[] args)
{
Class1 c = newClass1();
int[] explizitesArray = new int[5] {7,13,14,8,6};
c.zeigeWerte(explizitesArray);
c.bubble(explizitesArray);
c.zeigeWerte(explizitesArray);
c.schreibeWerte(explizitesArray);
}
public void zeigeWerte(params int[] intWerte)
{
foreach(int i in intWerte)
{
Console.WriteLine("Wert =" + i);
}
Console.WriteLine("---------");
}
private void schreibeWerte(params int[] intWerte)
{
// Es wird die Instanz schreiben des Datenstroms
StreamWriter angelegt,
// der zum Beschreiben von Text-Dateien dient.
StreamWriter schreiben = new
StreamWriter(@"C:\CsharpTest\SortOut.txt",false);
//Text-Datei beschreiben
foreach(int i in intWerte)
{
schreiben.WriteLine(i);
}
// Nachricht für den Anwender,
18.5 Übung 9
Kopieren einer Text-Datei:
using System;
using System.IO;
namespace TextDateiKopieren
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Class1 c = new Class1();
// Jetzt steht der Stream zur Verfügung, der "lesen" genannt wird
StreamReader lesen = dieQuellDatei.OpenText();
string text;
do
{
//Quell-Datei lesen
text = lesen.ReadLine();
//Ziel-Datei schreiben
schreiben.WriteLine(text);
}while (text != null);
//Aufräumen
lesen.Close();
schreiben.Close();
}
}
}
Ausgangsdatei:
Programmausgabe:
Ergebnisdatei:
18.6 Übung8
Zunächst wird hier eine Lösung mit dem Array vorgeschlagen, wie es in der Vorlesung behandelt wurde. Das
Problem hierbei ist, dass die Größe eines Arrays bereits vor dem Kompilieren festgelegt werden muss. In
dieser Übung sollen die Eingangswerte jedoch aus einer Datei eingelesen werden. Zum Zeitpunkt des
Programmierens wissen wir jedoch noch nicht, wieviele Zahlen die Datei mit den Eingangswerten enthalten
wird. Wir können unser Array also entweder nur zu groß oder zu klein dimensionieren. Eine
Sicherheitsabfrage verhindert, dass über die Array-Grenzen geschrieben wird, falls die Datei mit den
Eingangswerten sehr groß ist.
Alternativ wird deshalb noch eine zweite Lösung gezeigt, die dieses Problem umgeht. Es wird hier ein anderer
Typ Array verwendet, ein sog. ArrayList, der nicht in der Vorlesung behandelt wurde.
namespace BubbleLeseUndSchreibe
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Class1 c = new Class1();
int[] explizitesArray = new int[7];
c.leseWerte(explizitesArray);
c.zeigeWerte(explizitesArray);
c.bubble(explizitesArray);
c.zeigeWerte(explizitesArray);
c.schreibeWerte(explizitesArray);
}
// Jetzt steht der Stream zur Verfügung, der "lesen" genannt wird
StreamReader lesen = dieQuellDatei.OpenText();
//Text-Datei lesen:
//Die Schleife läuft so lange, bis entweder das Ende der Text- Datei
//erreicht wurde ("text" hätte dann den Wert "null" oder bis die
//Aufräumen
lesen.Close();
}
//Text-Datei beschreiben
foreach (int i in intWerte)
{
schreiben.WriteLine(i);
}
//Aufräumen
schreiben.Close();
}
Ergebnisdatei:
namespace BubbleSortArrayListLeseSchreibe
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Class1 c = new Class1();
c.leseWerte(dynamischesArray);
c.zeigeWerte(dynamischesArray);
c.bubble(dynamischesArray);
c.zeigeWerte(dynamischesArray);
c.schreibeWerte(dynamischesArray);
}
// Jetzt steht der Stream zur Verfügung, der hier "lesen" genannt
wird
StreamReader lesen = dieQuellDatei.OpenText();
//Text-Datei lesen:
//Die Schleife läuft so lange, bis das Ende der Text-Datei
//erreicht wurde ("text" hätte dann den Wert "null"). Über die
//Add-Methode kann das ArrayList befüllt werden. Bei einem ArrayList
//braucht man sich um die oberste Array-Grenze beim Beschreiben
//keine Sorgen zu machen. Diese wird vom ArrayList-Objekt selbst
//verwaltet.
string text;
do
{
text = lesen.ReadLine();
//Einem ArrayList darf kein Wert "null" zugewiesen werden.
//Deshalb muss die Zuweisung in diesen Fällen ausgefiltert
//werden. Zumindest einmal, am Ende der zu lesenden Datei,
//kommt der Wert "null".
if (text != null)
intWerte.Add(int.Parse(text));
}while (text != null);
//Aufräumen
lesen.Close();
}
//Text-Datei beschreiben
foreach (int i in intWerte)
{
C#-Script © Prof. Rasso Steinmann
Version 3.0 Hochschule München
108
schreiben.WriteLine(i);
}
//Aufräumen
schreiben.Close();
}
Ausgangsdatei:
Programmausgabe:
Ergbenisdatei:
19 Studienarbeit
Abgabe im ersten Semester.
Prädikat "mit Erfolg teilgenommen" ist Prüfungszulassung.
19.1 1. Aufgabe
Schreiben Sie ein C#-Programm, in dem die Auflagerkräfte und das maximale Biegemoment für einen
Einfeldträger unter einer Linienlast errechnet und ausgegeben werden.
Der Anwender soll die Wahl zwischen folgenden Lastfällen haben:
• Gleichmäßig verteilte Last (Rechteck)
• Dreieckslast mit Maximum am rechten Auflager
• Trapezförmige Last
• Parabelförmig verteilte Last mit Maximum in Trägermitte
Das Programm soll die Ergebnisse mit einer Last-Toleranz von +/-20% der angegebenen Lasten ermitteln und
die Ergebnisse in 1/5-tel Schritten zwischen minimaler und maximaler Last in einer Übersicht ausgeben.
19.2 2. Aufgabe
Erweitern Sie das Programm aus Aufgabe 1 wie folgt:
• Der Anwender soll die Wahl haben, seine Eingabewerte in einer Datei abspeichern zu können, und dabei
den Dateinamen bestimmen können
• Bei Neustart des Programmes soll der Anwender die Wahl haben, entweder bereits frühere Dateien mit
Eingabewerten verwenden, oder aber neue Eingabewerte eingeben zu können.
• Die Ergebnisse sollen ebenfalls in einer Datei abgespeichert werden, wobei der Anwender auch den
Namen dieser Datei bestimmen können soll.
19.3 3. Aufgabe
Entwickeln Sie ein Programm, das die Momentenlinie für den Träger aus Aufgabe 1 unter dem Lastfall
"Trapezförmige" Last in 1/10-tel Schritten berechnet und darstellt . Das Programm soll dabei auf eine
Eingabe-Datei zugreifen, die der Anwender mit dem Programm aus Aufgabe 2 abgespeichert hat.