Anatomie eines C#

Werbung
Kapitel 5
Anatomie eines C#-Programms
In diesem Kapitel:
Programmaufbau
using-Direktive und Framework-Klassen
Dateien und Assemblies
Imperative Programmierung in C#
Objektorientierte Programmierung in C#
94
98
102
104
109
93
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
94
Kapitel 5: Anatomie eines C#-Programms
Als Einleitung und Vorbereitung zu den nachfolgenden Kapiteln, die sich eingehender mit den einzelnen
Elementen und Konzepten der Programmiersprache C# beschäftigen, gibt dieses Kapitel einen Überblick
über die Organisation von C#-Code, die typischen Elemente einer C#-Anwendung, die Verteilung des
Codes auf mehrere Dateien und Assemblies und die Idee der objektorientierten Programmierung.
Programmaufbau
Anders als die Hybrid-Sprache C++, die gleichermaßen der strukturierten wie der objektorientierten
Programmierung verbunden ist, gehört C# zu den rein objektorientierten Sprachen. Zwar kann sich der
Programmierer auch in C#, so er will, den Idealen und Ideen der objektorientierten Programmierung
weitgehend verschließen, indem er, statt Klassen für eigene Objekte zu definieren, allein mit statischen
Methoden arbeitet, doch ganz entziehen kann er sich dem Diktat der Objekte und Klassen nicht, denn
abgesehen davon, dass die bei der täglichen Programmierarbeit dringend benötigte Standardfunktionalität
des .NET Framework in Form von Klassen bereitgestellt wird, verlangt C# vom Programmierer, dass dieser
auch seinen eigenen Code in Klassen verpackt.
1
Eine C#-Anwendung ist letztes Endes also nichts anderes als eine Sammlung von Klassendefinitionen .
Eine dieser Klassen muss einen Eintrittspunkt definieren: die Main()-Methode, mit der die Programmausführung beginnt. Selbst einfachste C#-Anwendungen bestehen also zumindest aus einer Klasse mit einer
Main()-Methode. Nicht zwingend erforderlich, aber üblich sind using-Direktiven und Kommentare. Die
nachfolgenden Abschnitte stellen die wichtigsten Programmelemente kurz vor.
/*
4
* Demo-Programm
*
* Dateiname: HelloWorld.cs
* Copyright: dieFirma.com
*/
3
1
Klasse
2
Eintrittspunkt
3
using-Direktive
4
Kommentar
using System;
class Program 1
{
static void Main(string[] args) 2
{
// Anwender begrüßen 4
Console.WriteLine("Hallo Welt!");
}
}
Abbildung 5.1 Elemente eines typischen C#Programms
Der Programmaufbau als Spiegel der eigenen Genealogie
So wie die Entwicklung eines menschlichen Fötus die Entwicklung des Menschen widerspiegelt, so lässt
sich am Layout eines C#-Programms die Evolution der imperativen Programmiersprache nachvollziehen.
Am Beginn dieser Evolution standen einfache imperative Sprache wie Fortran (1954) oder Basic (1964),
deren Programme aus einer Abfolge von simplen Anweisungen aufgebaut waren.
1
Eigentlich Typdefinitionen, denn neben Klassen können auch Strukturen, Schnittstellen und Enumerationen definiert werden.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
95
Programmaufbau
Mit zunehmender Komplexität der Programme wuchs auch die Notwendigkeit, den Code zu modularisieren: Anweisungen, die häufig benötigte Teilaufgaben lösten (etwa die Ausgabe auf Konsole), wurden
zu Funktionen zusammengefasst. Der in einer Funktion enthaltene Code konnte praktisch jederzeit und
an beliebiger Stelle durch Aufruf der Funktion ausgeführt werden. Über Parameter konnte die Funktion
beim Aufruf Daten vom Aufrufer entgegen nehmen, über ihren Rückgabewert konnte sie Ergebnisse
zurückliefern. Imperative Sprachen, die das Funktionenkonzept unterstützen, werden als strukturierte
Programmiersprachen bezeichnet. Ihr erfolgreichster Vertreter war die Sprache C, deren Programme alle
mit einer Funktion main() begannen.
In der strukturierten Programmierung sind die Daten und die sie verarbeitenden Funktionen voneinander getrennt. Diese Trennung aufzuheben und den Programmierer mit komplexen Objekten arbeiten zu
lassen, die in sich Daten und Funktionen vereinen, war das Verdienst der objektorientierten Programmierung. Die erste objektorientierte Sprache war Simula (1967). Inspiriert von Simula erweiterte der
Däne Bjarne Stroustrup Mitte der achtziger Jahre die Sprache C um objektorientierte Konzepte zur Sprache C++. Wegen der Abwärtskompatibilität zu C begannen C++-Programme immer noch mit der main()Funktion und der Programmierer konnte wahlweise mit Klassen und Objekten oder mit globalen Variablen und allein stehenden Funktionen arbeiten – oder beide Paradigmen vermischen. In C# wurde dieses
Zwitterstadium beendet, globale Daten und allein stehende Funktionen wurden aufgegeben und aus der
main()-Funktion wurde die in einer Klasse definierte statische Main()-Methode. Zudem übernahm C# –
wie Java – von Smalltalk die Idee des virtuellen Zwischencodes und der automatischen Speicherbereinigung (Garbage Collection).
using System;
class Program
{
static void Main(string[] args)
{
int[] values = { 1, 5, -89 };
int sum;
objektorientiert
strukturiert
imperativ
sum = 0;
for (int i = 0; i < values.Length; i++)
sum = sum + values[i];
Console.WriteLine(sum);
}
}
Abbildung 5.2 C#-Programmierung geschieht auf drei Ebenen, die den Paradigmen der imperativen, strukturierten und objektorientierten
Programmierung zugeordnet werden können
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
96
Kapitel 5: Anatomie eines C#-Programms
Klassen
In C# ist das Konzept der Klasse praktisch allgegenwärtig: sei es als Element des Programmlayouts (siehe
oben), als benutzerdefinierter Datentyp (siehe Kapitel 6 und 10) oder als Herzstück der objektorientierten
Programmierung in C# (siehe den gleichnamigen Abschnitt weiter hinten in diesem Kapitel). Hier sei nur
kurz angemerkt, dass Klassen Typen darstellen, die aus verschiedenen Elementen (Member) zusammen
gesetzt sind. Die wichtigsten Member sind Felder (klasseninterne Variablen) und Methoden (klasseninterne Funktionen). Nach Verwendung und Bedeutung für die Klasse unterscheidet man statische und nichtstatische Member.
Statische Member, die mit dem Schlüsselwort static definiert sind, können direkt über den Namen der
Klasse aufgerufen werden: Klassenname.Member.
Nicht-statische Member können nur über Objekte angesprochen werden. Objekte sind Instanzen einer
Klasse, die mit Hilfe des Schlüsselworts new erzeugt werden. Bei der Instanzbildung erhält das neue Objekt
eine Kopie von jedem (nicht-statischen) Feld der Klasse. Werden über ein Objekt (nicht-statische) Methoden der Klasse aufgerufen, operieren diese auf den Daten des Objekts. Nicht-statische Member werden
auch als Instanzmember bezeichnet.
Eintrittspunkt Main()
Die Ausführung einer C#-Anwendung beginnt mit der Main()-Methode, die in C# eine der folgenden
Signaturen haben muss:
static
static
static
static
void Main()
int Main()
void Main(string[] args)
int Main(string[] args)
ACHTUNG
Für Java-Programmierer: Methoden werden in C# üblicherweise groß geschrieben. Im Falle von Main() ist die
Großschreibung sogar obligatorisch, ansonsten wird die Methode nicht als Eintrittspunkt in die Anwendung erkannt.
Rückgabewert und Batch-Dateien
Wenn Main() mit dem Rückgabewert int deklariert wird, muss sie mit einer return-Anweisung abschließen,
die eine Ganzzahl zurückliefert.
static int Main()
{
// ...
return 0;
}
Dies ist immer dann interessant, wenn die Anwendung je nach Verlauf der Sitzung unterschiedliche
Rückgabewerte liefert, die dann von DOS-Batch-Dateien, die die Anwendung aufrufen, ausgewertet werden können.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Programmaufbau
97
Parameter und Befehlszeilenargumente
Ruft der Anwender ein Programm über die Konsole (Eingabeaufforderung) auf, kann er dem Programm
direkt beim Aufruf Daten übergeben. Die Daten werden dazu einfach als Argumente hinter dem Programmnamen aufgelistet.
Prompt:> Programmname 1 2
HINWEIS
Befehlszeilenargumente können auch via Batch-Dateien oder Meta-Programme, die die Anwendungen aufrufen,
übergeben werden. Die Visual Studio-IDE beispielsweise bietet auf der Seite Debuggen der Projekteigenschaften ein Eingabefeld Befehlszeilenargumente an, über das Sie Argumente an zu debuggende Anwendungen übergeben können.
Um die Befehlszeilenargumente in der Anwendung in Empfang zu nehmen, müssen Sie Main() mit einem
string[]-Parameter definieren. Die weitere Verarbeitung hängt von der Anwendung und der Bedeutung
der Argumente für die Anwendung ab. Die meisten Anwendungen übernehmen über die Befehlszeile zu
verarbeitende Daten (beispielsweise Dateinamen) oder Schalter zur Konfiguration des Programms. Gibt es
obligatorische Befehlszeilenargumente prüft die Anwendung in der Regel als Erstes, ob die korrekte (Mindest-)Zahl Argumente übergeben wurden. Falls nicht, werden eine Fehlermeldung und ein Hinweis auf den
korrekten Aufruf ausgegeben.
Die folgende Demo-Anwendung prüft, ob Befehlszeilenargumente übergeben wurden. Wenn ja, werden
die Argumente ausgegeben.
using System;
class Befehlszeile
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("\n keine Befehlszeilenargumente! \n");
}
else
{
Console.WriteLine("\n Befehlszeilenargumente: \n");
for (int i = 0; i < args.Length; i++)
Console.WriteLine("\t {0}. Argument: {1} \n", i, args[i]);
}
}
}
Listing 5.1 Befehlszeile.cs
C:\>Befehlszeile 1 2 drei vier
Befehlszeilenargumente:
0. Argument: 1
1. Argument: 2
2. Argument: drei
3. Argument: vier
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
98
Kapitel 5: Anatomie eines C#-Programms
TIPP
Die Argumente der Befehlszeile werden durch Leerzeichen voneinander getrennt. Wenn Sie ein Argument übergeben wollen, das selbst Leerzeichen enthält, müssen Sie das Argument in Anführungszeichen setzen, beispielsweise
C:\>Programmname "ein Argument"
using-Direktive und Framework-Klassen
Ohne die Funktionalität der .NET Framework-Klassen ist eine Programmierung mit C# kaum denkbar, ja
schlichtweg unmöglich. Ob Sie Daten auf die Konsole ausgeben, die Uhrzeit abfragen, grafische Benutzeroberflächen erstellen oder mehrere Threads parallel ablaufen lassen möchten… für nahezu alle wichtigen
Programmieraufgaben finden Sie im .NET Framework vordefinierte Klassen, auf deren Funktionalität Sie
zurückgreifen können.
Die Klassen der .NET Framework-Bibliothek sind auf mehrere DLL-Assemblies verteilt (System.dll, System.Data.dll, System.Windows.Forms.dll, System.XML.dll …). Um Klassen aus diesen Assemblies verwenden zu können, müssen Sie Ihren C#-Code zusammen mit Verweisen auf die Assemblies kompilieren. In
Visual Studio richten Sie Verweise über die Kontextmenübefehle des gleichnamigen Projektunterknotens
ein. Wenn Sie direkt mit dem csc-Compiler arbeiten, benutzen Sie die Compiler-Option /r, um auf die
betreffenden DLLs zu verweisen. Die DLL System.dll müssen Sie nicht explizit auflisten.
In Ihrem Quelltext können Sie die Klassen des .NET Framework direkt benutzen, müssen aber beachten,
dass die Klassen in eine hierarchische Struktur von Namespaces organisiert sind. Sie müssen daher entweder:
dem Klassennamen den vollständigen Namespace-Pfad voranstellen
class Program
{
static void Main(string[] args)
{
System.Console.WriteLine("Hallo Welt!");
}
}
oder am Anfang des Quelltextes eine using-Direktive einfügen, die alle Namen aus dem gewünschten
Namespace verfügbar macht
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hallo Welt!");
}
}
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
99
using-Direktive und Framework-Klassen
ACHTUNG
Für C++- und Java-Programmierer: C# verfügt über keine eigene Standardbibliothek. Stattdessen bilden die
Klassen des .NET Framework die C#-Laufzeitbibliothek. Anders als bei Java wird der System-Namespace (vergleichbar
java.lang) nicht automatisch eingebunden.
Ein- und Ausgabe
In Windows Forms-Anwendungen verlaufen Kommunikation und Datenaustausch mit dem Anwender
nahezu ausschließlich über die Steuerelemente der grafischen Benutzeroberfläche. Konsolenanwendungen
besitzen hingegen keine eigene Benutzeroberfläche, sie nutzen für den Austausch mit dem Anwender die
Konsole.
Ausgabe
Für die Ausgabe auf die Konsole stellt die .NET Framework-Klasse System.Console die statische Methode
WriteLine() zur Verfügung, der Sie einfach den auszugebenden String übergeben:
Console.WriteLine("Hallo Welt!");
Mit der gleichen Methode können Sie auch Werte der vordefinierten primitiven Typen (int, double etc.)
oder Objekte ausgeben. Die Werte werden dabei automatisch in ihre String-Darstellung umgewandelt:
double number = 0.234;
Console.WriteLine(number);
Strings und Variablenwerte können bei der Ausgabe mit dem +-Operator kombiniert werden:
double zahl = 0.234;
Console.WriteLine("Wert von number: " + number);
Oder Sie fügen in den auszugebenden String durchnummerierte Platzhalter der Form {0}, {1} und so weiter
ein, die WriteLine() bei der Ausgabe durch die Werte der nachfolgenden Argumente ersetzt:
Console.WriteLine("Das Quadrat von {0} ist {1}", number, number*number);
Platzhalter bieten zudem die Möglichkeit, das Ausgabeformat von numerischen Werten zu beeinflussen.
Mehr zu diesem Thema in Kapitel 22, Abschnitt »Formatierung mit Platzhaltern«.
Console.WriteLine("Wert von n: {0:D}", n);
Console.WriteLine("Wert von n: {0:D2}", n);
Console.WriteLine("Wert von n: {0:F2}", n);
Console.WriteLine("Preis: {0:C2}", n);
//
//
//
//
//
//
//
Ausgabe als ganze Zahl
Ausgabe als ganze Zahl (mindestens
2 Zeichen, notfalls Leerzeichen)
Ausgabe als Dezimalzahl mit genau
2 Nachkommastellen
Ausgabe als Währungsangabe mit genau
2 Nachkommastellen
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
100
Kapitel 5: Anatomie eines C#-Programms
Wenn Sie die Ausgabe nicht mit einem Zeilenumbruch beenden wollen, verwenden Sie statt WriteLine() die
Methode Write():
Console.Write(" Geben Sie eine Zahl ein: ");
Eingabe
Zum Einlesen von Benutzerdaten verwenden Sie die statische Console-Methode ReadLine():
1. Vor dem Einlesen der Benutzerdaten sollten Sie einen Text ausgeben, der dem Benutzer mitteilt, welche
Art von Daten er jetzt eingeben soll.
2. Lesen Sie die Daten dann durch einen Aufruf von Console.ReadLine() ein und speichern Sie die Daten in
einer Variablen.
3. Benutzereingaben werden immer als Strings eingelesen. Enthalten diese Strings Zahlendarstellungen,
können Sie diese gleich mit Hilfe einer der Convert-Methoden ToInt32(), ToLong(), ToDecimal(), ToDouble()
etc. umwandeln und in numerischen Variablen abspeichern.
string name = "";
int
age = 0;
Console.Write(" Geben Sie Ihren Namen ein: ");
name = Console.ReadLine();
// 1
// 2
Console.Write(" Geben Sie Ihr Alter ein: ");
string input = Console.ReadLine();
age = Convert.ToInt32(input);
// 1
// 2
// 3
Console.WriteLine("");
Console.WriteLine(" {0} ist {1} Jahre alt", name, age);
Schritt 2 und 3 können auch in einem Schritt ausgeführt werden:
age = Convert.ToInt32(Console.ReadLine());
Wenn Sie eine Stringeingabe in eine Zahl zu verwandeln suchen, die keine korrekte Zahlendarstellung
enthält, wird eine FormatException-Ausnahme ausgelöst, die – wenn nicht behandelt – zum Programmabsturz führt. Benutzerfreundlicher ist es, die Ausnahme im Programm abzufangen und dieses mit einem
entsprechenden Hinweis zu beenden (oder gegebenenfalls weiterzuführen).
try
{
Console.Write(" Geben Sie Ihr Alter ein: ");
string input = Console.ReadLine();
age = Convert.ToInt32(input);
}
catch (FormatException)
{
Console.Write(" Die Eingabe hatte kein gültiges Zahlenformat ");
Environment.Exit(0);
}
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
101
using-Direktive und Framework-Klassen
Kommentare
C# kennt drei Arten von Kommentaren, mit denen Quelltext für die Nachwelt oder spätere Überarbeitungen kommentiert und erläutert werden kann. Die folgenden Abschnitte stellen die drei Möglichkeiten zur
Kommentierung von Quelltext vor.
Einzeilige Kommentare
//-Kommentare reichen bis zum Ende der aktuellen Zeile. Sie werden meist genutzt, um die Bedeutung von
Variablen oder einzelnen Code-Abschnitten anzugeben
int AMethod()
{
double capital;
int interestRate;
int t;
// Startkapital
// Verzinsung in Prozent
// Laufzeit
// Kapitalertrag berechnen
double result = capital * (1 + interestRate/100.0 * t);
// ...
Mehrzeilige Kommentare
Mehrzeilige Kommentare werden mit /* eingeleitet und enden mit */. Sie können nicht ineinander verschachtelt werden. /*…*/-Kommentare werden traditionell für ausführlichere Beschreibungen von Klassen, Strukturen, Methoden etc. verwendet, obwohl es auch Programmierer gibt, die für diese Aufgaben
ebenfalls //-Kommentare nutzen und die /*…*/-Kommentare ausschließlich zum Auskommentieren von
Quelltextblöcken nutzen. In C# haben die mehrzeiligen Kommentare zusätzliche Konkurrenz bekommen,
denn für die Beschreibung von Klassen und Klassenmembern empfiehlt sich die Verwendung der speziellen Dokumentationskommentare.
/*
* Demo-Programm
*
* Dateiname: HelloWorld.cs
* Autor:
Manfred Mustermann
*/
using System;
class Program
{
}
Dokumentationskommentare
XML-Dokumentationskommentare beginnen mit einem dreifachen Schrägstrich /// und erläutern nicht
nur den Quelltext, sondern können auch zur Erstellung von HTML- oder XML-Dokumentationen genutzt
werden. Den XML-Dokumentationskommentaren ist das Kapitel 21 gewidmet.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
102
Kapitel 5: Anatomie eines C#-Programms
Dateien und Assemblies
Der Quelltext einer C#-Anwendung besteht zu 99% aus Typdefinitionen, allen voran Klassen. Gespeichert
wird dieser Quelltext in Quelldateien mit der Dateierweiterung .cs. Welche Beziehung besteht zwischen
Klassen (allgemein Typdefinitionen) und den Quelltextdateien?
Klassen und Dateien
Grundsätzlich sollte jede Klasse in ihrer eigenen Quelltextdatei definiert werden. Auf diese Weise wird der
Quelltext automatisch übersichtlich organisiert und die vom Programmierer gewählte CodeModularisierung spiegelt sich in der Aufteilung auf die Dateien wider.
Sie sind allerdings nicht sklavisch an die Einhaltung dieser Regel gebunden. In C# können Sie genauso gut
beliebig viele Klassen (und andere Typen) in einer Quelltextdatei zusammenfassen, ja Sie können sogar die
Definition einer Klasse auf mehrere Dateien verteilen (letzteres ist erst ab C# 2.0 möglich; siehe in Kapitel
10 den Abschnitt »Klassendefinition«). Sie sollten allerdings einen Grund für die Abweichung von der 1:1Regel haben.
Visual Studio beispielsweise nutzt die Möglichkeit zur Verteilung des Klassencodes auf mehrere Dateien
dazu, den Code von Formular-Klassen sauber zu trennen: in Code, der vom Windows Forms-Designer
erstellt und verwaltet wird (Form1.cs-Datei), und in Code, den der Programmierer selbst editiert
(Form1.Designer.cs-Datei). Für den normalen Entwickler dürfte die Option, mehrere Klassen (allgemein
Typen) in einer Quelltextdatei zusammen zu fassen, interessanter sein – beispielsweise um Hilfsklassen (typen), die nur zur Unterstützung der Hauptklasse der Datei benötigt werden, zusammen mit dieser in
einer Datei definieren zu können (falls die Hilfstypen nicht sowieso als verschachtelte Typen in der Klasse
definiert werden; siehe in Kapitel 10 den Abschnitt »Verschachtelte Typdefinitionen«).
ACHTUNG
Für Java-Programmierer: Anders als in Java können in einer C#-Quelltextdatei mehrere public-Klassen
definiert werden. Entsprechend gibt es keine Vorschrift, dass der Dateiname gleich dem Namen einer der Klassen aus der Datei
sein müsste.
Dateien und Assemblies
Der üblichen 1:1-Zuordnung von Klassen zu Dateien steht die n:1-Relation zwischen Quelldateien und
Assemblies gegenüber. Mit anderen Worten: Bei der Kompilierung muss der Programmierer den Quelltext,
den er der gerade erst aus gutem Grund auf mehrere Dateien verteilt hat, wieder zusammenführen.
In Visual Studio geschieht dies natürlich mithilfe der Projektverwaltung (siehe in Kapitel 2 den Abschnitt
»Die Projektverwaltung«). Die Projektverwaltung kompiliert die Quelltextdateien allerdings nicht selbst,
sondern generiert lediglich aus den Daten über Projektaufbau und -konfiguration den passenden Aufruf
des Befehlszeilencompiler csc.exe.
Wenn Sie möchten, können Sie den csc-Compiler natürlich auch direkt aufrufen, um Ihre Quelldateien zu
Assemblies zu kompilieren. Die Bedienung des Compilers ist nicht sonderlich schwierig und verrät gleichzeitig ein bisschen mehr über die Arbeitsweise der Projektverwaltung.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Dateien und Assemblies
103
Anwendungen aus einzelnen Dateien erstellen
Eine einfache Anwendung, die nur aus einer einzigen Quelltextdatei besteht, kompilieren Sie, indem Sie csc
mit dem Namen der Quelltextdatei aufrufen:
csc Quelltextdatei.cs
Der Compiler erzeugt daraufhin eine Assembly für eine Konsolenanwendung. Die Assembly trägt den
Namen der Quelltextdatei – also Quelltextdatei.exe – und wird im aktuellen Verzeichnis abgespeichert.
Soll die .exe-Datei einen anderen Namen erhalten, geben Sie diesen als Wert der /out-Option an:
csc /out:Demo.exe Quelltextdatei.cs
Bei der Arbeit mit Visual Studio würden Sie stattdessen ein Projekt auf Basis der Projektvorlage für Konsolenanwendungen anlegen und den Quelltext in die Datei Program.cs eingeben, die zur Grundausstattung
der Projektvorlage gehört. Beim Erstellen wird eine .exe-Datei erzeugt, die den Namen des Projekts (nicht
der Quelltextdatei!) trägt.
Anwendungen aus mehreren Dateien erstellen
Gehören zu der Anwendung mehrere Quelltextdateien, müssen Sie diese alle im csc-Aufruf auflisten:
csc Datei1.cs Datei2.cs Datei3.cs
Liegen die Quelltextdateien in einem gemeinsamen Verzeichnis, kann der *-Platzhalter viel Tipparbeit
sparen. Der folgende Aufruf kompiliert alle .cs-Dateien im aktuellen Verzeichnis zu einer Assembly:
csc *.cs
Die Assembly trägt den Namen der Quelltextdatei, die die Main()-Methode enthält.
Bei der Arbeit mit Visual Studio müssen Sie darauf achten, dass alle zur Anwendung gehörenden Quelltextdateien auch als Teil des Projekts erfasst sind. Neue Quelltextdateien legen Sie dazu direkt als Teil des
Projekts an (Menübefehl Projekt/Neues Element hinzufügen); bestehende Quelltextdateien können Sie mit
dem Menübefehl Projekt/Vorhandenes Element hinzufügen in das Projekt integrieren.
Bibliotheken und andere Zieltypen erstellen
Insgesamt können Sie Assemblies für vier verschiedene Zieltypen erstellen: Konsolenanwendungen, Windows Forms-Anwendungen, Bibliotheken und Module. Den Zieltyp teilen Sie csc über die Option /target,
abgekürzt /t, mit. Mögliche Werte sind: exe (Standard), winexe, library und module.
csc /t:library Class1.cs Class2.cs
Wenn Sie eine Bibliothek erstellen, trägt diese standardmäßig den Namen der ersten aufgeführten Quelldatei mit der Dateierweiterung dll – in obigem Fall also Class1.dll.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
104
Kapitel 5: Anatomie eines C#-Programms
Soll die Bibliothek einen anderen Namen erhalten, geben Sie diesen als Wert der /out-Option an:
csc /t:library /out:mylib.dll Class1.cs Class2.cs
Unter Visual Studio legen Sie den Zieltyp durch Auswahl einer passenden Projektvorlage fest. Man sollte es
zwar vermeiden, aber falls Sie den Zieltyp nachträglich ändern müssen, finden Sie dazu in den Projekteigenschaften auf der Seite Anwendung die Option Ausgabetyp.
Bibliotheken in Anwendungen verwenden
Wenn Sie eine Anwendung kompilieren, die auf Klassen und andere Typen zugreift, die in einer anderen
Assembly definiert sind, müssen Sie dem Compiler einen Verweis auf diese Assembly mitgeben. Hierzu
gibt es die Compiler-Option /reference oder abgekürzt /r:
csc /t:winexe /r:mylib.dll Program.cs Form1.cs
Unter Visual Studio richten Sie Verweise mithilfe des Befehls Verweis hinzufügen aus dem Kontextmenü
des Projektunterknotens Verweise ein, siehe auch in Kapitel 2 den Abschnitt »Die Projektverwaltung«.
Imperative Programmierung in C#
Das imperative Erbe von C# besteht im Wesentlichen aus vier grundlegenden Konzepten: Datentypen,
Variablen, Operatoren und Anweisungen.
Daten und Datentypen
Damit ein Programm Daten eines bestimmten Typs (ganze Zahlen, Dezimalzahlen, Zeichenfolgen, Adressen, Vektoren etc.) verarbeiten kann, muss der jeweilige Datentyp bekannt sein. Der Typ bestimmt, wie die
Daten im Arbeitsspeicher abgelegt werden und welche Operationen auf den Daten erlaubt sind.
C# kennt verschiedene vordefinierte Typen, beispielsweise int für Ganzzahlen, double für Gleitkommazahlen (Dezimalzahlen), bool für die booleschen Wahrheitswerte (true und false) oder string für Strings
(Zeichenfolgen). Weitere Datentypen, insbesondere für komplexe Daten wie Adressen, Vektoren, Kunden
etc., können in Form von Klassen, Strukturen oder Enumerationen vom Programmierer definiert werden.
Die Daten eines Typs werden als Werte bezeichnet, im Falle komplexer Typen, insbesondere Klassen, auch
als Objekte.
Die Typinformation ist vor allem für die Übersetzungswerkzeuge wichtig, also für Compiler und Interpreter. Für den Programmierer weit interessanter ist hingegen die Frage, wie er die Daten, mit denen er arbeiten möchte, überhaupt in seinem Programm repräsentieren kann. Dies führt uns zu den Literalen und
Variablen.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
105
Imperative Programmierung in C#
Literale und Variablen
Literale sind Daten, die direkt im Quelltext codiert sind. Es gibt sie nur für einige vordefinierte Typen wie
Ganzzahlen, Gleitkommazahlen oder Strings. Der Typ eines Literals ist am Format erkennbar
1, -12
10.34
"Hallo"
// int-Literal
// double-Literal
// string-Literal
Variablen sind Speicher für Daten. Sie werden unter Angabe eines Typs und eines Namens deklariert.
int age;
string name;
double price;
Mit dem Zuweisungsoperator = können in Variablen Werte gespeichert werden. Voraussetzung ist allerdings, dass der Typ des Werts und der Typ der Variablen identisch sind oder es zumindest eine implizite
Typumwandlung zwischen den Typen gibt.
age = 34;
name = "Jim";
price = 10.34; // implizite Umwandlung von double (Typ des Literals) zu int (Typ der
// Variablen price). In price wird der umgewandelte Wert (10)
// gespeichert.
In anderen, allerdings bei weitem nicht allen Fällen ist eine explizite Typumwandlung mit dem CastOperator () oder durch Aufruf einer passenden Konvertierungsmethode möglich:
double r = 34.5;
int age = (int) r;
// explizite Typumwandlung, speichert in age
// den Wert 34
string s = "23.95";
double price = Convert.ToDouble(s); // Konvertierungsmethode, speichert in price
// den Wert 23.95
ACHTUNG
Für C++-Programmierer: In C# fallen Deklaration (Bekanntmachung eines neuen Elements beim Compiler)
und Definition (Beschreibung des Elements) stets zusammen. Die Begriffe Deklaration und Definition werden daher meist
synonym verwendet.
Operatoren und Ausdrücke
Die Werte der primitiven vordefinierten Typen können mit Operatoren verarbeitet werden. Ganzzahlen
können beispielsweise mit den Operatoren +, –, * und / addiert, subtrahiert, multipliziert oder dividiert
werden:
3 + 4
// addiert die ganzen Zahlen
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
106
Kapitel 5: Anatomie eines C#-Programms
Eine Kombination aus Literalen, Variablen und Operatoren bezeichnet man als Ausdruck. Variablen in
Ausdrücken repräsentieren den Wert, der in ihnen gerade gespeichert ist. Jeder Ausdruck steht für einen
Wert, der zur Laufzeit berechnet wird.
3 + 4
3 + 4 * 2
n < 4
"Leopold" + "Bloom"
//
//
//
//
Wert 7
Wert 11
true, wenn der Wert von n kleiner als 4 ist, ansonsten false
verkettet die beiden Strings zu "Leopold Bloom"
HINWEIS
Grundsätzlich verändern Operatoren nicht den Wert ihrer Operanden. Einzige Ausnahme: die Operatoren für
Inkrement ++ und Dekrement --, die den Wert ihres einzigen Operanden um Eins erhöhen oder erniedrigen.
Anweisungen
Anweisungen legen fest, was eine Anwendung tun soll. Es gibt verschiedene Arten von Anweisungen, die
meisten von Ihnen erkannt man daran, dass sie mit einem Semikolon abgeschlossen werden. Zwei Arten
von Anweisungen haben Sie bereits kennen gelernt: die Deklarationsanweisung zur Einrichtung von
Variablen und die Zuweisung.
int
int
int
c =
a = 10;
b = 5;
c;
a * b;
// Wird einer Variablen bereits bei der Definition ein Wert zugewiesen
// spricht man von Initialisierung
// Zuweisung
Auf der rechten Seite der Zuweisung steht eine Variable, auf der linken Seite ein Ausdruck.
Blöcke
Anweisungen können mithilfe geschweifter Klammern zu Anweisungsblöcken zusammen gefasst werden:
{
// Folge von Anweisungen
}
Blöcke können ineinander geschachtelt werden. Variablen, die in einem Block definiert werden, sind nur in
ihrem Block gültig (untergeordnete Blöcke mit eingeschlossen).
Verzweigungen
Speziellen Anweisungen dienen dazu, den Programmfluss zu steuern. Dieser verläuft standardmäßig ganz
geradlinig, d.h. die Anweisungen werden in der Reihenfolge ausgeführt, in der sie im Quelltext stehen.
Sprünge, Verzweigungen und Schleifen erlauben dem Programmierer den Programmablauf zu steuern.
Die if-Anweisung ist eine einfache Verzweigung. Sie prüft anhand einer Bedingung (ein Ausdruck, der
einen der booleschen Wahrheitswerte true oder false zurückliefert), ob die nachfolgende Anweisung
respektive der nachfolgende Anweisungsblock ausgeführt werden soll.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Imperative Programmierung in C#
107
if (n < 100)
{
//... Anweisungen, die nur dann ausgeführt werden, wenn n kleiner 100 ist
}
Die Erweiterung der if-Anweisung ist die if…else-Anweisung, die anhand einer Bedingung entscheidet,
welcher von zwei nachfolgenden Blöcken ausgeführt werden soll.
if (n < 100)
{
//... Anweisungen, die ausgeführt werden, wenn n kleiner 100 ist
}
else
{
//... Anweisungen, die ausgeführt werden, wenn n nicht kleiner 100 ist
}
Schleifen
Schleifen sind Iterationsanweisungen, die einen zugehörigen Anweisungsblock mehrmals hintereinander
ausführen. Die wichtigsten Schleifen sind die while- und die for-Schleife.
Die while-Schleife wird so lange ausgeführt, wie die eingangs der Schleife formulierte Bedingung true
ergibt.
int n = 2;
while (n < 10)
{
n = n + (n-1);
}
Schleifen bergen stets die Gefahr, dass es durch Unachtsamkeit oder Fehleinschätzung zu Endlosausführungen kommt. Die obige Schleife würde beispielsweise nach dem vierten Durchlauf (Iteration) beendet, da
n dann den Wert 17 enthält. Würde n aber mit dem Wert 1 initialisiert, würde die Schleife endlos ausgeführt, da der Ausdruck n + (n-1) immer wieder 1 ergibt.
Die for-Schleife ist etwas sicherer in der Verwendung, da in ihrem Fall alle Informationen zur Schleifensteuerung im Kopf der Schleife zusammengezogen sind. Sie wird meist dann verwendet, wenn die
Anzahl der Schleifeniterationen feststeht. Die folgende for-Schleife beispielsweise berechnet das Quadrat
der ersten zehn natürlichen Zahlen:
for (int i = 1; i <= 10; i++)
{
Console.WriteLine(" Das Quadrat von {0} ist {1}", i, i*i);
}
Die Ausgabe sieht folgendermaßen aus:
Das Quadrat von 1 ist 1
Das Quadrat von 2 ist 4
Das Quadrat von 3 ist 9
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
108
Kapitel 5: Anatomie eines C#-Programms
Das
Das
Das
Das
Das
Das
Quadrat
Quadrat
Quadrat
Quadrat
Quadrat
Quadrat
von
von
von
von
von
von
4
5
6
7
8
9
ist
ist
ist
ist
ist
ist
16
25
36
49
64
81
Funktionen (Methoden)
Höher entwickelte imperative Programmiersprachen unterstützen die Auslagerung von Code in Funktionen. So können Teilprobleme separat gelöst und anschließend an anderen Stellen im Quelltext beliebig
eingesetzt werden. C# kennt Funktionen nur als Member von Klassen (dann Methoden genannt). Das
Prinzip ist allerdings das Gleiche.
Funktionen bestehen aus einem Funktionskopf und einem Anweisungsblock (Funktionsrumpf). Der
Funktionskopf deklariert die Funktion, gibt ihr einen Namen und legt die Schnittstelle zur Außenwelt fest.
Eine Funktion zur Berechnung des Quadrats könnte wie folgt aussehen:
int Quadrat(int n)
{
int result;
result = n * n;
return result;
}
Es ist sinnvoll, den Funktionsnamen so zu wählen, dass daraus der Gebrauch der Funktion direkt ersichtlich ist. Hinter dem Funktionsnamen folgt in Klammern die Liste der Parameter, die der Funktion übergeben werden können. Im obigen Fall ist für die Funktion nur ein Parameter (n) vorgesehen.
Mittels der Anweisung
return result;
wird das Ergebnis der Berechnung zurückgeliefert. Das Schlüsselwort int vor dem Funktionsnamen gibt an,
dass es sich bei dem von der Funktion zurückgelieferten Wert um einen int-Wert handelt.
Code, der die Funktion aufruft, übergibt ihr Werte für die Parameter und speichert das Ergebnis in einer
eigene Variable:
int square = 0;
int number = 12;
square = Quadrat(number);
HINWEIS
Es ist angebracht, an diese Stelle einige weitere mit Funktionen verbundene Begriffe zu erklären:
Als Parameter einer Funktion werden diejenigen Variablen der Funktion bezeichnet, die innerhalb der Klammern im
Funktionskopf deklariert werden
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Objektorientierte Programmierung in C#
109
Die Werte, die beim Funktionsaufruf an die Parameter übergeben werden, nennt man auch Argumente.
Variablen, die innerhalb eines Funktionsrumpfes deklariert werden, bezeichnet man als lokale Variablen.
Objektorientierte Programmierung in C#
Objektorientierte Programmierung bedeutet, dass der Programmierer versucht, die ihm gestellten Aufgaben mithilfe von Objekten zu lösen – eine Form der Problemlösung, die uns Menschen unter anderem den
Titel »Werkzeug-Benutzer« eingebracht hat.
Stellen Sie sich ein Grundstück vor, das von einem kleinen Bach durchflossen wird. Von diesem Bach
wollen Sie einen Kanal abzweigen, der zu einem Feld führt, das Sie auf diese Weise bewässern möchten.
Wie gehen Sie vor?
Sie könnten den Kanal mit Ihren Händen graben. Diese Lösung verzichtet auf jegliche Hilfsmittel. Übertragen auf die Software-Entwicklung würde dies bedeuteten, dass Sie auf die Vorzüge einer höheren Programmiersprache verzichten und in Assembler auf Maschinenbefehl-Ebene programmieren. In der
Software-Entwicklung hat dieser Ansatz durchaus Vorzüge, führt allerdings hier wie dort zu blutigen
Händen.
Oder Sie benutzen eine Schaufel. Sie akzeptieren einfache Hilfsmittel, erledigen aber die ganze Arbeit in
Handarbeit. In der Software-Entwicklung wäre dies mit dem Einsatz einer strukturieren, aber nicht objektorientierten höheren Programmiersprache vergleichbar.
Sie könnten aber auch den Bauplan für einen kleinen Bagger entwerfen, den Bagger konstruieren und mit
diesem den Kanal ausheben. Dies entspräche dem objektorientierten Ansatz. Statt alles in Handarbeit zu
machen, überlegen Sie sich, wie ein Hilfsmittel (im OOP-Ansatz ein Objekt) aussehen müsste, mit dem Sie
die gestellte Aufgabe schnell und effizient erledigen können. Da Sie kein solches Hilfsmittel zur Verfügung
haben, entwerfen Sie einen Bauplan (im OOP-Ansatz eine Klasse), der beschreibt, wie das Hilfsmittel
(Objekt) auszusehen hat. Dann konstruieren Sie nach dem Bauplan das Hilfsmittel (Objekterzeugung). Ist
das Hilfsmittel fertig, benutzen Sie es zur Lösung der Aufgabe.
In einem Aspekt stimmt die Korrelation zwischen technischem und objektorientiertem Lösungsansatz
allerdings nicht: Während bei dem technischen Lösungsansatz der zweite Schritt (der Bau des Baggers) die
meiste Zeit und Mühe kostet, kann sich der Programmierer darüber freuen, dass dieser Schritt (die Objekterzeugung) fast vollständig vom Compiler übernommen wird. Die Relation zwischen dem ersten und
dritten Schritt ist hingegen durchaus realistisch wiedergegeben: Bei der objektorientierten Programmierung fließt viel Arbeit in die Konstruktion der Klassen (Baupläne). Die Programmierung mit den Objekten
der Klassen fällt dafür umso leichter.
Wie sind OOP-Objekte beschaffen?
Die Objekte, mit denen wir es in der objektorientierten Programmierung zu tun haben, sind den Objekten,
denen wir in der realen Welt begegnen, durchaus vergleichbar. Sie besitzen
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
110
Kapitel 5: Anatomie eines C#-Programms
Merkmale
Die Merkmale beschreiben das Objekt und seinen Zustand. Vielleicht haben Sie gerade eine Tasse vor
sich stehen. Wenn Sie die Tasse anblicken, registriert ihr Gehirn sofort eine ganze Reihe von Merkmalen wie Größe, Form, Inhaltsvolumen, Farbe oder auch Merkmale, die den aktuellen Zustand beschreiben (etwa Lackschäden oder verbliebene Menge Kaffee in der Tasse).
und Verhaltensweisen
Die Verhaltensweisen geben an, wie das Objekt zu verwenden ist (aus einer Tasse kann man trinken)
oder wie das Objekt selbst reagieren kann (eine Tasse kann zerspringen).
Mit »Verhaltensweisen« verbinden wir in der Regel Aktivitäten, die ein Objekt von selbst zeigt: etwa das
Wachsen einer Pflanze, das Wiehern eines Pferdes, das Umkippen einer Flasche oder das Klingeln des
Weckers. Im objektorientierten Sinne zählen zu den Verhaltensweisen aber auch Aktivitäten, die mit
der Bedienung oder Verwendung eines Objekts zu tun haben, also das Gießen einer Pflanze, das Pflegen
eines Pferdes, das Öffnen einer Flasche oder das Stellen eines Weckers.
Arten von Objekten
Die Nachbildung realer Dinge durch Objekte ist ein wichtiges und mächtiges Instrument der objektorientierten Programmierung. Auf der anderen Seite würde die objektorientierte Programmierung schnell an
ihre Grenzen stoßen, wenn ihre Objekte ausschließlich Dinge der realen Welt nachbilden könnten.
Glücklicherweise kann ein Objekt aber nahezu alles sein, was als Einheit aus Merkmalen und zugehörigen
Verhaltensweisen ausgedrückt werden kann. So gibt es beispielsweise
Objekte, die Dinge der realen Welt nachbilden.
Objekte, die virtuelle Dinge repräsentieren (beispielsweise Dinge, die auf dem Bildschirm zu sehen
sind, wie Fenster oder Schaltflächen oder ein gezeichnetes Monster).
Objekte, die Daten repräsentieren oder verwalten (ein Programm zur Verwaltung von Musik-CDs
könnte die einzelnen CDs als Objekte darstellen und zusammen in einem Container-Objekt verwalten, das das Ablegen, Sortieren und Suchen bestimmter CDs erleichtert).
Objekte, die einfach eine bestimmte Funktionalität zur Verfügung stellen (beispielsweise für einen
gegebenen Satz von Daten statistische Berechnungen wie Mittelwert, Standardabweichung u.a. ausführen).
Objekte und Klassen
OOP-Objekte kommen nicht aus dem Nichts, sondern werden aus Klassen erzeugt.
Die Klassendefinition
Klassen sind nichts anderes als vom Programmierer definierte Datentypen. In der Klassendefinition werden die Merkmale und Verhaltensweise des Objekts zusammengefasst. Merkmale werden dabei als Variablen, Verhaltensweisen als Methoden definiert.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
111
Objektorientierte Programmierung in C#
HINWEIS
Die objektorientierte Terminologie ist etwas uneinheitlich. Variablen, die als Member von Klassen definiert
werden, heißen in der Literatur meist Felder, Member-Variablen oder Elementvariablen. Methoden, die in Klassen definiert
werden, werden auch als Member-Funktionen oder Elementfunktionen bezeichnet.
Eine Klasse zur Erzeugung von Employee-Objekten könnte beispielsweise über die Felder Name und Salary
sowie die Methode GetsPromotion() verfügen:
class Employee
{
public string Name;
public int Salary;
public void GetsPromotion(int amount)
{
Salary = Salary + amount;
}
// Felder für Merkmale
// Methode für Verhaltensweise
}
Klassen und Objekte
Die Beziehung zwischen Klasse und Objekt sollte ganz klar sein. Im objektorientierten Denkmodell ist ein
Objekt ein Ding, beispielsweise Sie selbst, Ihr Nachbar, der Bleistift, der neben Ihnen liegt, oder die Bäume,
die Sie sehen, wenn Sie aus dem Fenster schauen. Einerseits ist jedes dieser Objekte für sich genommen
einzigartig, andererseits gibt es zu jedem dieser Objekte auch gleichartige Objekte. Wenn Sie beispielsweise
in ein Schreibwarengeschäft gehen, um einen Buntstift zu kaufen, finden Sie dort rote, blaue, grüne und
gelbe, dicke und dünne, billige und teure Buntstifte.
Zur Bezeichnung gleichartiger Objekte verwenden wir in der Sprache Oberbegriffe: »Buntstift« ist beispielsweise ein solcher Oberbegriff. Er weckt in uns die Vorstellung von einem langen, dünnen Schreibgerät aus Holz, das eine bestimmte Farbe und eine bestimmte Dicke hat, das man anspitzen und mit dem
man malen kann. Beachten Sie, dass der Oberbegriff »Buntstift« nichts über die genaue Farbe oder Dicke
aussagt, er legt nur fest, dass die Objekte, die wir als Buntstifte ansehen, Merkmale wie Farbe und Dicke
haben. Welche Farbe und welche Dicke ein Buntstift-Objekt hat, ist seine Sache.
Was in der Sprache die Oberbegriffe sind, sind in der objektorientierten Programmierung die Klassen.
Auch Klassen sind allgemeine Beschreibungen für eine Gruppe gleichartiger Objekte. Während wir in der
Sprache Oberbegriffe jedoch meist nur dazu verwenden, die Objekte, denen wir täglich begegnen, zu
beschreiben und zu klassifizieren, erfüllen die Klassen in der objektorientierten Programmierung zwei
Aufgaben:
sie beschreiben die Dinge, die wir in dem Programm verwenden wollen und
sie dienen als Vorlage oder Gussform, aus der wir die Objekte, mit denen wir arbeiten wollen, erst
erzeugen.
Die Objekterzeugung
Eine Klasse ist lediglich ein Datentyp. Der nächste Schritt besteht folglich darin, eine Variable vom Typ der
Klasse zu definieren.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
112
Kapitel 5: Anatomie eines C#-Programms
Employee jim;
Dies ist allerdings erst einmal nur eine Variable. Die Variable enthält noch kein Objekt. Welches Objekt
auch, es wurde ja noch gar kein Objekt erzeugt. Dies geschieht erst vermittels des new-Operators:
jim = new Employee();
Der new-Operator sorgt dafür, dass im Speicher ein Objekt, man spricht auch von einer Instanz, der Klasse
angelegt wird. Jedes Objekt erhält dabei Kopien der in der Klasse definierten Felder und einen Verweis auf
die Methoden der Klassen.
HINWEIS
Die einzelnen Objekte, die von einer Klasse gebildet werden, besitzen also jede einen eigenen Satz von Feldern,
verwenden aber die gleichen Methoden. Die Einrichtung eines Objekts wird auch als Instanzbildung oder Instanziierung
bezeichnet.
Klassen sind Verweistypen
Das mithilfe von new erzeugte Objekt wird allerdings nicht direkt in der Variablen jim gespeichert. Vielmehr
wird das Objekt irgendwo im Arbeitspeicher angelegt und in der Variablen wird lediglich ein Verweis auf
das Objekt gespeichert. Den Verweis liefert der new-Operator zurück.
Oft werden Variablendefinition und Objekterzeugung in einem Schritt ausgeführt:
Employee jim = new Employee();
HINWEIS
C# teilt seine Typen in Wert- und Verweistypen, je nachdem, ob die Werte des Typs direkt in den Variablen
gespeichert werden oder ob diese lediglich Verweise auf das irgendwo sonst im Speicher liegende Objekt verwahren. Die
vordefinierten Typen int und double sind Beispiele für Werttypen, Klassen sind Verweistypen.
Der Konstruktor
Sind Ihnen im obigen Abschnitt die runden Klammern hinter dem Typ Employee aufgefallen? Es sieht aus,
als wäre an der Objekterzeugung eine Methode beteiligt, die den Namen der Klasse trägt. Nun, dies ist gar
nicht mal so falsch.
Tatsächlich verfügt jede Klasse über eine spezielle »Methode«, den so genannten Konstruktor, der genauso
heißt wie die Klasse. Klassen, die keinen eigenen Konstruktor definieren, bekommen einen Ersatzkonstruktor vom Compiler zugeteilt. Der Konstruktor wird ausschließlich bei der Objekterzeugung aufgerufen.
Programmieren mit Objekten
Mit Hilfe des Punktoperators können Sie auf die Member eines Objekts zugreifen:
Employee jim = new Employee();
jim.Name = "Jim Bubble";
jim.Salary = 1500;
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Objektorientierte Programmierung in C#
113
Console.WriteLine("\n Mitarbeiter {0} verdient {1} Euro", jim.Name, jim.Salary);
jim.GetsPromotion(275);
Console.WriteLine("\n Mitarbeiter {0} verdient {1} Euro", jim.Name, jim.Salary);
Allerdings ist der Zugriff nicht für jeden Member des Objekts erlaubt. Die Klasse kann nämlich sensible
Elemente vor dem Zugriff von außen (über die Objektvariable) schützen. Dies bringt uns zu den Konzepten und Zielen, die mit der Klassendefinition verbunden sind.
Der null-Verweis
Wenn Sie in einer Variable den Verweis auf ein Objekt speichern:
Employee assistant = new Employee();
verweist die Variable so lange auf das Objekt, bis die Variable aufhört zu existieren oder Sie ihr einen
Verweis auf ein anderes Objekt zuweisen:
assistant = new Employee();
// Zuweisung eines anderen Objekts
Soll die Variable zeitweilig ohne Objektverweis sein, weisen Sie ihr null zu:
assistant = null;
An kritischen Stellen können Sie dann prüfen, ob die Variable auf ein Objekt verweist oder nicht:
if (assistant != null) {
// assistant verweist auf ein Objekt, also weiter
...
Zugriffe über null-Referenzen lösen zur Laufzeit eine NullReferenceException-Ausnahme aus. Statt auf null
zu prüfen, können Sie fehlerhafte Zugriffe also auch durch eine Ausnahmebehandlung (siehe Kapitel 15)
abfangen:
try
{
Console.WriteLine(obj1.iStorage);
}
catch (Exception)
{
Console.WriteLine("Fehler: Zugriff über null-Verweis");
}
Die Klasse als Zentrum der objektorientierten Programmierung
Eine Klasse zu definieren, bedeutet weit mehr als einfach nur die Felder und Methoden aufzulisten, die
dem Objekten der Klasse gemein sind. Eine gute Klassendefinition sollte Wert legen auf:
Abgeschlossenheit
Sichere Verwendung
Kapselung
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
114
Kapitel 5: Anatomie eines C#-Programms
Kapselung
Die Klasse verbirgt die Interna ihrer
Implementierung vor dem Benutzer.
Trennung von Schnittstelle und
Implementierung.
Klasse
Abgeschlossenheit
Sicherheit
Die Klasse vereint in sich funktionell
zusammen gehörende Daten und
Methoden.
Die Klasse schützt sich selbst vor
unsachgemäßem Gebrauch.
(Versch. Techniken: Konstruktor,
Zugriffsmodifizierer, public-Methoden
vermitteln Zugriff auf private Felder.)
Abbildung 5.3 Mit dem Klassen-Design verknüpfte Ideen und Konzepte
Abgeschlossenheit
Eine Klasse sollte in sich alle Elemente vereinen, die nötig sind, damit mit den Objekten der Klasse sinnvoll
gearbeitet werden kann. So wäre z.B. eine Klasse Counter, die keine Felder zum Speichern des aktuellen
Zählerstandes oder Methoden zum Weiterdrehen und Zurücksetzen des Zählers definiert, vermutlich
ziemlich nutzlos. Felder wie Color oder Methoden wie ChangeColor() haben dagegen in einer Klasse Counter
üblicherweise nichts zu suchen – es sei denn, der spezielle Einsatz der Klasse in einer Anwendung erfordert
es.
using System;
class Counter
{
public int Value;
public void Turn()
{
++Value;
if (Value > 999)
Value = 0;
}
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Objektorientierte Programmierung in C#
115
public void Reset()
{
Value = 0;
}
}
class Program
{
static void Main(string[] args)
{
Counter counter = new Counter();
counter.Value = 0;
counter.Turn();
Console.WriteLine(counter.Value);
}
}
Sichere Verwendung
Als Autor einer Klasse sollten Sie stets bemüht sein, die Programmierung mit der Klasse und ihren Objekten so einfach und sicher wie möglich zu gestalten. Die Klasse unterstützt dies mit
Konstruktoren
Zugriffsrechten
Der Konstruktor wird automatisch im Zuge der Objekterzeugung aufgerufen und soll sicherstellen, dass
das Objekt in einen sinnvollen Anfangszustand gesetzt wird. Er wird vor allem dazu genutzt, den Feldern
sinnvolle Anfangswerte zuzuweisen oder nötige Initialisierungsarbeiten, beispielsweise die Anforderung
externen Ressourcen (Dateien, Internet-Verbindungen etc.), zu erledigen.
class Counter
{
public int Value;
public Counter(int startvalue)
{
if (startvalue < 1000)
{
Value = startvalue;
}
else
{
Value = 0;
}
}
// wie gehabt
}
class Program
{
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
116
Kapitel 5: Anatomie eines C#-Programms
static void Main(string[] args)
{
Counter counter = new Counter(0);
counter.Turn();
Console.WriteLine(" " + counter.Value);
}
}
Durch die Vergabe von Zugriffsrechten für Member kann die Klasse steuern, wer auf die Member zugreifen
kann. Beispielsweise kann die Klasse mit dem Zugriffsspezifizierer private festlegen, dass die betroffenen
Member privat sind und nur innerhalb der Klassendefinition verwendet werden können. Als public deklarierte Elemente sind dagegen öffentlich und unterliegen keinerlei Zugriffsbeschränkung (d.h. sie können
über die Objekte angesprochen werden).
HINWEIS
Member, die ohne Zugriffsmodifizierer deklariert werden, sind in C# standardmäßig private.
Die Vergabe von Zugriffsrechten ist die Grundlage zweier weiterer Konzepte:
Der Vorstellung von der Klasse als Black Box, meist als »Kapselung« bezeichnet (siehe den nachfolgenden Abschnitt).
Der durch Methoden moderierte Zugriff auf Felder.
Greifen wir hierzu noch einmal das Beispiel der Klasse Counter auf. Die Objekte der Klasse sollen dreistellige Zähler repräsentieren, die nur in Einerschritten weitergedreht und bei Erreichen der 999 auf 0 zurückgestellt werden. Solange der Benutzer der Counter-Objekte immer brav die Methode Turn() aufruft, ist die
korrekte Funktionsweise der Objekte sichergestellt. Sobald er aber auf die Idee kommt, Value direkt zu
manipulieren, kann er den internen Zähler beliebig vor- oder zurückstellen und sogar Werte über 999
zuweisen.
Ein allgemein übliches Konzept ist es daher, Felder durch private-Deklaration vor dem direkten Zugriff zu
schützen und ihre Manipulation nur über public-Methoden und -Konstruktoren zu gestatten. In deren
Implementierung kann der Autor der Klasse dann sicherstellen, dass die angestrebte Manipulation stets so
ausgeführt wird, dass die Integrität des Objekts nicht verletzt wird.
Wenn Sie in der Klasse Counter das Feld Value als private deklarieren, kann der Wert des Zählers nur über
den Konstruktor oder eine der Methoden Turn() und Reset() gesetzt oder verändert werden. Da diese dem
Feld Value nur Werte kleiner als 1000 zuweisen, ist die korrekte Verwendung der Counter-Objekte sichergestellt. Allerdings gibt es jetzt auch keine Möglichkeit mehr, den aktuellen Stand des Zählers – sprich den
Wert von Value – abzufragen. Hierfür muss eine zusätzliche public-Methode definiert werden:
class Counter
{
private int value;
public Counter(int startvalue)
{
// wie oben
}
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Objektorientierte Programmierung in C#
117
public void Turn()
{
// wie oben
}
public void Reset()
{
// wie oben
}
public int GetValue()
{
return value;
}
}
class Program
{
static void Main(string[] args)
{
Counter counter = new Counter(0);
counter.Turn();
Console.WriteLine(" " + counter.GetValue());
}
}
HINWEIS
Öffentliche Methoden, die allein dazu dienen, den Wert von private-Feldern abzufragen oder zu setzen, werden
in der objektorientierten Programmierung auch als Get-/Set-Methoden bezeichnet. In C#-Code findet man sie allerdings eher
selten, denn für diese Aufgabe gibt es in C# einen speziellen Member-Typ: die Eigenschaft (siehe Kapitel 10).
Kapselung
Ein wichtiger Grundsatz objektorientierter Programmierung ist, nur die Member als public zu deklarieren,
die für die Arbeit mit den Objekten nötig sind. Member, die lediglich der internen Implementierung
dienen, sollten hingegen private sein. Auf diese Weise wird die Klasse zu einer Black Box, vergleichbar
einem elektronischen Gerät, beispielsweise einem DVD-Player. Nach außen stellt der DVD-Player dem
Benutzer alle Bedienelemente und Anschlüsse zur Verfügung, die für die Verwendung des DVD-Players
benötigt werden (Benutzerschnittstelle). Was der DVD-Player sonst noch an Elementen enthält und was
sich hinter den Bedienelementen tatsächlich verbirgt, versteckt der DVD-Player in seinem Gehäuse (Implementierung).
Die Trennung in öffentliche Schnittstelle und private Implementierung wird in der objektorientierten
Programmierung als Kapselung bezeichnet.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
118
Kapitel 5: Anatomie eines C#-Programms
Konzepte, die auf der Klasse aufbauen
Die Klasse ist nicht nur Gegenstand wichtiger objektorientierter Konzepte und Ideen, sie bildet gleichzeitig
auch die Basis und Grundlage für Techniken wie:
Modularität
Vererbung
Polymorphie
Kapselung
Die Klasse verbirgt die Interna ihrer
Implementierung vor dem Benutzer.
Trennung von Schnittstelle und
Implementierung.
Vererbung
Polymorphie
Klasse
Abgeschlossenheit
Sicherheit
Die Klasse vereint in sich funktionell
zusammen gehörende Daten und
Methoden.
Die Klasse schützt sich selbst vor
unsachgemäßem Gebrauch.
(Versch. Techniken: Konstruktor,
Zugriffsmodifizierer, public-Methoden
vermitteln Zugriff auf private Felder.)
Modularität
Abbildung 5.4 OOP-Konzepte, die mit der Implementierung von Klassen verbunden sind bzw. auf dem Klassenkonzept basieren
Modularität
Größere Software-Projekte lassen sich nur durch Aufteilung des Codes in einzelne Module bewältigen. Die
Module können dann getrennt voneinander entwickelt und getestet und anschließend zum fertigen Programm zusammengesetzt zu werden. Die objektorientierte Programmierung fördert durch die Aufteilung
des Codes in Klassendefinitionen automatisch die Modularisierung.
Klassen sind in sich abgeschlossene Module, die gut zu warten und leicht wieder zu verwenden sind.
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
119
Objektorientierte Programmierung in C#
Vererbung
Klassen haben noch einen weiteren Vorzug, der ihre Wiederverwertbarkeit betrifft. Sie sind vererbbar, das
heißt, eine Klasse kann von einer anderen Klasse deren Felder und Methoden übernehmen. Auf diese
Weise können ganze Klassenhierarchien entstehen – etwa eine Formen-Hierarchie für ein Grafikprogramm.
Am Beginn der Hierarchie könnte eine Basisklasse Shape stehen, die zwei Member definiert, über die später
alle Formobjekte verfügen sollen: ein Feld rp, das einen Referenzpunkt zum Positionieren definiert, und
eine Methode Move() zum Verschieben der Form. Von Shape werden dann Klassen für die einzelnen Formen
abgeleitet: Line, Polygon und Circle. Diese Klassen erben die Funktionalität von Shape und definieren zusätzlich eigene Member, allen voran Felder zum Speichern der formspezifischen Daten wie z.B. Anfang- und
Endpunkt für Line oder Mittelpunkt und Radius für Circle. Von den abgeleiteten Klassen können wiederum Klassen abgeleitet werden. Beispielsweise bietet es sich an, von der Klasse Polygon zwei Klassen Rectangle
und Square abzuleiten, die auf diese Weise die gesamte Polygon-Funktionalität inklusive der Shape-Member
erben.
Shape
Line
Polygon
Rectangle
Circle
Square
Abbildung 5.5 Aufbau einer Klassenhierarchie
Eine solche Hierarchie ist klar strukturiert und spart durch die Vererbung viel unnötigen Programmieraufwand. Umgesetzt in C#-Code würde sie etwa wie folgt aussehen:
using System;
// Basisklasse
class Shape
{
// Referenzpunkt zum Verschieben und Positionieren der Form
protected Point rp = new Point(); // Point sei an anderer Stelle definiert
// Konstruktor
public Shape(Point p)
{
rp = p;
}
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
120
Kapitel 5: Anatomie eines C#-Programms
// Methode zum Verschieben, wird von allen abgeleiteten Klassen genutzt
public void Move(int dx, int dy)
{
rp.x += dx;
rp.y += dy;
}
}
// Abgeleitete Klassen
class Line : Shape
{
protected Point start;
protected Point end;
// Der Line-Konstruktor ruft über base den Konstruktor der Basisklasse auf,
// um den Referenzpunkt zu setzen.
public Line(Point s, Point e) : base(s)
{
start = s;
end = e;
}
}
class Circle : Shape
{
protected Point center;
protected int radius;
// Der Circle-Konstruktor ruft über base den Konstruktor der Basisklasse auf,
// um den Referenzpunkt zu setzen.
public Circle(Point p, int r) : base(p)
{
center = p;
radius = r;
}
}
class Polygon : Shape
{
protected Point[] nodes;
// Der Polygon-Konstruktor ruft über base den Konstruktor der Basisklasse auf,
// um den Referenzpunkt zu setzen.
public Polygon(Point[] nodes) : base(nodes[0])
{
this.nodes = nodes;
}
}
class Rectangle
{
// Benötigt
// verpackt
// übergibt
: Polygon
keine eigenen Felder für die Form-Daten. Der Rectangle-Konstruktor
einfach die Point-Objekte für die vier Eckpunkte in einem Array und
dieses an den Basisklassenkonstruktor, der die Punkte im
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Objektorientierte Programmierung in C#
121
// geerbten nodes-Feld speichert und den Referenzpunkt setzt
public Rectangle(Point lo, Point lu, Point ro, Point ru)
: base(new Point[4] {lo, lu, ro, ru})
{
}
}
class Square : Polygon
{
// Für Erläuterung siehe den Konstruktor der Klasse Rectangle
public Square(Point lo, int width)
: base(new Point[4] {lo,
new Point(lo.x, lo.y - width),
new Point(lo.x + width, lo.y),
new Point(lo.x + width, lo.y - width)})
{
}
}
Der Code, insbesondere die Implementierung der Konstruktoren, ist schon recht anspruchsvoll und für
OOP-Einsteiger sicher schwer zu verstehen. Was aber auch ohne Vorkenntnisse in objektorientierter
Programmierung deutlich ins Auge fällt, ist, dass die Klasse Square außer einem Konstruktor keine eigenen
Member definiert!
Dies liegt natürlich daran, dass die Klasse alle benötigten Member von ihren Basisklassen Polygon und Shape
erbt. Ein zugegebenermaßen extremer Fall von Code-Wiederverwertung durch Vererbung, aber nicht
unbedingt außergewöhnlich.
HINWEIS
Konstruktoren werden nicht vererbt.
Wie kann mit einem Square-Objekt programmiert werden?
Der Square-Konstruktor erwartet als Argumente ein Point-Objekt, das die linke obere Ecke bezeichnet und
eine Seitenlänge. Die Instanziierung eines Square-Objekts könnte demnach wie folgt aussehen:
Point p = new Point(-10, 10);
Square square = new Square(p, 20);
Der Square-Konstruktor berechnet aus den Koordinaten der linken oberen Ecke und der Seitenlänge die
Point-Objekte für alle vier Ecken und übergibt diese als Array an den Konstruktor der Basisklasse Polygon.
Dieser speichert das Array mit den Point-Objekten im geerbten Feld square.nodes und reicht das erste PointObjekt an den Konstruktor von Shape weiter, der das Objekt im geerbten Feld square.rp speichert.
Der Programmierer, der Square instanziiert, kann selbst allerdings nicht auf square.nodes oder square.rp
zugreifen, da diese in ihren Klassen als protected deklariert sind. Der Zugriffsmodifizierer protected erlaubt
den Zugriff nur von innerhalb der eigenen oder abgeleiteten Klassen aus. Allerdings gibt es auch einen
öffentlichen vererbten Member: die Methode Move(). Dieses kann für jedes Objekt einer Klasse aus der
Shape-Hierarchie aufgerufen werden:
square.Move(-5, 20);
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
122
Kapitel 5: Anatomie eines C#-Programms
Danach speichert der Referenzpunkt des square-Objekts die Koordinaten (–15, 30).
HINWEIS
Klassen, die ihre Funktionalität vererben, werden als Basisklassen oder Super-Klassen bezeichnet. Klassen, die
von einer bestehenden Klasse erben, nennt man abgeleitete Klassen oder Sub-Klassen.
In C# gehen alle Klassen automatisch auf die oberste Basisklasse Object zurück, von der sie eine gewisse Grundfunktionalität
erben.
Polymorphie
Abgeleitete Klassen zeichnen sich in der Regel nicht nur durch hinzukommende Merkmale und Verhaltensweisen aus. Es ist auch möglich, dass sie Verhaltensweisen (sprich Methoden) höherer Klassen erben
und modifizieren.
So müssen beispielsweise alle Formen aus der im vorigen Abschnitt beschriebenen Shape-Hierarchie auch
gezeichnet werden. Es liegt also nahe, eine entsprechende Methode Draw() in Shape zu definieren und an die
abgeleiteten Klassen zu vererben.
Ein Shape-Objekt zu zeichnen, erfordert aber ganz anderen Code als das Zeichen einer Linie oder eines
Kreises. Die objektorientierte Programmierung erlaubt daher in abgeleiteten Klassen das geerbte Verhalten
(sprich die Methode) durch Überschreibung anzupassen. Das folgende Code-Fragment zeigt dies exemplarisch an der abgeleiteten Klasse Line:
class Shape
{
// wie oben
// Methode zum Zeichen, wird von abgeleiteten Klassen überschrieben
public virtual void Draw()
{
Console.WriteLine(" Form ({0},{1}) ", rp.x, rp.y);
}
}
class Line : Shape
{
protected Point start;
protected Point end;
public Line(Point s, Point e) : base(s)
{
start = s;
end = e;
}
// Überschreibt die geerbte Draw()-Methode
public override void Draw()
{
Console.WriteLine(" Linie ({0},{1}) ", rp.x, rp.y);
Console.WriteLine(" ({0},{1}) bis ({2},{3}) ", start.x, start.y, end.x, end.y);
}
}
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Objektorientierte Programmierung in C#
123
Die Draw()-Methode der Shape-Hierarchie wird damit zur polymorphen Methode: sie kann für alle Objekte
der Klassenhierarchie aufgerufen werden und zeichnet jedes Objekt so, wie es seinem Typ gemäß ist.
Shape shape = new Shape(p1);
shape.Draw();
Line line = new Line(p1, p4);
line.Draw();
// ...
HINWEIS
Richtig interessant wird die Polymorphie allerdings erst dann, wenn Sie Objekte abgeleiteter Klassen über
Basisklassenvariablen ansprechen (siehe Kapitel 12).
Die Klasse als Funktionensammlung
Nicht jede Klasse dient dazu, eine bestimmte Art von Objekten zu beschreiben. Manche Klassen sind nichts
anderes als Sammlungen von unabhängigen Methoden, Feldern und Konstanten zu einem bestimmten
Themenbereich. In solchen Fällen werden die Member der Klasse als static deklariert. Statische Member
existieren nur als Elemente ihrer Klasse. Der Zugriff erfolgt daher nicht über Objekte der Klasse, sondern
ausschließlich über den Namen der Klasse. Klassen, die ausschließlich statische Member enthalten, werden
sogar meist so implementiert, dass eine Instanzbildung gar nicht mehr möglich ist (durch Implementierung eines privaten Konstruktors; siehe in Kapitel 10 den Abschnitt »Konstruktoren«.).
Prominentes Beispiel für eine Klasse mit ausschließlich statischen Member ist System.Math. Die in Math
definierten statischen Methoden implementieren häufig benötigte mathematische Funktionen wie Sinus,
Kosinus, Betrag, Logarithmus und so weiter.
double angle;
double sinus;
Console.Write(" Geben Sie einen Winkel im Bogenmaß ein: ");
angle = Convert.ToDouble(Console.ReadLine());
// Berechnung des Sinus mit der statischen Math-Methode Sin()
sinus = Math.Sin(angle);
Dirk Louis, Shinja Strasser; Klaus Löffelmann: Microsoft Visual C# 2005 - Das Entwicklerbuch. Microsoft Press 2006 (ISBN 978-3-86063-543-8)
Herunterladen