Programmieren in C - Hochschule Esslingen

Transcrição

Programmieren in C - Hochschule Esslingen
Programmieren in C#
Prof. Dr.-Ing. Harald Melcher
Hochschule Esslingen - University of Applied Sciences
Dieses Skript “Programmieren in C# ” darf in seiner Gesamtheit nur zum privaten Studiengebrauch benützt werden. Das Skript ist in seiner Gesamtheit urheberrechtlich geschützt. Folglich
sind Vervielfältigungen, Übersetzungen, Mikroverfilmungen, Scan-Vervielfältigungen, Verbreitung
sowie die Einspeicherung und Verarbeitung in elektronischen Systemen unzulässig. Ein darüber
hinausgehender Gebrauch ist zivil- und strafrechtlich unzulässig.
3
Inhaltsverzeichnis
1 Bezeichner, Typen und Zugriffsrechte
1.1 Wertetypen . . . . . . . . . . . . .
1.2 Referenztypen . . . . . . . . . . . .
1.3 Einpacken in Objekte . . . . . . . .
1.4 Variablen-Initialisierung . . . . . . .
1.5 Zugriff und Haltbarkeit . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
7
7
7
8
2 Kontrollstrukturen
10
2.1 Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2 Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3 Klassen
3.1 Konstruktor . .
3.2 Destruktor . . .
3.3 Versiegelt . . .
3.4 Partielle Klassen
3.5 Vererbung . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
16
16
20
22
23
23
4 Methoden und Eigenschaften
4.1 Methoden-Parameter . . . . . . . . .
4.2 Methoden mit variabler Parameterzahl
4.3 Überschreiben von Methoden . . . . .
4.4 Operator-Überschreiben . . . . . . . .
4.5 Eigenschaften (Properties) . . . . . .
4.6 Indexer . . . . . . . . . . . . . . . . .
4.7 Signatur . . . . . . . . . . . . . . . .
4.8 Generische Methoden . . . . . . . . .
4.9 die Main-Methode . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
27
27
28
29
31
31
32
34
35
35
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5 Interfaces
5.1 IEnumerable . .
5.2 IEnumerator . .
5.3 IConvertible . .
5.4 IComparable . .
5.5 ICloneable . . .
5.6 IFormatable . .
5.7 IEnumerable<T>
5.8 IList<T> . . . .
5.9 ICollection<T>
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
37
37
37
38
38
38
38
39
40
40
6 Strings
6.1 Escape-Character
6.2 Interfaces . . . .
6.3 String-Erzeugung
6.4 String-Methoden .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
41
42
42
43
Inhaltsverzeichnis
4
6.5
6.6
6.7
Format-Angaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Effiziente Stringbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7 Collections
7.1 List<T> . . . . . . . . . . . . . .
7.2 LinkedList<T> . . . . . . . . . .
7.3 Queue<T> . . . . . . . . . . . . .
7.4 Stack<T> . . . . . . . . . . . . .
7.5 Dictionary<TKey,TValue> . . .
7.6 SortedList . . . . . . . . . . . .
7.7 foreach über eigene Sammlungen
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
48
50
53
55
55
58
60
62
63
63
63
8 Delegates, Events und Exceptions
65
8.1 Delegates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
8.2 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
8.3 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
9 Threads
74
9.1 Threads erzeugen und verwalten . . . . . . . . . . . . . . . . . . . . . . . . . . 74
9.2 Prioritäten von Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
9.3 Synchronisation von Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
10 Dateien
79
10.1 Die Laufwerke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
10.2 Verzeichnisse und Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
10.3 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
11 Forms und Controls
11.1 Label . . . . . . . . . . . . . . . . . . . .
11.2 MessageBox . . . . . . . . . . . . . . . . .
11.3 Button . . . . . . . . . . . . . . . . . . . .
11.4 CheckBox . . . . . . . . . . . . . . . . . .
11.5 RadioButton . . . . . . . . . . . . . . . .
11.6 ComboBox, ListBox und CheckedListBox
11.7 ListView . . . . . . . . . . . . . . . . . .
11.8 TextBox . . . . . . . . . . . . . . . . . . .
11.9 RichTextBox . . . . . . . . . . . . . . . .
11.10ProgressBar . . . . . . . . . . . . . . . .
11.11StatusStrip . . . . . . . . . . . . . . . .
11.12ContextMenuStrip . . . . . . . . . . . . .
11.13OpenFileDialog . . . . . . . . . . . . . .
11.14Eigenständige Forms . . . . . . . . . . . . .
11.15Panels . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
85
85
85
86
87
88
89
91
93
98
99
99
100
100
101
102
12 Tipps und Tricks
12.1 Dokumentierende Kommentare
12.2 Laufzeitmessung . . . . . . . .
12.3 Zufallszahlen . . . . . . . . . .
12.4 Besondere Verzeichnisse . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
103
103
106
106
106
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
1 Bezeichner, Typen und Zugriffsrechte
C# unterscheidet zwischen Wertetypen (value types) und Referenztypen (reference types). Das
Laufzeitsystem legt Wertetypen auf dem Stack ab, was sehr schnell geht. Referenztypen legt es
auf dem Heap ab und der Stack enthält nur die Referenz auf das Objekt. Das braucht mehr Zeit,
weil das Laufzeitsystem erst den Platz auf dem Heap aussuchen und zuweisen muss. Wertetypen
innerhalb von Referenztypen, also z.B. Werte in Objekten, liegen innerhalb des Objektes auf
dem Heap.
1.1 Wertetypen
C# hat eine Reihe von Wertetypen, die aus andern Programmiersprachen wie C++ oder Java
bekannt sind. Es gibt vorzeichenbehaftete und vorzeichenlose Varianten von byte, int und
long, sowie Gleitkommazahlen einfacher (float) und doppelter (double) Genauigkeit und einen
eigenen Zahlentyp für Dezimalzahlen (decimal). Weil C# mit anderen Programmiersprachen
zusammenarbeiten soll, die der .NET Common Language Specification folgen, sind für C# viel
genauere Angaben zu Wertebereichen der eingebauten Typen nötig, als sie bei C oder C++ nötig
waren. So ist in C++ der long-Typ meist 4 Byte lang, in C# ist er immer 8 Byte lang. Auch
gibt es keine Abbildung vom Typ bool auf Integerwerte mehr – boolsche Variable sind in C# ein
eigenständiger Datentyp. Speziell lassen sie sich nicht, wie in C++, mit Integertypen mischen.
Das vermeidet aber netterweise einen der häufigsten Programmierfehler von C oder C++, das
Verwechseln von = mit ==:
bool ist
nicht int
if ( i = 5) printf ( " i ist fuenf " ) ;
Spezielle Wertetypen sind struct und enum.
Bezeichnung
sbyte
byte
short
ushort
int
uint
long
ulong
float
double
decimal
char
bool
enum
struct
Anzahl Bytes
1
1
2
2
4
4
8
8
4
8
12
2
1
?
?
Beschreibung
kleine Zahl mit Vorzeichen
kleine Zahl ohne Vorzeichen
kurze Zahl mit Vorzeichen
kurze Zahl ohne Vorzeichen
normale Zahl mit Vorzeichen
normale Zahl ohne Vorzeichen
lange Zahl mit Vorzeichen
lange Zahl ohne Vorzeichen
Gleitkommazahl
doppelt genaue Gleitkommazahl
Dezimalzahl
Zeichen in Unicode
Wahrheitswert
Aufzählung
Zusammengesetzter Typ
Tabelle 1.1: Wertetypen
Wertebereich
−128 . . . + 127
0 . . . 255
−32.768 . . . + 32.767
0 . . . + 65.536
−231 . . . + 231 − 1
0 . . . 232
63
−2 . . . + 263 − 1
0 . . . 264
bis zu 28 Stellen
Textzeichen
true oder false
1 Bezeichner, Typen und Zugriffsrechte
6
1.1.1 struct
Structs sind
Wertetypen Ein Struct in C# ist ein benutzerdefinierter Wertetyp. Es unterstützt Properties, Methoden, Fields
und Operatoren, hingegen keine Vererbung oder Destruktoren. Durch die Ablage auf dem Stack
(als Wertetyp) kann das Laufzeitsystem Structs schnell erzeugen und auf sie zugreifen. Bei der
Verwendung in untypisierten Collections sind Structs allerdings langsamer als Klassen mit gleichen
Eigenschaften, weil das Laufzeitsystem sie erst in Objekte einpacken (boxen) muss.
1.1.2 enum
Enum ist ein benutzerdefinierter Aufzählungstyp. Intern verwendet C# zur Darstellung einen Integertyp (byte, int oder long), je nach Anzahl der angebenen Elemente. Standardtyp ist int.
using System ;
class Program
{
public enum getraenk : byte
{
Bier ,
// Automatisch 0
Wein ,
// Automatisch 1
Milch ,
// Automatisch 2
Met = 66 ,
// Erzwungen 66
Absynth
// Automatisch 67
}
static void Main ( string [] args )
{
getraenk g = getraenk . Bier ;
Console . WriteLine ( g ) ;
}
}
Die abstrakte Basisklasse von Enum ist System.Enum, so dass C# eine Reihe von Methoden
bereitstellen kann, falls das Programm einen Enum-Wert wie ein Objekt anspricht. Speziell interessant sind die Funktionen GetNames() und GetValues(), die Arrays mit den Namen oder
Werten eines Enum-Typs zurückgeben.
using System ;
class Program
{
public enum getraenk : byte
{
Bier ,
// Automatisch 0
Wein ,
// Automatisch 1
Milch ,
// Automatisch 2
Met = 66 ,
// Erzwungen 66
Absynth
// Automatisch 67
}
static void Main ( string [] args )
{
string [] gs = Enum . GetNames ( typeof ( getraenk ) ) ;
foreach ( String s in gs )
Console . WriteLine ( s ) ;
}}
1.2 Referenztypen
7
1.1.3 var
Var ist eine implizit definierte lokale Variable: Der C# -Compiler erkennt den Typ der Variablen
aus ihrem Initialwert. Daher braucht eine var-Variable immer gleich bei der Deklaration eine
Wertzuweisung. Dies ist vor allem bei den später vorkommenden anonymen Klassen praktisch.
var
var
var
var
index = 5;
fach = " Programmieren in C # " ;
schiffe = new List < Schiff >;
person = new { Vorname = " Martin " , Nachname = " Maier " , MatrNr
=732388};
1.2 Referenztypen
Alle Klassen sind Referenztypen. Das Laufzeitsystem legt sie als Objekt auf dem Heap an und die
dazugehörige Referenz, die auf das Objekt zeigt, auf dem Stack. Bei Methodenaufrufen übergibt
das Laufzeitsystem die Referenz auf das Objekt als Kopie – das Objekt selber bleibt nur einfach
auf dem Heap. Das bedeutet, dass Manipulationen am Objekt innerhalb einer Methode den
Inhalt des Original-Objekts verändern. Falls das unerwünscht ist, liefert der Copy-Konstruktor
eine Kopie des Objekts mit einstellbarer Tiefe.
1.3 Einpacken in Objekte
Jeder Typ in C# leitet sich von Object ab und so wandelt das Laufzeitsystem Wertetypen automatisch in Objekte um, wenn das Programm deren Objekteigenschaften anspricht. Diesen
Vorgang des Einpackens nennt man boxing. Boxing erzeugt ein Objekt auf dem Heap, das den
Wertetyp zum Inhalt hat und legt eine Referenz darauf auf den Stack.
Das folgende Programm packt den Wert von i automatisch in ein Objekt ein, um dessen
ToString()-Methode aufrufen zu können.
class Program
{
static void Main ( string [] args )
{
String s = " Ein String " ;
int i = 42;
System . Console . WriteLine ( s . ToString () ) ;
System . Console . WriteLine ( i . ToString () ) ;
}
}
Dieser Vorgang kostet Zeit und bringt daher Geschwindigkeitsverluste zur Laufzeit. Um eine
schnelle Programmausführung zu gewährleisten, sollte die Programmiererin darauf achten, dass
das Laufzeitsystem nicht Wertetypen einpacken muss, um sie z.B. in Collections unterzubringen.
Das Auspacken eines Werts aus einem Objekt erfolgt nicht automatisch, sondern das Programm
muss gezielt nachsehen, ob das Objekt den richtigen Typ eingepackt hat und den Wert aus dem
Objekt dann in eine Werte-Variable kopieren. Falls das Objekt null ist oder den falschen Wertetyp
kapselt, dann wirft das Laufzeitsystem eine Exception, die invalidCastException.
1.4 Variablen-Initialisierung
Alle Variablen, die C# verwendet, müssen bei der ersten lesenden Verwendung einen zugewiesenen
Wert haben. Die Deklararation einer Variablen muss ihr noch keinen Wert zuweisen, aber zur
1 Bezeichner, Typen und Zugriffsrechte
8
ersten Verwendung muss die Variable einen Wert haben, das stellt der Compiler sicher:
ˆ Falls die Programmiererin Variable innerhalb einer Klasse oder eines Structs nicht bei der
Deklaration explizit initialisiert hat, setzt der Compiler sie auf Null.
ˆ Variable, die lokal in einer Methode sind, muss die Programmiererin vor Gebrauch durch
das Programm initialisieren, entweder durch eine explizite Zuweisung bei der Deklaration
oder durch eine Zuweisung zur Laufzeit. Dazu prüft der Compiler alle möglichen Codewege
ab und meldet als Fehler, wenn er einen Weg findet, auf dem die Variable nicht initialisiert
wird.
Variablen-Initialisierung erfolgt entweder bei der Deklaration einer Klasse für die einzelnen Variablen oder innerhalb eines Konstruktors.
Falls Variable im Programm als Referenz-Parameter einer Methode gedacht sind, die einen
Wert zurückliefern, so gibt es dazu in C# den out - Parametertyp einer Methode. Der C# Compiler weiß dann, dass er im aufrufenden Programmteil nicht auf die Initialisierung der Variablen achten muss, da sie innerhalb der Methode als “ohne Wert” bekannt ist.
int a ;
static int zwei = 2;
static double r = 4.5;
double umfang = zwei * Math . PI * r ;
double flaeche = Math . PI * Math . Pow (r , 2.0) ;
1.5 Zugriff und Haltbarkeit
Variable und
Methoden
ohne weitere
Angabe sind
privat
1.5.1 Privat
Alle Variablen und Methoden in C# sind, wenn nicht anders angegeben, privat. Dann können
nur Methoden aus der Klasse, in der eine Variable definiert ist, auf die Variable zugreifen und
nur Methoden von innnerhalb der Klasse deren private Methoden aufrufen. Zugriff auf die Variablen kann dennoch erfolgen über Properties. Dies erlaubt kontrollierteren Zugriff auf Variable und dient dem Information-Hiding, das heißt dem Verstecken der Vorgänge im Innern. So
lässt die Trennung zwischen Zugriff auf Variable und deren interner Repräsentation bei der Programmierung Platz für Flexibilität. Private Variable und Methoden können zur Klarstellung den
Zugriffs-Modifikator private tragen.
1.5.2 Öffentlich
Alle Methoden und Variable, die von außerhalb sichtbar und zugreifbar sein sollen, sind public.
Nur die wirklich nach außen benötigten Methoden öffentlich zu machen, ist eine Methode der
Verringerung der Komplexität und lässt der Programmiererin mehr Freiheiten bei der ImplemenVon
Inter- tation der Klasse. Speziell Methoden, die von einem Interface herrühren, müssen öffentlich sein,
faces geerbte denn die Implementierung eines Interfaces ist ein “Versprechen” der implementierenden Klasse,
Methoddie Methoden des Interfaces zu anzubieten.
en
müssen
öffentlich sein 1.5.3 Geschützt
Auf protected deklarierte Variable und Methoden können Methoden der eigenen Klasse und
die davor abgeleiteten Klassen zugreifen. protected erweitert damit private dahin, dass auch
Methoden der abgeleiteten Klassen auf die Variablen oder Methoden zugreifen dürfen.
1.5 Zugriff und Haltbarkeit
9
1.5.4 Intern
Für Java- oder C++-Programmiererinnen neu ist der Zugriffsmodifikator internal. Bei internal dürfen andere Klassen auf die internal deklarierten Variablen und Methoden zugreifen als
wären sie public. Aber nur, solange die andern Klassen im gleichen Assembly liegen, wie die
Klasse, auf die sie zugreifen.
1.5.5 Statisch
Statische Variable sind einmal pro Klasse vorhanden – alle Instanzen teilen sich ein und dieselbe
Variable. Das spart zum einen Speicherplatz, zum andern erlauben sie aber auch die Kommunikation zwischen verschiedenen Instanzen einer Klasse. Eine typische Anwendung ist das Mitzählen,
wieviel Instanzen einer Klasse es gerade gibt.
class Program
{
static int instanceCounter ;
public Program ()
{
instanceCounter ++;
}
static void Main ( string [] args )
{
Program p ;
p = new Program () ;
p = new Program () ;
Console . WriteLine ( instanceCounter ) ;
}
}
Näheres zum statischen Konstuktor findet sich unter 3.1.2 auf Seite 18
10
2 Kontrollstrukturen
2.1 Bedingungen
2.1.1 If
Wie Java oder C++ kennt C# auch das if-Statement und kennzeichnet den alternativen Ausführungspfad mit else:
using System ;
class Program
{
static void Main ( string [] args )
{
int i = 4;
if ( i > 3)
Console . WriteLine ( " i ist größer als 3 " ) ;
else
Console . WriteLine ( " i ist kleiner / gleich 3 " ) ;
}
}
Mehrere Anweisungen als Folge einer Bedingung fasst C# mit geschweiften Klammern in einen
Block zusammen:
using System ;
class Program
{
static void Main ( string [] args )
{
int i = 4;
if ( i > 3)
{
Console . WriteLine ( " i ist größer als 3 " ) ;
i -= 1;
}
else
{
Console . WriteLine ( " i ist kleiner / gleich 3 " ) ;
i += 1;
}
}
}
Anders als C lässt C# innerhalb der Bedingung einer if-Anweisung nur einen boolschen Wert zu.
Das schließt einen der häufigsten C-Programmierfehler aus:
if ( i = 3)
printf ( " i ist 3 " ) ;
2.1 Bedingungen
11
2.1.2 Switch
Das switch-Statement eignet sich besonders gut für die Abfrage einer Reihe sich gegenseitig
ausschließender Bedingungen. Das Argument der switch-Anweisung gibt an, welchen Ausdruck
die darauf folgenden case-Anweisungen prüfen sollen. Ein abschließendes default-Statement
fängt alle Fälle ein, die die case-Anweisungen nicht abdecken. Anders als in C++ braucht jeder Jede casecase-Ast eines switch-Statements ein abschließendes break!
Bedingung
bei switch
using System ;
braucht ein
break
class Program
{
static void Main ( string [] args )
{
int i ;
i = Convert . ToInt16 ( Console . ReadLine () ) ;
switch ( i )
{
case 0: Console . WriteLine ( " Nix " ) ;
break ;
case 1: Console . WriteLine ( " Eins " ) ;
break ;
case 2: Console . WriteLine ( " Zwei " ) ;
break ;
case 3: Console . WriteLine ( " Drei " ) ;
break ;
default : Console . WriteLine ( " Viele " ) ;
break ;
}
}
}
Nur wenn ein Ast eines Switch-Statements keinen Code erzeugt, dann ist Durchfallen zum nächsten Ast möglich, um völlig gleichartige Fälle zusammenzufassen:
switch ( i )
{
case 0: Console . WriteLine ( " Nix " ) ;
break ;
case 1:
case 2:
case 3:
Console . WriteLine ( " Wenige " ) ;
break ;
default : Console . WriteLine ( " Viele " ) ;
break ;
}
Das“Springen”zu einem andern Ast eines switch-Statements bewirkt in C# die goto-Anweisung,
die jedoch nicht sonderlich zur Lesbarkeit des Codes beiträgt:
using System ;
class Program
{
static void Main ( string [] args )
{
string f = Console . ReadLine () ;
2 Kontrollstrukturen
12
switch ( f )
{
case " Fahrrad " :
Console . WriteLine ( " Ein Rad " ) ;
goto case " Einrad " ;
case " Einrad " :
Console . WriteLine ( " ... und noch ein Rad " ) ;
break ;
case " Auto " :
Console . WriteLine ( " Drei Räder " ) ;
goto case " Einrad " ;
default :
Console . WriteLine ( " Unbekanntes Fahrzeug : " + f ) ;
break ;
}
}
}
2.2 Schleifen
2.2.1 for
Die for-Schleife bietet die Möglichkeit, einen Block mehrfach zu durchlaufen und vor jedem
Durchlauf zu prüfen, ob eine bestimmte Bedingung noch erfüllt ist. Sie hat folgenden Aufbau:
for ( Initialisierung ; Abbruch - Bedingung ; Iterator }
Statement ( s )
mit
Initialisierung Diesen Code führt das Laufzeitsystem aus, bevor es das erste Mal die Schleife
durchläuft. Oft initialisiert dieser Code die Schleifenvariable, wobei C# hier die Deklaration
einer Schleifenvariablen zulässt.
Abbruch-Bedingung Das Erfüllen dieser Bedingung entscheidet über einen (weiteren) Durchlauf der Schleife. Falls die Bedingung zu Anfang nicht erfüllt ist, findet überhaupt kein
Schleifendurchlauf statt. Die Bedingung muss einen booleschen Wert ergeben.
Iterator Das Laufzeitsystem führt den Iterator nach jedem Schleifendurchlauf aus. Das Ergebnis geht nicht in die Entscheidung über einen weiteren Schleifendurchlauf ein. Jedoch
findet direkt nach Ausführen des Iterators das Abprüfen der Bedingung statt, so dass sie
Entscheidung direkt nach dem Iterator fällt. Oft besteht der Iterator aus dem Hoch- oder
Runterzählen des Schleifenzählers.
Das folgende Programm
1. deklariert die Schleifenvariable i im Initialisierer
2. prüft dann die Abbruch-Bedingung
3. führt die Schleife aus
4. führt den Iterator aus
5. macht beim Prüfen der Abbruch-Bedingung (Punkt 2) weiter
13
2.2 Schleifen
using System ;
class Program
{
static void Main ( string [] args )
{
for ( int i = 1; i < 100000; i *= 2)
Console . WriteLine ( i ) ;
}
}
Recht häufig tregen mehrfach verschachtelte Schleifen auf. Typischerweise treten doppelt
verschachtelte bei 2-dimensionalen Aufgabenstellungen und dreifach verschachtelte bei 3dimensionalen auf. Die “innere” Schleife läuft dabei am schnellsten durch und die äußeren immer
langsamer, je weiter außen sie liegen. Das automatische Einrücken des Text-Editors hilft dabei,
die Übersicht zu behalten:
using System ;
class Program
{
static void Main ( string [] args )
{
int x , y ;
for ( y = 1; y <= 10; y ++)
{
for ( x = 1; x <= 10; x ++)
Console . Write ( " {0 ,3} " , x * y ) ;
Console . WriteLine () ;
}
}
}
ergibt die Tabelle mit dem kleinen 1x1:
1
2
3
4
5
6
7
8
9
10
2
4
6
8
10
12
14
16
18
20
3
6
9
12
15
18
21
24
27
30
4
8
12
16
20
24
28
32
36
40
5
10
15
20
25
30
35
40
45
50
6
12
18
24
30
36
42
48
54
60
7
14
21
28
35
42
49
56
63
70
8
16
24
32
40
48
56
64
72
80
9 10
18 20
27 30
36 40
45 50
54 60
63 70
72 80
81 90
90 100
2.2.2 while
Die while-Schleife von C# funktioniert genau so wie die in C++ oder Java: Das Laufzeitsystem
führt ein Statement oder einen Block so lange aus, wie die Schleifenbedingung erfüllt ist. Der
Test auf Erfüllung der Schleifenbedingung erfolgt vor jedem Schleifendurchlauf, also auch vor
dem ersten. Bei Nichterfüllen der Bedingung führt das Laufzeitsystem das Statement oder den
Block überhaupt nicht aus.
using System ;
class Program
{
2 Kontrollstrukturen
14
static void Main ( string [] args )
{
int f1 =1 , f2 =1;
while ( f2 < 100000)
{
int nextGen = f1 + f2 ;
f1 = f2 ;
f2 = nextGen ;
Console . WriteLine ( nextGen ) ;
}
}
}
Dieses Programm erzeugt eine uns wohlbekannte Zahlenfolge:
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
2.2.3 do...while
Die do...while-Schleife ist eine Variante der while-Schleife, nur mit dem Test nach dem Anweisungsteil. Das bedeutet auch, dass das Laufzeitsystem den Anweisungsteil der do...whileSchleife mindestens einmal zu Beginn ausführt, selbst wenn schon zu Schleifenbeginn die Bedingung nicht erfüllt ist.
using System ;
class Program
{
static void Main ( string [] args )
{
double zahl =2 , f1 =1 , f2 , diff ;
do
{
// Heron - Verfahren
15
2.2 Schleifen
f2 = ( f1 + zahl / f1 ) / 2;
Console . WriteLine ( f2 ) ;
if ( f2 > f1 ) diff = f2 - f1 ;
else diff = f1 - f2 ;
f1 = f2 ;
}
while ( diff > 0.000000001) ;
}
}
Dieses Programm nähert eine häufig verwendete Zahl an:
1 ,5
1 ,41666666666667
1 ,41421568627451
1 ,41421356237469
1 ,41421356237309
2.2.4 foreach
Zusätzlich zu den normalen Schleifen-Konstruktionen wie for und while besitzt C# auch noch
die foreach-Schleife, die durch alle Elemente einer Sammlung (Collection) iteriert. Die Sammlung besteht aus einzelnen Elementen und die foreach-Schleife durchläuft alle Elemente in einer
Reihenfolge, die die Programmiererin der Sammlung festgelegt hat.
foreach ( ElementTyp element in Sammlung }
Statement ( s )
Der Wert des einzelnen Elements lässt sich dabei innerhalb der Statement-Blocks nicht ändern.
using System ;
class Program
{
static void Main ( string [] args )
{
String [] getraenke = { " Bier " , " Milch " , " Wasser " };
foreach ( String g in getraenke )
Console . WriteLine ( g ) ;
}
}
2.2.5 break
Das break-Statement kam bereits beim switch-Statement vor, wo es die Abarbeitung eines
Verzweigungs-Astes beendet. Bei Schleifen beendet break die Ausführung einer Schleife sofort und das Programm macht am ersten Statement nach der Schleife weiter. Das gilt für alle
Schleifen, also für for, while, do...while und foreach.
2.2.6 continue
Das continue-Statement beendet die Ausführung des aktuellen Schleifendurchlaufs sofort und
leitet den nächsten Schleifendurchlauf ein. Es tritt bei for, while, do...while und foreach
auf.
16
3 Klassen
Jede Klasse in C# erbt von der Object-Klasse und stellt damit auch deren öffentliche Methoden Equals, GetHashCode, GetType, ReferenceEquals und ToString zur Verfügung. Gerade
ToString ist sehr nützlich, weil es durch Aufruf dieser Methode zu jedem Wert in C# eine lesbare
Darstellung gibt. Beim Aufruf der ToString-Methode von Wertetypen boxt C# den Wertetyp,
um die ToString-Methode des kapselnden Objekts aufrufen zu können. Über Type GetType()
kann ein Programm zur Laufzeit eine Instanz von System.Type erhalten, die weitere Informationen über das Objekt liefert (Klassenhierarchie, Basistyp, Methoden, Properties, etc.).
Methode
bool Equals(object o)
bool Equals(object o,
object p)
void Finalize()
int GetHashCode()
Type GetType()
object MemberwiseClone()
bool ReferenceEquals(object o, object p)
string ToString()
Zugriff
public virtual
public static
protected virtual
public virtual
public
protected
Aufgabe
Testet 2 Objekte auf Gleichheit
Testet 2 Objekte auf Gleichheit
Die .NET-Version eines Destruktors
Liefert einen objekt-eindeutigen Wert
Liefert eine Instanz von System.Type
Erstellt eine flache Kopies
public static
2 Referenzen auf das selbe Objekt?
public virtual
die String-Darstellung des Objekts
Tabelle 3.1: Methoden von Object
3.1 Konstruktor
Konstruktoren sehen in C# aus wie in C++ oder Java. Der Konstruktor einer Klasse ist eine
Methode der Klasse, die den gleichen Namen hat wie die Klasse und keinen Rückgabewert.
Wie in C++ oder Java auch, braucht die Programmiererin keinen Konstruktor zu deklarieren.
In dem Fall erzeugt der Compiler einen argumentenlosen Default-Konstruktor. benötigt weniger
Konstruktoren als die anderen Programmiersprachen, weil der Compiler Klassenvariable zu Null
initialisiert.
Die Programmiererin kann Konstruktoren überladen, wie jede andere Klassenmethode auch.
Wenn jedoch ein Konstruktor mit Argumenten existiert, dann erzeugt der Compiler nicht mehr
automatisch eine vorzeichenlose Version davon.
using System ;
class Program
{
static void Main ( string [] args )
{
DemoClass d1 = new DemoClass () ;
DemoClass d2 = new DemoClass (17 , 4) ;
3.1 Konstruktor
17
}
}
public class DemoClass
{
public DemoClass ()
{
Console . WriteLine (
" Konstruktor ohne Argumente " ) ;
}
public DemoClass ( int x , int y )
{
Console . WriteLine (
" Konstruktor mit 2 Argumenten " ) ;
}
}
3.1.1 Objekt-Initialisierer
Neu in C# ist der Objekt-Initialisierer, der öffentliche Objekt-Variablen vorbesetzen kann, ohne
dass dafür ein Konstruktor vorhanden sein muss:
using System ;
class MainClass
{
public static void Main ( string [] args )
{
Student s = new Student {
Vorname = " Martin " ,
Nachname = " Maier " ,
MatrNummer = 7325633
};
}
}
public class Student {
public String Vorname ;
public String Nachname ;
public int MatrNummer ;
}
}
Nachteilig an dieser Variante ist, dass damit von andern Klassen aus Zugriff auf Member-Variable
möglich ist, was der losen Kopplung der objektorientierten Programmierung widerspricht, da
andere Klasse über das interne Funktionieren der aktuellen Klasse Bescheid wissen (müssen).
Richtig Sinn macht der Objekt-Initialisierer daher erst beim Zugriff über öffentliche Eigenschaften. Diese können dann gleich beim Erzeugen des Objekts eine Plausibilitätsprüfung
vornehmen:
using System ;
class MainClass
{
public static void Main ( string [] args )
{
Student s = new Student {
3 Klassen
18
Vorname = " Martin " ,
Nachname = " Maier " ,
MatrNummer = 7325633
};
Console . WriteLine ( s . ToString () ) ;
}
}
public class Student {
private String vorname ;
private String nachname ;
private int matrNummer ;
public String Vorname {
get { return vorname ; }
set { vorname = value ; }
}
public String Nachname {
get { return nachname ; }
set { nachname = value ; }
}
public int MatrNummer {
get { return matrNummer ; }
set { if ( matrNummer > 0)
matrNummer = value ; }
}
public override string ToString ()
{
return string . Format (
" [ Student : Vorname ={0} , Nachname ={1} , MatrNummer ={2}] " ,
Vorname , Nachname , MatrNummer ) ;
}
}
3.1.2 statischer Konstruktor
Das Laufzeitsystem führt den statischen Konstruktor einer Klasse genau einmal pro verwendeter
Klasse aus, bevor es anderen Code aus der Klasse ausführt.
Der Code im folgenden Beispiel
using System ;
class Program
{
static void Main ( string [] args )
{
DemoClass d ;
d = new DemoClass () ;
d = new DemoClass () ;
d = new DemoClass () ;
}
}
class DemoClass
{
3.1 Konstruktor
19
static int instanceCounter = 0;
static DemoClass ()
{
Console . WriteLine ( " Statischer Konstruktor " ) ;
}
public DemoClass ()
{
++ instanceCounter ;
Console . WriteLine ( " {0}: Default Konstruktor " , instanceCounter )
;
}
~ DemoClass ()
{
Console . WriteLine ( " {0}: Destructor " , instanceCounter ) ;
-- instanceCounter ;
}
}
ergibt folgende Ausgabe:
Statischer Konstruktor
1: Default Konstruktor
2: Default Konstruktor
3: Default Konstruktor
3: Destructor
2: Destructor
1: Destructor
3.1.3 Copy-Konstruktor
Im Gegensatz zur“flachen”Wertezuweisung bei Objektreferenzen, die nur die Referenz verdoppeln
und das Objekt einfach im Speicher liegen lassen, kann der Copy-Konstruktor eine “intelligente”
Kopie einstellbarer Tiefe vornehmen.
using System ;
class Program
{
static void Main ( string [] args )
{
DemoClass d , e , f ;
d = new DemoClass ( " Eins " ) ;
e = d;
f = new DemoClass ( d ) ;
d . Print ( " d " ) ;
e . Print ( " e " ) ;
f . Print ( " f " ) ;
d . SetLabel ( " Zwei " ) ;
d . Print ( " d " ) ;
e . Print ( " e " ) ;
f . Print ( " f " ) ;
}
}
class DemoClass
3 Klassen
20
{
String label ;
public DemoClass ( String label )
{
this . label = label ;
}
public DemoClass ( DemoClass d )
{
this . label = d . label ;
}
public void SetLabel ( String newLabel )
{
this . label = newLabel ;
}
public void Print ( String what )
{
Console . WriteLine ( " {0}: {1} " , what , label ) ;
}
}
Die Ausgabe zeigt den deutlichen Unterschied zwischen der reinen Zuweisung und der Verwendung des Copy-Konstruktors:
d:
e:
f:
d:
e:
f:
Eins
Eins
Eins
Zwei
Zwei
Eins
3.2 Destruktor
Das Laufzeitsystem ruft den Destruktur eines Objekts auf, wenn es das Objekt entsorgen will.
Das ist normalerweise nicht gleich der Fall, wenn das Objekt außer Sichtweite des Programms
geht, sondern wenn der Speicher knapp wird und das Laufzeitsystem den Garbage Collector
aufruft um nicht mehr referenzierte Objekte zu löschen.
3.2.1 Müllabfuhr
Einer der größten Unterschiede zu C++ ist die Art, wie das C# -Laufzeitsytem mit Klassen umgeht, die außer Scope, d.h. außer Sichtbarkeit geraten sind. Die C++-Programmiererin entlastet das
C# -Laufzeitsystem von den Sorgen über Speicherlecks und nicht freigegebenen Zeigern. Dafür
hat sie aber nun auch keine explizite Kontrolle darüber, wann genau Objekte zerstört werden. Es
gibt in C# nämlich keine expliziten Destruktor.
Wenn ein Objekt nun aber eine ungemanagte Ressource beinhaltet, muss es eine Methode geben,
die Ressource freizugeben, wenn das Objekt sie nicht mehr verwendet, damit andere User oder
Prozesse darauf zugreifen können. Eine implizite Verwaltung der Ressource ist in der FinalizeMethode einer Klasse möglich, die der Müllsammler aufruft, wenn er das Objekt zerstört. Diese
Methode heißt auch Finalizer. Ein selbstgeschriebener Finalizer soll nur ungemanagte Ressourcen
freigeben, um die andern Ressourcen kümmert sich das Laufzeitsystem. Wenn die Klasse keine
ungemanagten Ressourcen einsetzt, braucht die Programmiererin überhaupt keinen eigenen Finalizer zu schreiben. Ein Programm darf auch nie den Finalizer direkt aufrufen, sondern überlässt
3.2 Destruktor
21
diesen Aufruf dem Müllsammler. Innerhalb einer eventuell doch selbstgeschriebenen FinalizerMethode ist der Aufruf der Finalizer-Methode der Basisklasse jedoch erforderlich.
Identisch zum Schreiben einer Finalize-Methode ist die Verwendung des C# -Destruktors. Damit
sind folgende Code-Stücke äquivalent:
MyClass . Finalize ()
{
// ...
// Freigeben der unmanaged Ressources hier
// ...
// Aufrufen des Finalizers der Basis - Klasse
base . Finalize () ;
}
ist äquivalent zu:
~ MyClass ()
{
// ...
// Freigeben der unmanaged Ressources hier
// ...
// Aufruf des Basisklassen - Finalizer erledigt das Laufzeitsystem
}
Falls das Programm benötigte (meist externe) Ressourcen schon zur Laufzeit und ohne Warten
auf den Müllsammler freigeben soll, muss die Klasse, die die Ressourcen hortet, das IDisposableInterface implementieren. Damit verspricht sie, die Methode Dispose() anzubieten, die die
Aufräumarbeiten selbst erledigt. Das Programm muss dann die Methode Dispose() explizit
aufrufen, um die Ressourcen freigeben zu lassen, ohne auf den Müllsammler warten zu müssen.
Hat die Programmiererin nun eine Dispose()-Methode eingebaut und aufgerufen, so muss sie
das Laufzeitsystem dazu bringen, beim Entsorgen des Objekts über den Müllsammler nicht noch
einmal das Objekt zu entsorgen und die eingebaute Dispose()-Methode zu verwenden. Dies
erreicht sie durch Aufruf der Methode GC.SuppressFinalize beim Abarbeiten der Dispose()Methode und übergibt dabei eine Referenz auf das Objekt, das keine zweite Entsorgung benötigt.
Tritt der Müllsammler in Aktion und das Objekt ist aus dem Blickfeld geraten, ohne dass es das
Programm mit Dispose() explizit zerstört hat, so ruft der Müllsammler die Finalize-Methode
auf, deren Implementation sich praktischerweise wieder der Dispose()-Methode bedient.
So rufen beide, entweder das Programm explizit oder der Müllsammler implizit, die Methode
Dispose() auf und geben damit die Ressourcen des Objekts frei.
public void Dispose ()
{
// ...
// Ressourcen freigeben
//
GC . SupressFinalize ( this ) ;
}
public override void Finalize ()
{
Dispose () ; // Aufruf unserer Dipsose - Methode
base . Finalize () ;
}
Bei einigen Klassen, z.B. zur Dateibehandlung oder bei Datenströmen, ist der Aufruf einer
Close()-Methode verständlicher als der einer Dispose()-Methode. In solchen Fällen erstellt
3 Klassen
22
die Programmierierin eine private Dispose()-Methode und bietet nach außen eine öffentliche
Close()-Methode an, die intern Dispose aufruft.
Die Verantwortung für den Aufruf von Dispose() zum frühest möglichen Zeitpunkt liegt nun
beim Programm. Vergisst das Programm, Dispose() aufzurufen, gibt erst der Finalizer beim
Müllsammeln die Ressourcen frei.
Als Erleichterung dafür kennt C# die using-Konstruktion. Sie stellt sicher, dass das Laufzeitsystem Dispose() zum frühest möglichen Zeitpunkt aufruft. Die Idee dahinter ist, dem Laufzeitsystem eine Reihe von Objekten der gleichen Klasse mitzuteilen und den Programmabschnitt,
in dem sie gültig sind. Die Klasse der Objekte im using-Statement muss dazu das Interface
IDisposable unterstützen. Sobald das Laufzeitsystem diesen Programmabschnitt verlässt, gibt
sie die Objekte mit Dispose() frei.
Das using-Statement schützt auch vor unerwarteten Ausnahmen. Egal, was innerhalb der
using-Klammern passiert, Dispose wird auf jeden Fall aufgerufen. Speziell ist das beim Auftreten
von Exceptions interessant, die dadurch möglicherweise keinen try-catch-Block brauchen, weil
Dispose() auf jeden Fall aufgerufen wird.
using System ;
using System . IO ;
class Program
{
static void Main ( string [] args )
{
string path = @ " c :\ tmp \ logfile . txt " ;
// Logfile Öffnen oder neu Erstellen
using ( StreamWriter sw = File . AppendText ( path ) )
{
sw . WriteLine ( " Neuer Eintrag " ) ;
}
// Logfile ist freigegeben
// ...
// Normales Programm
// ...
// Logfile Lesen
using ( StreamReader sr = File . OpenText ( path ) )
{
string s = " " ; // Für alle Fälle
while (( s = sr . ReadLine () ) != null )
{
Console . WriteLine ( s ) ;
}
}
}
}
3.3 Versiegelt
Klassen, von denen andere Programmierer nicht ableiten sollen, kennzeichnet die Programmiererin als sealed. Der Grund, keine weitere Vererbung zuzulassen, ist meist, dass die Klasse
in einer Klassenbibliothek eine interne Hilfsklasse ist, deren unveränderte Funktion wichtig für
das Funktionieren der Bibliotheksklasse ist. Geade bei lizensieren Bibliotheken ist ein weiterer
23
3.4 Partielle Klassen
Grund der, dass die liefernde Firma Modifikationen der Bibliothek verhindern möchte, die der
Kunde nicht lizensiert hat. Das Microsoft .NET Framework verwendet genau aus diesem Grunde
sealed Klassen. So ist z.B. die Klasse String , die als compilerbekannte Klasse eine Sonderfunktion einnimmt, sealed.
using System ;
class Program
{
static void Main ( string [] args )
{
AuxiliaryClass a = new AuxiliaryClass () ;
}
}
sealed class AuxiliaryClass
{
// ... do something internal
// to the library
}
3.4 Partielle Klassen
C# erlaubt, den Code für eine Klasse auf mehrere Files aufzuteilen. Visual Studio 2005 nutzt
das zum Beispiel, um vom Form Designer automatisch erzeugten Code vom“handgeschriebenen”
Code der Entwicklerin zu trennen. Das reduziert die sichtbare Komplexität und steigert die
Übersichtlichkeit. Dabei tragen die einzelnen Klassen-Teile das Schlüselwort partial.
using System ;
partial class Program
{
static void Main ( string [] args )
{
Console . WriteLine ( Quadrat (256) ) ;
}
}
// -----
anderes File
------
partial class Program
{
static int Quadrat ( int x )
{
return x * x ;
}
}
3.5 Vererbung
C# unterstützt die einfache Vererbung. Ohnehin erbt jede Klasse von Object.
using System ;
class Program
3 Klassen
24
{
static void Main ( string [] args )
{
Azubi a = new Azubi () ;
Console . WriteLine ( a ) ;
}
}
class Person
{
protected string Nachname = " Müller " , Vorname = " Sven " ;
protected int Alter =17;
}
class Mitarbeiter : Person
{
protected int Personalnummer = 1232;
}
class Azubi : Mitarbeiter
{
int Lehrjahr = 1;
public override string ToString ()
{
return " Azubi " + Vorname + Nachname +
" im " + Lehrjahr + " . Lehrjahr " +
" , Alter " + Alter + " Jahre " +
" , Personalnummer " + Personalnummer ;
}
}
Soll der Konstruktor einer Klasse den Konstruktor der Basisklasse aufrufen, so geschieht dies in
der Klassendefinition. Im folgenden Beispiel ruft der Konstruktor von Mitarbeiter den Konstruktor
der Basisklasse Person auf, um eine Code-Dopplung zu vermeiden:
using System ;
class Program
{
static void Main ( string [] args )
{
Mitarbeiter m = new Mitarbeiter ( " Müller " , " Sven " , 17 , 32168) ;
Console . WriteLine ( m ) ;
}
}
class Person
{
protected string Nachname , Vorname ;
protected int Alter ;
protected Person ( string nachname , string vorname , int alter )
{
this . Nachname = nachname ;
this . Vorname = vorname ;
this . Alter = alter ;
}
public override string ToString ()
3.5 Vererbung
25
{
return " Name : " + Vorname + " " + Nachname + " , Alter : " +
Alter ;
}
}
class Mitarbeiter : Person
{
protected int Personalnummer ;
public Mitarbeiter ( string nn , string vn , int alter , int pn )
: base ( nn , vn , alter )
{
this . Personalnummer = pn ;
}
public override string ToString ()
{
return base . ToString () + " Personalnummer : " + Personalnummer ;
}
}
3.5.1 Der is-Operator
Der is-Operator ist true, wenn ein Objekt vom gleichen Typ ist wie das angegebene Objekt
oder davon abgeleitet.
using System ;
class Program
{
static void Main ( string [] args )
{
Person m = new Mitarbeiter () ;
Person p = new Person () ;
if ( p is Person )
Console . WriteLine ( " Person ist Person " ) ;
if ( m is Person )
Console . WriteLine ( " Mitarbeiter ist Person " ) ;
if ( p is Mitarbeiter )
Console . WriteLine ( " Person ist Mitarbeiter " ) ;
if ( m is Mitarbeiter )
Console . WriteLine ( " Mitarbeiter ist Mitarbeiter " ) ;
}
}
class Person
{
protected string Nachname = " Müller " , Vorname = " Sven " ;
protected int Alter =17;
}
class Mitarbeiter : Person
3 Klassen
26
{
protected int Personalnummer = 1232;
}
ergibt folgende Ausgabe:
Person ist Person
Mitarbeiter ist Person
Mitarbeiter ist Mitarbeiter
3.5.2 Der as-Operator
Der as-Operator nimmt direkt eine Typumwandlung vor. Falls der Zieltyp vom gleichen Typ ist
oder vom Typ einer Mutterklasse ist, wie der Ursprung, funktioniert die Konvertierung. Ansonsten
ist das Ergebnis null.
using System ;
class Program
{
static void Main ( string [] args )
{
Mitarbeiter m = new Mitarbeiter () ;
Person p = new Person () ;
Person tp = m as Person ;
Mitarbeiter tm = p as Mitarbeiter ;
Console . WriteLine ( " Testperson :
" + tp ) ;
Console . WriteLine ( " Testmitarbeiter : " + tm ) ;
}
}
class Person
{
protected string Nachname = " Müller " , Vorname = " Sven " ;
protected int Alter =17;
}
class Mitarbeiter : Person
{
protected int Personalnummer = 1232;
}
in diesem Fall gelingt die Konvertierung von Mitarbeiter Û Person mit Person tp = m as
Person; aber die Konvertierung von Person Û Mitarbeiter mit Mitarbeiter tm = p as Mitarbeiter; misslingt. Daher gibt die zweite Ausgabezeile keinen weiteren Text ab:
Testperson :
Mitarbeiter
Testmitarbeiter :
27
4 Methoden und Eigenschaften
Methoden sind Klassenfunktionen. Funktionen außerhalb von Klassen kommen in C# nicht vor1 .
Sie können die gleichen Zugriffsmodifikatoren wie Variable haben: public, private, protected,
internal, und static.
Auch sealed als Modifier ist möglich: Methoden, die andere Programmierer nicht überschreiben sollen, sind sealed. Das klingt zunächst seltsam, denn das Weglassen von virtual
hätte auch gereicht, um Überschreiben zu verhindern. Bei abgeleiteten Methoden gibt es aber in
einer Basisklasse eine Methode, die virtual ist, und wenn die Programmiererin in der abgeleiteten Klasse das weitere Ableiten verhindern will, kennzeichnet sie die Methode als selaed.
using System ;
class Program
{
static void Main ( string [] args )
{
LimitedClass l = new LimitedClass () ;
}
}
class FreeClass
{
public virtual void DoSomething ()
{
Console . WriteLine ( " FreeClass " ) ;
}
}
class LimitedClass : FreeClass
{
public sealed override void DoSomething ()
{
base . DoSomething () ;
}
}
4.1 Methoden-Parameter
Die Übergabe von Wertetypen als Methoden-Parameter erfolgt by-value - das Laufzeitsytem
erstellt eine Kopie der Werte und übergibt sie auf dem Stack. Bei Referenztypen erstellt das
Laufzeitsystem eine Kopie des Referenz-Zeigers, während das Objekt weiterhin nur einmal auf
dem Heap liegt. Sollen Methodenparameter einen Rückgabewert tragen, so erfährt der Compiler das über die Kennzeichnungen ref und out. Normale Parameter übergeben Werte in die
Methode, die bei Wertetypen durch Kopie aus den Orignalen auf dem Stack entstehen. Bei Referenztypen legt das Laufzeitsystem eine Kopie der Referenz auf den Stack - das Originalobjekt
bleibt unkopiert. ref-Parameter übergeben Werte in die Methode und können Rückgabewerte
1 Einige
Klasse definieren statische Methoden, die als Ersatz von globalen Funktionen dienen können.
4 Methoden und Eigenschaften
28
tragen. Hierbei achtet der C# -Compiler darauf, dass die Variablen vor der Übergabe initialisiert
sind. out-Parameter tragen nur Rückgabewerte - das aufrufende Programm braucht sie nicht zu
initialisieren. Die Programmiererin muss ref und out bei der Deklaration der Methode angeben
und dann bei jedem Aufruf der Methode noch einmal.
using System ;
class Program
{
static void Main ( string [] args )
{
int a = 3;
int b = 2;
int c ;
int d = DoSomething (a , ref b , out c ) ;
Console . WriteLine ( " a ={0} b ={1} c ={2} d ={3} " ,
a, b, c, d );
}
static int DoSomething ( int x , ref int y , out int z )
{
x += y ;
y = 7;
z = 6;
return x ;
}
}
Der aufrufende Programmteil übergibt Methoden-Parameter standardmäßig entweder als Wert
(call-by-value), wenn es sich um Wertetypen handelt, oder über Referenzen (call-by-reference).
Die Programiererin kann die Übergabe als Referenz erzwingen, wenn sie den Methoden-Parameter
auch für die Rückgabe von Funktionswerten nutzen möchte. Speziell bei Objekten bedeutet das,
dass sowohl bei Übergabe als Referenz aber auch als Wert der aufrufende Programmteil eine Kopie
vom Zeiger auf das Objekt übergibt, ohne jedoch das Objekt selber zu duplizieren. Das Verhalten
von Strings ist wiederum anders. Strings können sich nicht verändern und eine Manipulation des
übergebenen Strings innerhalb einer Methode erzeugt ein neues String-Objekt und dadurch bleibt
das String-Objekt im aufrufenden Programmteil unverändert.
4.2 Methoden mit variabler Parameterzahl
C# bietet eine einfache Weise, Methoden mit einer variablen Anzahl Parametern gleichen Typs zu
versorgen. Das params-Schlüsselwort vor dem letzten Argument einer Methodenparameterliste
bewirkt, dass C# die beim Aufruf folgenden Parameterwerte in einem Array des angegebenen
Typs übergibt.
static void Main ( string [] args )
{
int [] einArray = { 12 , 17 , 30 , 21 };
PrintArgs ( einArray ) ;
PrintArgs (10 , 12 , 14 , 16) ;
}
static void PrintArgs ( params int [] args )
{
foreach ( int i in args )
Console . WriteLine ( i ) ;
}
4.3 Überschreiben von Methoden
29
Sogar das Mischen von Parametertypen bei Parameterlisten variabler Länge ist möglich, wenn
der Typ der variablen Liste Object ist:
static void Main ( string [] args )
{
Object [] einArray = { 18 , 20 , " weg ! " };
PrintArgs ( einArray ) ;
PrintArgs ( " Stuttgart " , 21) ;
}
static void PrintArgs ( params Object [] args )
{
foreach ( Object o in args )
Console . WriteLine ( o ) ;
}
4.3 Überschreiben von Methoden
4.3.1 Überschreiben virtueller Methoden
In C# gibt das Schlüsselwort virtual an, dass die Programmiererin in abgeleiteten Klassen diese
Methode durch eine eigene Methode überschreiben oder durch eine neue Methode ersetzen darf.
Das Laufzeitsystem geht von einer überschriebenen Methode den Vererbungsweg rückwärts, bis
sie eine Methode der gleichen Signatur gefunden hat, die virtual ist. Damit das Laufzeitsystem erkennt, dass das Überschreiben absichtlich erfolgt ist, muss die Programmiererin beim
Überschreiben einer Methode durch eine eigene Methode dieses mit override angeben. Beim
Ersetzen einer Methode kennzeichnet sie dies mit new.
Überschriebene Methoden der Basisklassen kann die abgeleitete Klasse immer noch aufrufen,
wenn sie das base-Schlüsselwort verwendet. Dabei sucht das Laufzeitsystem bei base immer die
Vererbungshierarchie aufwärts, bis es die Methode findet und ruft diese dann auf.
using System ;
class Program
{
static void Main ( string [] args )
{
Grossvater [] familie = { new Grossvater () , new Vater () , new
Sohn () };
foreach ( Grossvater gi in familie )
Console . WriteLine ( gi . Info () ) ;
}
}
class Grossvater
{
public virtual String Info ()
{
return " Großvater " ;
}
}
class Vater : Grossvater
{
public override String Info ()
{
4 Methoden und Eigenschaften
30
return base . Info () + " Vater " ;
}
}
class Sohn : Vater
{
public new String Info ()
{
return base . Info () + " Sohn " ;
}
}
Wie das Ergebnis zeigt, überschreibt die Info()-Methode des Vaters diejenige des Großvaters.
Daher ruft der Iterator sie beim Durchlaufen auch auf. Der Iterator ruft jedoch nicht die Info()Methode des Sohns auf, weil diese Methode die des Großvaters nicht überschreibt, sondern nur
für die Verwendung und alle weiteren Ableitungen verdeckt.
Großvater
Großvater Vater
Großvater Vater
4.3.2 Erweitern vorhandener Klassen (Extension-Methoden)
Neu in C# 3.0 ist die Möglichkeit, vorhandene Klassen um eigene Methoden erweitern zu können.
Dies war bisher nur dann möglich, wenn es sich um von der Programmiererin selbst geschriebene
Klassen handelte und Zugriff auf den Quellcode möglich war. Speziell partielle Klassen (Schlüsselwort partial haben sich dafür angeboten, weil der Code für die Erweiterungen in andern
Dateien liegen konnte.
Das folgende Beispiel zeigt das Ergänzen der String-Klasse um den Reverse()-Aufruf, der
einen umgedrehten String liefert:
using System ;
using System . Text ;
namespace ExtensionMethodTest
{
class Program
{
static void Main ( string [] args )
{
string aok = " All OK " ;
Console . WriteLine ( " {0}: {1} " , aok , aok . Reverse () ) ;
}
}
public static class ExtensionMethods
{
public static string Reverse ( this string s ) {
StringBuilder sb = new StringBuilder () ;
if ( s . Length > 0)
for ( int i = s . Length -1; i >=0; i - -)
sb . Append ( s [ i ]) ;
return sb . ToString () ;
}
}
}
4.4 Operator-Überschreiben
31
4.4 Operator-Überschreiben
Das Überschreiben von Operatoren funktioniert in C# wie in C++ oder Java auch. Wer dabei
den == Operator überschreibt, muss auch den != Operator überschreiben und muss dann noch
Object.Equals(Object o) und Object.GetHashCode() überschreiben.
using System ;
class Program
{
static void Main ( string [] args )
{
DemoClass a = new DemoClass () ;
DemoClass b = new DemoClass () ;
if ( a == b )
Console . WriteLine ( " a und b sind gleich " ) ;
}
}
class DemoClass
{
public static bool operator == ( DemoClass a , DemoClass b )
{
return true ;
}
public static bool operator !=( DemoClass a , DemoClass b )
{
return false ;
}
public override bool Equals ( object obj )
{
return base . Equals ( obj ) ;
}
public override int GetHashCode ()
{
return base . GetHashCode () ;
}
}
Beim Indexer-Operator [] geht das Überschreiben nicht. Statt dessen greift ein Programm auf
den Indexer über die Property this auf den Indexer zu.
4.5 Eigenschaften (Properties)
Propertis erlauben kontrollierteren Zugriff auf Klassen-Variable und dienen dem InformationHiding. Nach außen sieht eine Property aus wie eine Variable. Ein lesender oder schreibender
Zugriff auf eine Property ruft allerdings eine Methode auf, die einen Wert setzt oder einen Wert
zurückliefert.
using System ;
class Program
{
static void Main ( string [] args )
4 Methoden und Eigenschaften
32
{
Zeit t = new Zeit () ;
t . Minute = 30;
Console . WriteLine ( t ) ;
t . Minute = 70;
Console . WriteLine ( t ) ;
}
}
class Zeit
{
int stunde , minute , sekunde ;
// ...
public int Minute
{
get { return minute ; }
set
{
if ( value >= 0 && value <= 60)
minute = value ;
}
}
public override string ToString ()
{
return String . Format ( " {0: D2 }:{1: D2 }:{2: D2 } " ,
stunde , minute , sekunde ) ;
}
}
Mit Hilfe von Properties kann die Klasse so präzise überwachen, ob die Werte auf die das
zugreifende Programmteil eine Variable setzen möchte überhaupt zulässig sind. Zum anderen
kann die Klasse so die interne Repräsentation eines Wertes verstecken (Information Hiding). So
kann sich die interne Repräsentation der Werte innerhalb der Klasse später ändern, ohne dass
dies Code außerhalb der Klasse betrifft.
4.5.1 Automatische Eigenschaften
Version 3.0 von C# erlaubt, Eigenschaften automatisch zu erstellen. Sie geben dazu nur
public String Name { get ; set ;}
und der Compiler erzeugt den Code für die Eigenschaft und den Zugriff auf eine dahinterliegende
Variable.
Dies entspricht der Verwendung einer öffentlichen Variablen und der Nutzen dieser
Abkürzungsmöglichkeit aus Sicht von Microsoft ist nicht ganz einsichtig. Einzig die Möglichkeit,
eine der Methoden get oder set als private zu deklarieren und die andere nicht unterscheidet
diese Variante von der Verwendung einer öffentlichen Variablen.
4.6 Indexer
Mit der Hilfe eines Indexers kann das aufrufende Programmteil eine Klasse ansprechen wie ein
Array. Indexer sehen zunächst aus wie eine Property, aber sie verwenden this[...]. Der Index
4.6 Indexer
33
des auszulesenden Werts steht in den eckigen Klammern hinter this, der zu setzende Wert in
value, einem reservierten Wort von C# .
using System ;
class Program
{
static void Main ( string [] args )
{
}
}
class Schachfigur
{
// ....
}
class Schachfeld
{
private Schachfigur [ ,] feld ;
public void Initialize ()
{
// ....
}
public Schachfigur this [ int x , int y ]
{
get
{
return feld [x , y ];
}
set
{
feld [x , y ] = value ;
}
}
}
Dabei sind die Indexparametertypen nicht auf Integerwerte beschränkt, es sind vielmehr beliebige
Typen zulässig. Das führt zu einer ganz einfachen Implementation von assoziativen Arrays:
using System ;
class Program
{
static void Main ( string [] args )
{
Rechteck r = new Rechteck (3 , 4) ;
Console . WriteLine ( " Länge : {0} , Breite : {1} " ,
r [ " Länge " ] , r [ " Breite " ]) ;
r [ " Breite " ] = 5;
Console . WriteLine ( " Länge : {0} , Breite : {1} " ,
r [ " Länge " ] , r [ " Breite " ]) ;
}
}
4 Methoden und Eigenschaften
34
class Rechteck
{
double [] dimensions = new double [2];
public Rechteck ( double l , double b )
{
dimensions [0] = l ;
dimensions [1] = b ;
}
public double this [ String side ]
{
get
{
if ( side == " Länge " )
return dimensions [0];
else if ( side == " Breite " )
return dimensions [1];
else throw
new Index OutOf RangeE xcept ion ( " Index nicht \" Breite \"
oder \" Höhe \" " ) ;
}
set
{
if ( value < 0) throw
new ArgumentException ( " Dimension kleiner Null " ) ;
if ( side == " Länge " )
dimensions [0] = value ;
if ( side == " Breite " )
dimensions [1] = value ;
}
}
}
4.7 Signatur
Der C# -Compiler hält überschriebene Methoden und Delegates über deren Signatur auseinander. Die Signatur einer Methode besteht aus ihrem Namen und der Zahl und dem Typ ihrer
Argumente. Sie beinhaltet nicht den Rückgabewert oder eine params-Angabe für den letzten
Parameter. Ebenfalls spielen Zugriffsbeschränkungen oder static bei Signatur keine Rolle.
using System ;
class Program
{
static void Main ( string [] args )
{
Console . WriteLine ( " Die Summe ist : " + Sum (1 , 2) ) ;
Console . WriteLine ( " Die Summe ist : " + Sum (1 , 2 , 3) ) ;
}
static int Sum ( int x , int y )
{
return x + y ;
}
35
4.8 Generische Methoden
static int Sum ( int x , int y , int z )
{
return x + y + z ;
}
}
4.8 Generische Methoden
Eine Methode, die einen Typ-Paramenter hat, ist generisch. Der generische Typ steht dabei in
spitzen Klammern – bei Methoden mit nur einem Typ heißt dieser oft “T”.
Im folgenden Programm vertauscht die Methode Tausche() ihre beiden Parameter:
using System ;
class Program
{
static void Main ( string [] args )
{
string sa = " Zucker " , sb = " Salz " ;
int ia = 4 , ib = 8;
Console . WriteLine ( " String a ={0} , b ={1} " ,
Console . WriteLine ( " int
a ={0} , b ={1} " ,
Tausche < string >( ref sa , ref sb ) ;
Tausche < int >( ref ia , ref ib ) ;
Console . WriteLine ( " String a ={0} , b ={1} " ,
Console . WriteLine ( " int
a ={0} , b ={1} " ,
}
sa , sb ) ;
ia , ib ) ;
sa , sb ) ;
ia , ib ) ;
private static void Tausche <T >( ref T a , ref T b )
{
T temp = a ;
a = b;
b = temp ;
}
}
Die Ausgabe zeigt, dass die Parameter nun vertauscht sind:
String
int
String
int
a = Zucker , b = Salz
a =4 , b =8
a = Salz , b = Zucker
a =8 , b =4
4.9 die Main-Methode
In C# gibt die Main-Methode an, wo das Laufzeitsystem die Ausführung des Codes starten soll.
Falls mehrere Klassen eine Main-Methode besitzen, muss die Anwenderin des Programms bei
Start den gewünschten Klassennamen angeben. Dazu stellt sie unter“Project/Properties/Startup
Object” die Klasse ein, deren Main()-Methode das Laufzeitsystem beim Programmstart aufruft.
Im Gegensatz zu C++ ist diese Startmethode Main in C# groß geschrieben und immer statisch.
using System ;
class Program1
{
4 Methoden und Eigenschaften
36
static void Main ( string [] args )
{
Console . WriteLine ( " Programm 1 " ) ;
}
}
class Program2
{
static void Main ( string [] args )
{
Console . WriteLine ( " Programm 2 " ) ;
}
}
37
5 Interfaces
Interfaces geben die öffentlichen Methoden und Properties an, die eine Klasse implementieren
muss. Interfaces finden dann Verwendung, wenn es keine passende Klasse gibt, von der eine
Klasse brauchbare Methoden und Properties erben kann. Während eine abstrakte Klasse dann das
hierarchische Erben von zu implementierenden Methoden erzwingt, sind Interfaces eher geeignet,
Klassen mit verschiedenen Aufgaben durch ihr Interface zusammenzubringen.
Die Definition eines Interfaces beginnt mit dem Schlüsselwort interface, gefolgt von öffentlichen Methoden und Properties. Die Klasse, die das Interface verwendet, schließt damit
einen Vertrag mit dem C# -Compiler, alle Methoden und Properties des Interfaces zu implementieren. Fehlt eine Methode oder Property bei der Implementierung, so führt das zu einem
Compilerfehler. In C# sind nur public Interfaces zulässig.
5.1 IEnumerable
IEnumerable stammt aus .NET 1.0 und verspricht eine einzige Funktion, GetEnumerator(),
die eine Methode zurückliefert, die das Interface IEnumerator unterstützt:
GetEnumerator() liefert einen Enumerator zurück, der Current, MoveNext() und Reset()
unterstützt.
Interface IEnumerable
{
IEnumerator GetEnumerator () ;
}
In .NET 2.0 bietet sich die Verwendung der typsicheren Variante dieses Interfaces an, IEnumerable<T> .
5.2 IEnumerator
Das Interface IEnumerator unterstützt die Methoden von .NET 1.0 zum Durchlaufen von
Sammlungen (Collections).
Interface IEnumerator
{
object Current { get ; }
bool MoveNext () ;
void Reset () ;
}
Dabei haben die Methoden und Properties folgende Aufgaben:
Current
Vorsicht
beim Cast
liefert das aktuelle Objekt zurück. Diese Methode ist nicht typsicher, weil das von Object
aufrufende Programm das gelieferte Objekt auf den wirklich gespeicherten Objekttyp casten wird - eine häufige Fehlerquelle.
5 Interfaces
38
MoveNext() geht weiter zum nächsten Objekt in der Sammlung und liefert true, falls es
ein nächstes Objekt gibt. Falls ein nächstes Objekt nicht existiert, dann liefert
MoveNext() false zurück.
Reset()
setzt den internen Zeiger in die Sammlung vor das erste Objekt, so dass das nächste
MoveNext() das erste Objekt zurückgibt oder false zurückgeben kann, falls die
Sammlung kein Element enthält.
5.3 IConvertible
IConvertible implementiert Methoden, die ein Objekt der aktuellen Klasse explizit in eine
andere Darstellung konvertieren (Boolean, Byte, Character, DateTime, Decimal, Double, Int16,
Int32, Int64, SByte, Single, UInt16, Uint32, UInt64)
5.4 IComparable
IComparable implementiert die Methode CompareTo(), die einen Vergleich mit einem andern
Objekt bietet. Eigene Klassen überschreiben meist CompareTo() durch geeigneteren Code für
einen Vergleich.
CompareTo() bietet einen Vergleich mit andern Objekten.
5.5 ICloneable
ICloneable implementiert die Methode Clone(), die eine weitere Referenz auf das gleiche
Objekt zurückliefert. Das eigentliche Objekt bleibt dabei nur einfach im Speicher – es hat lediglich
2 Referenzen, die auf es zeigen.
Clone() liefert neue Referenz auf bekanntes Objekt.
5.6 IFormatable
Das Interface IFormatable überlädt die systemeigene parameterlose ToString()-Methode um
eine Methode mit 2 Parametern:
Interface IFormatable
{
string ToString ( string format , IFormatProvider formatProvider ) ;
}
ToString() benutzereigene Darstellung einer Klasse, passend zur formatierten Ausgabe mit
String.Format() oder System.Console.WriteLine().
Das folgende Beispiel definiert einen Ausschnitt aus einer Klasse für komplexe Zahlen, die ein
eigenes Formatierungskennzeichen, den Buchstaben “c” erhält:
using System ;
class Program
{
static void Main ( string [] args )
{
5.7 IEnumerable<T>
39
Complex
a = new Complex () ,
b = new Complex (7.3 , 13) ;
Console . WriteLine ( " a ist {0: c } " , a ) ;
Console . WriteLine ( " b ist {0: c } " , b ) ;
}
}
class Complex : IFormattable
{
public double re , im ;
public Complex ()
{
}
public Complex ( double r , double i )
{
re = r ; im = i ;
}
public string ToString ( string format ,
IFormatProvider formatProvider )
{
if ( format == null ) return ToString () ;
if ( format == " c " )
return String . Format ( " ({0} ,{1} i ) " ,
re , im ) ;
return ToString () ;
}
// ...
}
Falls die Klasse Complex das Interface IFormatable angibt, dann sieht die Ausgabe folgendermaßen aus:
a ist (0 ,0 i )
b ist (7 ,3 ,13 i )
5.7 IEnumerable<T>
Wenn das foreach-Konstrukt durch eine Sammlung laufen soll, dann muss diese Sammlung das
Interface IEnumerable<T> unterstützen. Das Interface IEnumerable<T> verspricht, die Methode GetEnumerator() zu unterstützen:
GetEnumerator() liefert einen Enumerator zurück, der Current, MoveNext() und Reset()
unterstützt.
Die generische Implementation erbt dabei von der nicht-generischen, objektbasierten Implementation von IEnumerable .
5 Interfaces
40
5.8 IList<T>
Klassen, die das Interface IList<T> unterstützen, erlauben einen Zugriff auf die Klassenelemente,
als ob es ein Array wäre. Darüberhinaus unterstützen die Methoden des Interfaces das Entnehmen
und Einfügen von Elementen an beliebigen Positionen.
Item
Property zum Lesen und Setzen eines Array-Elements.
bool IsFixedSize
Property zum Feststellen, ob das Array feste Größe hat.
bool IsReadOnly
Property zum Feststellen, ob nur lesender Zugriff erlaubt
ist.
int Add(element)
Fügt ein Element zur Liste hinzu. Liefert die Position des
eingefügten Elements zurück.
void Clear()
Löscht alle Listenelemente.
bool Contains(element)
Durchsucht die Liste nach dem angegebenen Element und
liefert zurück, ob es enthalten ist.
int IndexOf(element)
Durchsucht die Liste nach dem angegebenen Element und
liefert dessen Position zurück, wenn es enthalten ist, andernfalls -1.
void Insert(position, element) Fügt das Element an der angegebenen Position ein. In der
Liste evtl. schon vorhandene Elemente rutschen um eine
Position nach hinten.
void Remove(object)
Sucht das angegebene Element in der Liste und entfernt
es. Die nachfolgenden Elemente rutschen um eine Position auf. Falls das Element nicht in der Liste vorhanden
ist, passiert nichts.
void RemoveAt(position)
Entfernt das Element an der angegebenen Position. Die
nachfolgenden Elemente rutschen um eine Position auf.
5.9 ICollection<T>
Collection-Klassen implementieren das Interface ICollection. Die Methoden des Interfaces
fügen Elemente zur Sammlung hinzu oder löschen sie.
int Count
Liefert die Anzahl der Elemente in der Sammlung.
bool IsReadOnly
Ist die Sammlung nur zum Lesen gedacht?
void Add()
Fügt ein Element zur Sammlung hinzu.
void Clear()
Löscht alle Elemente der Sammlung.
Contains
Durchsucht die Liste nach dem angegebenen Element und liefert
zurück, ob es enthalten ist.
void CopyTo(array, index) Kopiert alle Elmente der Liste ab einem bestimmten Index in
das übergebene Array.
bool Remove(element)
Sucht das angegebene Element in der Liste und entfernt dessen
erstes Auftreten. Liefert bei Erfolg true.
41
6 Strings
In C# sind Strings Objekte einer eigenen Klasse System.String, der String-Klasse, die mehr
Funktionen zur Verfügung stellt als C++ mit seinem String-Array. Da Programmiererinnen von
String nicht ableiten sollen, ist die String-Klasse sealed .
Strings in C# bestehen aus Unicode-Zeichen mit 2 Byte Größe. Einmal erzeugt sind sie unveränderlich. Das heißt, dass schon das einfache hinzufügen eines weiteren Zeichens zu einer
Kopieroperation an einen neuen Ort im Speicher führt, den das Laufzeitsystem zuvor noch bereitstellen muss.
Sonderzeichen innerhalb von Strings schützt die Programmiererin durch einen vorangestellten
Rückwärtsstrich (backslash). Strings unter Windows enthalten oft recht viele Rückwärtsstriche ,
weil das Dateisystem das erfodert. Da der Rückwärtsstrich dasjenige Zeichen ist, das Sonderzeichen schützt, braucht es zwei aufeinanderfolgende Rückwärtsstriche, um einen Rückwärtsstrich
in einen String einzubauen. C# hat daher zur Vereinfachung den “literalen” String eingeführt
(gekennzeichnet durch ein @), in dem der Compiler alle Sonderzeichen im String ignoriert.
using System ;
class Program
{
static void Main ( string [] args )
{
String dateiname1 = " C :\\\\ Windows \\ Temp \\ cs_log . txt " ;
String dateiname2 = @ " C :\\ Windows \ Temp \ cs_log . txt " ;
Console . WriteLine ( dateiname1 ) ;
Console . WriteLine ( dateiname2 ) ;
}
}
6.1 Escape-Character
Innerhalb
fluss auf
Zeichen
\a
\b
\t
\r
\v
\f
\n
\x20
\u0020
von Strings gibt es eine Reihe von Sonderzeichen, die speziell Eindie Darstellung oder Formattierung des Ausgabetextes haben.
Bedeutung
Akustisches Signal.
Backspace.
Tabulator.
Carriage Return.
Vertical Tabulator.
Form Feed.
Newline.
ASCII-Zeichen in Hexadezimaldarstellung (genau 2 Hex-Zeichen).
Unicode-Zeiche in Hexadezimaldarstellung (genau 4 Hex-Zeichen).
Tabelle 6.1: Sonderzeichen in Strings
6 Strings
42
6.2 Interfaces
Die Klasse String implementiert die Interfaces IEnumerable, IConvertible und erbt von
Object insbesondere IComparable und ICloneable.
IEnumerable
Implementiert die Methode GetEnumerator, die einen Enumerator zurückliefert,
der durch die einzelnen Zeichen eines Strings iterieren kann
IConvertible Implementiert Methoden, die den Sting explizit in eine andere Darstellung konvertieren (Boolean, Byte, Character, DateTime, Decimal, Double, Int16, Int32,
Int64, SByte, Single, UInt16, Uint32, UInt64)
IComparable
Implementiert die Methode CompareTo, die, je nach Überladung, einen
Stringvergleich oder einen Vergleich mit einem andern Objekt bietet
ICloneable
Implementiert die Methode Clone, die eine weitere Referenz auf den gleichen
Stringinhalt zurückliefert
Die String-Klasse implementiert eine öffentlich sichtbare und nur lesbare Variable, den leeren
String Empty. Weiterhin gibt es zwei öffentlich lesbare Properties, Chars, die das Zeichen an der
angegebenen Position zurückliefert und Length, die die Länge des Strings in Zahl der Zeichen
angibt.
6.3 String-Erzeugung
Der Compiler erkennt einen literalen String1 und baut ihn zur Laufzeit in ein Objekt der Klasse
System.String ein. Dieses Objekt unterstützt dann alle Methoden der Klasse String, wie zum
Beispiel auch den überschriebenen “+”-Operator.
using System ;
class Program
{
static void Main ( string [] args )
{
String gruss = " Hallo " ;
gruss += " Welt ! " ;
Console . WriteLine ( gruss ) ;
}
}
Im vorangehenden Beispiel hat das Laufzeitsystem erst ein String-Objekt erzeugt und mit dem
Text“Hallo“ gefüllt (inclusive dem nacheilenden Leerzeichen). Auch hat er“Welt!”in ein weiteres
String-Objekt eingepackt. Dann hat der überschriebene “+”-Operator von gruss beide Strings
hintereinander in ein neu erzeugtes String-Objekt gepackt und die Referenz gruss auf das neue
Objekt umgebogen.
1 String,
der als Text in Hochkommata steht: “Beispiel”
43
6.4 String-Methoden
6.4 String-Methoden
Methode
Clone()
Compare()
CompareOrdinal()
CompareTo()
Concat()
Contains()
Copy()
CopyTo()
EndsWith()
Equals()
Format()
GetEnumerator()
GetHashCode()
GetType()
GetTypeCode()
IndexOf()
IndexOfAny()
Insert()
Intern()
IsInterned()
IsNormalized()
IsNullOrEmpty()
Join()
Aufgabe
Liefert eine Objekt-Referenz auf den gleichen String zurück.
Vergleicht die beiden übergebenen Strings und liefert eine Zahl größer
Null zurück, wenn der erste String lexikalisch (nach aktueller Kultureinstellung) größer ist als der zweite. Dazu entsprechend 0 bei Gleichheit
und eine Zahl <0, wenn der zweite String kleiner ist als der erste.
Vergleicht die in den beiden Strings enthaltenen Zahlen und liefert
größer(>0), gleich(0) oder kleiner(<0) zurück.
Vergleicht den übergebenen String mit dem eigenen Stringobjekt.
Hängt bis zu vier Strings aneinander und liefert den durch Aneinanderhängen entstandenen String zurück.
Liefert true zurück, wenn der String im Argument im aktuellen String
enthalten ist.
Kopiert den übergebenen String und liefert ihn in einen neuen Stringobjekt zurück.
Kopiert Zeichen ab einer gegebenen Zeichenposition im aktuellen
String in ein Character-Array um.
Liefert true zurück, wenn der aktuelle String mit dem als Argument
übergebenen String endet.
Prüft, ob zwei String-Objekte gleich sind (mit einem kultur-invarianten
und groß-klein-berücksichtigenden Vergleich).
Ersetzt den Platzhalter im Format-String durch eine im Format-String
angegebene Darstellung der als weiteren Argumente übergebenen Objekte.
Liefert einen Enumerator, der die einzelnen Zeichen eines Strings
durchlaufen kann.
Liefert einen möglichst string-eindeutigen Wert.
Liefert eine Instanz von System.Type, mit deren Hilfe das Laufzeitsystem mehr über den String erfahren kann.
Liefert einen Enumerator zurück, der den Typ angibt (welcher Wertetyp
oder Objekt oder String).
Gibt die Position des Auftretens des übergebenen Suchstring im aktuellen String an.
Gibt die Position des ersten Auftretens irgendeines Zeichens aus dem
übergebenen Sucharray im aktuellen String an.
Fügt den übergebenen String an der angegebenen Position in den aktuellen String ein und liefert ein neues Stringobjekt mit dem Ergebnis
zurück.
C# speichert jeden String nur einmal (auch Literale) im internen Pool.
Intern() liefert die Stringreferenz auf die interne Darstellung zurück.
Prüft nach, ob der übergebene String im internen Pool gespeichert ist.
Prüft, ob der String in Unicode-Normalform vorliegt.
Prüft, ob der String leer ist oder eine Null-Referenz.
Baut einen String aus einem übergebenen String-Array zusammen, jeweils getrennt durch den angegebenen Trenn-String.
Fortsetzung auf der nächsten Seite Û
6 Strings
44
Methode
LastIndexOf()
LastIndexOfAny()
Normalize()
op_Equality()
op_Inequality()
PadLeft()
PadRight()
ReferenceEquals()
Remove()
Replace()
Split()
StartsWith()
Substring()
ToCharArray()
ToLower()
ToLowerInvariant()
ToString()
ToUpper()
ToUpperInvariant()
Trim()
TrimEnd()
TrimStart()
Aufgabe
Gibt die letzte Position des Auftretens des übergebenen Suchstring im
aktuellen String an.
Gibt die letzte Position des Auftretens irgendeines Zeichens aus dem
übergebenen Sucharray im aktuellen String an.
Wandelt den String in eine bestimmte Unicode-Darstellung um.
Prüft auf Gleichheit von zwei Strings. Verwendet intern Equals().
Prüft auf Ungleichheit von zwei Strings. Verwendet intern Equals().
Schiebt einen String nacht rechts und füllt links auf die angegebene
Länge mit dem angegebenen Zeichen auf.
Schiebt einen String nacht links und füllt rechts auf die angegebene
Länge mit dem angegebenen Zeichen auf.
Prüft, ob zwei Referenzen auf das selbe Objekt zeigen.
Löscht die angegeben Zahl von Zeichen ab der angegebenen Position.
Ersetzt alle Vorkommen des einen Strings durch den andern String.
Zerlegt einen String in ein String-Array, indem es bei bei den im
übergebenen Character-Array enthaltenen Buchstaben schneidet. Die
Trenn-Buchstaben sind in den Rückgabe-Strings nicht enthalten.
Liefert true zurück, wenn der aktuelle String mit dem als Argument
übergebenen String anfängt.
Schneidet einen Teilstring aus dem aktuellen String aus und liefert ihn
zurück.
Liefert die einzelnen Zeichen im String als Elemente eines CharacterArray zurück.
Wandelt den String in Kleinbuchstaben.
Wandelt den String in Kleinbuchstaben mit den Ersetzungsregeln der
invarianten Kultur.
Liefert eine Referenz auf die gleiche Instanz des Strings zurück. Keine
Konvertierung etc.
Wandelt den String in Großbuchstaben.
Wandelt den String in Großbuchstaben mit den Ersetzungsregeln der
invarianten Kultur.
Entfernt alle Vorkommen von im Character-Array übergebenen Zeichen
von Beginn und Ende des Strings.
Entfernt alle Vorkommen von im Character-Array übergebenen Zeichen
von Ende des Strings.
Entfernt alle Vorkommen von im Character-Array übergebenen Zeichen
von Beginn des Strings.
Tabelle 6.2: Methoden von String
6.4.1 String-Vergleiche
Zum Vergleichen von Strings eignen sich die Methoden Compare(), CompareOrdinal(), CompareTo(), Equals(), EndsWith() und StartsWith():
using System ;
class Program
{
static void Main ( string [] args )
{
string muster = " 123 gna gna gna 456 " ;
6.4 String-Methoden
45
string vgl = " 123 " ;
Console . WriteLine ( String . Compare ( muster , vgl ) ) ;
Console . WriteLine ( String . CompareOrdinal
( muster ,0 , vgl , 0 , 3) ) ;
Console . WriteLine ( muster . CompareTo ( vgl ) ) ;
Console . WriteLine ( muster . Equals ( vgl ) ) ;
Console . WriteLine ( muster . StartsWith ( vgl ) ) ;
Console . WriteLine ( muster . EndsWith ( vgl ) ) ;
}
}
ergibt folgende Ausgabe:
1
0
1
False
True
False
6.4.2 String-Suchen
Zum Suchen von Zeichen und Zeichenfolgen innerhalb von Strings dienen IndexOf(), IndexOfAny(), LastIndexOf(), and LastIndexOfAny():
using System ;
class Program
{
static void Main ( string [] args )
{
string muster = " 123 gna gna gna 456 " ;
char [] pat = { ’3 ’ , ’g ’ , ’5 ’ };
Console . WriteLine ( muster . IndexOf ( " gna " ) ) ;
Console . WriteLine ( muster . IndexOfAny ( pat ) ) ;
Console . WriteLine ( muster . LastIndexOf ( " gna " ) ) ;
Console . WriteLine ( muster . IndexOfAny ( pat ) ) ;
}
}
ergibt folgende Ausgabe:
4
2
12
2
6.4.3 Kopieren von Strings
Copy() und CopyTo() kopieren Strings in Strings oder Character-Arrays:
using System ;
class Program
{
static void Main ( string [] args )
{
string muster = " 123 gna gna gna 456 " ;
6 Strings
46
string kopie = String . Copy ( muster ) ;
char [] einzeln = { ’ ’, ’ ’, ’ ’, ’ ’, ’ ’, };
muster . CopyTo (0 , einzeln , 0 , 5) ;
Console . WriteLine ( kopie ) ;
Console . WriteLine ( einzeln ) ;
}
}
ergibt folgende Ausgabe:
123 gna gna gna 456
123 g
6.4.4 Zerlegen und Zusammenbauen von Strings
Substring() und Split() zerlegen Strings in Teile und Concat() und Join() bauen sie wieder
zusammen:
using System ;
class Program
{
static void Main ( string [] args )
{
string muster = " 123 gna gna gna 456 " ;
string s1 = muster . Substring (4 , 7) ;
// Split () - Argument nutzt params
string [] sa2 = muster . Split ( ’ ’, ’2 ’) ;
string s3 = String . Join ( " + " , sa2 ) ;
string s4 = String . Concat ( muster , s3 ) ;
Console . WriteLine ( s1 ) ;
foreach ( string s in sa2 )
Console . WriteLine ( s ) ;
Console . WriteLine ( s3 ) ;
Console . WriteLine ( s4 ) ;
}
}
füht zu:
gna gna
1
3
gna
gna
gna
456
1+3+ gna + gna + gna +456+
123 gna gna gna 456 1+3+ gna + gna + gna +456+
6.4.5 Ersetzen und Verändern von String-Inhalten
Insert(), Replace(), Remove(), PadLeft(), PadRight(), Trim(), TrimEnd() und TrimStart() verändern String-Inhalte und liefern einen neuen String zurück:
47
6.4 String-Methoden
using System ;
class Program
{
static void Main ( string [] args )
{
string muster = " 123 gna gna gna 456 " ;
string [] sa = new string [8];
sa [0] = muster ;
sa [1] = muster . Insert (4 , " bla " ) ;
sa [2] = muster . Replace ( " gna " , " bla " ) ;
sa [3] = muster . Remove (3 , 14) ;
sa [4] = muster . PadRight (24 , ’+ ’) ;
sa [5] = sa [4]. PadLeft (28) ;
sa [6] = sa [5]. Trim () ;
sa [7] = sa [6]. TrimEnd ( ’+ ’) ;
foreach ( string s in sa )
Console . WriteLine ( s ) ;
}
}
...ergibt folgende Ausgabe (mit angezeigten Leerzeichen):
123 gna
123 bla
123 bla
12356
123 gna
123
123 gna
123 gna
gna gna 456
gna gna gna 456
bla bla 456
gna
gna
gna
gna
gna
gna
gna
gna
456 ++++
gna 456 ++++
456 ++++
456
6.4.6 Groß- und Kleinschreibung von Strings
ToLower(), ToLowerInvariant(), ToUpper() und ToUpperInvariant() verändern Groß- zu
Kleinschreibung und umgekehrt:
using System ;
class Program
{
static void Main ( string [] args )
{
string muster = " 123 Gna Gna Gna 456 " ;
Console . WriteLine ( muster ) ;
Console . WriteLine ( muster . ToLower () ) ;
Console . WriteLine ( muster . ToUpper () ) ;
}
}
ergibt
123 Gna Gna Gna 456
123 gna gna gna 456
123 GNA GNA GNA 456
48
6 Strings
6.4.7 String-Formatierung
Format() schreibt benutzerdefinierte Darstellungen von Werten in Strings hinein.
Mehr über die möglichen Formatangaben steht im Kapitel 6.5.
6.4.8 Länge eines Strings und Zugriff auf Einzelbuchstaben
Length() liefert die Länge eines Strings zurück. Zugriff auf einen einzelnen Buchstaben bietet
der Indexer. Durch ihn verhält sich ein String wie ein Character-Array, wenn das Laufzeitsystem
ihn mit der Array-Notierung anspricht:
using System ;
class Program
{
static void Main ( string [] args )
{
string muster = " Muster = Pattern " ;
// Property statt Methode
int musterLaenge = muster . Length ;
for ( int i = 0; i < musterLaenge ; i += 2)
// Indexer
Console . Write ( muster [ i ]) ;
Console . WriteLine () ;
}
}
ergibt die einzeilige Ausgabe
Mse = atr
6.5 Format-Angaben
In vielen Fällen ist die Standard-Ausgabe von ToString() nicht so, wie sie aussehen soll.
C# bietet die Möglichkeit, zu eigenen Klassen benutzerdefinerte Formate anzugeben. Dies könnte z.B. bei einer eigenen komplexen Klasse der Fall sein, oder bei einer Klasse, die Boote und
Fähren beschreibt. Die Klasse unterstützt dazu das Interface IFormatable. Damit weiß das
Laufzeitsystem, dass die Klasse eine erweiterte Form der Methode ToString unterstützt.
6.5.1 Standard-Formatangaben
Das folgende Beispiel zeigt die Verwendung der eingebauten Standard-Formatangaben bei der
Ausgabe von Zahlen mit WriteLine():
using System ;
class Program
{
static void Main ( string [] args )
{
double d = 1.23456;
int i = 314;
Console . WriteLine ( " d ={0 ,15: E } , i ={1 ,4} " , d , i ) ;
}
}
49
6.5 Format-Angaben
mit der Ausgabe
d=
1 ,234560 E +000 , i = 314
Die Argumente zu WriteLine() sind zum einen der Formatstring, zum andern die Variablen, die WriteLine() darstellen soll. Im Formatstring befinden sich, in geschweifte Klammern
eingeschlossen, die Platzhalter, an deren Stelle das Laufzeitsystem bei der Ausgabe die formatierten Zahlen setzt. Der Platzhalter besteht aus folgenden Komponenten:
ˆ Der Nummer des Arguments in der nachfolgenden Argumentenliste, beginnend bei 0. Einer der häufigsten Gründe für Exceptions ist, zu beim Umstellen der Argumenteliste das Häufiger
Anpassen der Argumentenummer zu vergessen.
Programmierfehler
ˆ Der Länge der Darstellung der Ausgabe dieses Elements, durch ein Komma getrennt von der führt zu
Argumentennummer. Im vorigen Beispiel ist die angegebene Länge in beiden Fällen (Double Exceptions
und Integer) größer als nötig und das Laufzeitsystem hat mit Leerzeichen aufgefüllt.
ˆ Der Formatspezifikation, durch einen Doppelpunkt von den vorangehenden Elementen getrennt. Tabelle 6.5.1 listet die Formatspezifikatoren auf:
Außer der Argumentennummer sind alle andern Angaben optional.
Spezifikator
c
D
E
F
G
N
P
X
Wirkung
Länderabhängiger Geldwert
Normale Integerzahl
Wissenschaftliche Darstellung
Festkommazahl
Normale Zahl
Länderspezifisches Zahlenformat
Prozentangabe
Hexadezimalzahl
Tabelle 6.3: Formatspezifikatoren
Das Beispiel zeigt die verschiedenen Ausgaben bei Standardlängeneinstellung:
using System ;
class Program
{
static void Main ( string [] args )
{
double d = 123456.789;
int i = 314;
Console . WriteLine ( " c : {0: c } " , d ) ;
Console . WriteLine ( " D : {0: D } " , i ) ;
Console . WriteLine ( " E : {0: E } " , d ) ;
Console . WriteLine ( " F : {0: F } " , d ) ;
Console . WriteLine ( " G : {0: G } " , d ) ;
Console . WriteLine ( " N : {0: N } " , d ) ;
Console . WriteLine ( " P : {0: P } " , d ) ;
Console . WriteLine ( " X : 0 x {0: X } " , i ) ;
}
}
Console.WriteLine() ruft dabei intern String.Format() auf, das die Formatierungsarbeiten
übernimmt:
6 Strings
50
c:
D:
E:
F:
G:
N:
P:
X:
123.456 ,79 ?
314
1 ,234568 E +005
123456 ,79
123456 ,789
123.456 ,79
12.345.678 ,90%
0 x13A
In diesem Beispiel zu beachten: Die Console kann Euro-Zeichen nicht korrekt darstellen.
6.6 Reguläre Ausdrücke
Reguläre Ausdrücke erleichtern Such- und Ersetzungs-Aufgaben, indem sie vordefinierte Muster
und Suchverfahren bieten. Typische Einsatzgebiete der Regulären Ausdrücke sind:
ˆ Vereinheitlichen der unterschiedlichen Schreibweisen von Telefonnummern in ein gemeinsames Format.
ˆ Wortwiederholungen in Dokumenten erkennen und entfernen, wie sie typischerweise beim
häufigen Umstellen von Sätzen in Skripten oder Diplomarbeiten entstehen.
ˆ Alle Anfangsbuchstaben von Wörtern groß schreiben, z.B. bei den ID-Tags in einer Sammlung mit .mp3-Dateien.
ˆ Jeden Satz mit einem Großbuchstaben beginnen lassen.
ˆ Einen Uniform Resource Identifier (URI) in seine Bestandteile zerlegen, z.B.:
http://meier:[email protected]:8080/info.php?docid=17&format=pdf
Das System der Regulären Ausdrücke besteht aus zwei Komponenten:
1. Ein Satz von Escape Codes, die bestimmte Zeichenarten angeben: z.B. Ziffern, Punktuation, Buchstaben, Großbuchstaben, Wort- und Satzgrenzen . . .
2. Eine Methode, Teilstrings, Zwischenresultate und Endergebnisse zu gruppieren.
Das folgende Beispiel sucht alle Vorkommen des Strings Ausdrücke und gibt die Positionen des
Vorkommens des Strings als Collection zurück:
using System ;
using System . Text . RegularExpressions ;
class Program
{
static void Main ( string [] args )
{
string input =
@ " Reguläre Ausdrücke erleichtern Such - und
Ersetzungs - Aufgaben , indem sie vordefinierte Muster
und Suchverfahren bieten .
Typische Einsatzgebiete der Regulären Ausdrücke sind : " ;
string pattern = " Ausdrücke " ;
MatchCollection matches =
Regex . Matches ( input , pattern ) ;
51
6.6 Reguläre Ausdrücke
foreach ( Match m in matches )
Console . WriteLine ( m . Index ) ;
}
}
ergibt folgende Ausgabe:
9
162
Etwas kompliziertere Muster aus einzelnen Symbolen finden fast beliebige Teilstrings innerhalb
des gegebenen Inputs. Ein solches Muster besteht aus normalen Zeichen wie Buchstaben, Ziffern
oder Satzzeichen sowie Symbolen mit Sonderbedeutung.
Die folgende Tabelle gibt eine Übersicht über die C# bekannten Symbole:
Symbol
.
*
^
Bedeutung
Beliebiger Buchstabe außer Zeilenende
Vorhergehendes Zeichen oder Gruppe 0mal, 1-mal oder beliebig oft wiederholt
Vorhergehendes Zeichen oder Gruppe 1mal oder beliebig oft wiederholt
Vorhergehendes Zeichen oder Gruppe 0mal oder 1-mal wiederholt
Mindestens n und höchstens m Wiederholungen des vorangehenden Zeichens oder
der vorangehenden Gruppe.
Mindestens n Wiederholungen des vorangehenden Zeichens oder der vorangehenden Gruppe.
Genau n Wiederholungen des vorangehenden Zeichens oder der vorangehenden
Gruppe.
Runde Klammern fassen Gruppen zusammen. Gleichzeitig ist alles innerhalb einer
Gruppe im Suchergebnis enthalten (siehe
Beispiel)
Textanfang
$
Textende
\d
\D
\s
\S
\b
\B
Eine Ziffer
Alle Zeichen, die keine Ziffer sind
Whitespace (Leerzeichen, Tab, . . . )
Alle Zeichen, die kein Whitespace sind
Wortgrenze
Alle Zeichen, die keine Wortgrenze sind
+
?
{n,m}
{n, }
{n}
(xxx )
Beispiel
de.
I*
I*
Wirkung
der, des, dem, . . .
leer, I, II, . . .
leer, I, II, . . .
IB?M
IBM und IM
a{3,4}
aaa, aaaa
a{2,}
aa, aaa, . . .
a{5}
aaaaa
(ein)+deutig
eineindeutig, nicht eindeutig
^Das
Das, aber nur wenn es am
Textanfang steht
hat., aber nur wenn es am
Textende steht
DIN-A4, DIN-A5, . . .
AT, AE, . . .
ein, \tein, \nein, . . .
mein, nein, . . .
ein, aber nicht dein
ei in Wortmitte, z.B. mein,
nein, aber nicht ein.
hat$
DIN\-A\d
A\D
\sein
\Sein
\bein
\Bei\B
Tabelle 6.4: RegEx-Symbole
Zur Suche nach Symbolen selbst im Text schützt man die Symbole durch einen vorgestellten
Rückstrich (’\’).
Beispiel für die Suche nach ganzen Wörtern. Das Suchmuster besteht aus mehr als einem oder
mehreren (’cd+’) nichtleeren Zeichen (’\S’), gefolgt von einem Leerzeichen (’\s’).
using System ;
6 Strings
52
using System . Text . RegularExpressions ;
class Program
{
static void Main ( string [] args )
{
string input =
@ " Reguläre Ausdrücke erleichtern Such - und
Ersetzungs - Aufgaben , indem sie vordefinierte Muster
und Suchverfahren bieten .
Typische Einsatzgebiete der Regulären Ausdrücke sind : " ;
string pattern = @ " (\ S +) \ s " ;
MatchCollection matches
= Regex . Matches ( input , pattern ) ;
foreach ( Match m in matches )
Console . WriteLine ( m . ToString () ) ;
}
}
Ergibt nachstehende Ausgabe. Das letzte Wort im Eingabetext kommt dabei im Ausgabetext
nicht vor, weil hinter ihm kein Leerzeichen folgt. Satzzeichen, die direkt auf Worte folgen, fallen
mit unter das nicht-leere Suchmuster und tauchen im Ausgabetext wieder mit auf.
Reguläre
Ausdrücke
erleichtern
Such und
Ersetzungs - Aufgaben ,
indem
sie
vordefinierte
Muster
und
Suchverfahren
bieten .
Typische
Einsatzgebiete
der
Regulären
Ausdrücke
Da C# bei regulären Ausdrücken immer den längsten Match sucht, liefert eine kleine Änderung
alle Worte, wie gewünscht.
string pattern = @ " (\ S +) " ;
Das Einschränken der Zeichen auf nur alfabetische Zeichen durch die Liste in eckigen Klammern
string pattern = @ " ([ A - Z Ä Ö Üa - zäöüß ]+) " ;
ergibt endlich die gewünschte Liste von Wörtern.
Eine kleine Änderung liefert die Hauptwörter:
string pattern = @ " ([ A - Z Ä Ö Ü ][ a - zäöüß ]+) " ;
liefert
53
6.7 Effiziente Stringbehandlung
Reguläre
Ausdrücke
Such
Ersetzungs
Aufgaben
Muster
Suchverfahren
Typische
Einsatzgebiete
Regulären
Ausdrücke
6.7 Effiziente Stringbehandlung
Die String-Methoden erzeugen bei jeder Veränderung eines Strings eine neue Kopie auf dem
Heap und allozieren dafür Speicherplatz, den der Garbage-Collector später wieder freigeben muss.
Die StringBuilder-Klasse verwaltet Zeichenketten intern in einem großzügig dimensionierten
Puffer, den die Klasse selten vergrößern muss. Die StringBuilder-Klasse verwaltet den internen
Puffer selbst und vergrößert ihn bei Bedarf, immer in Zweierpotenzen.
StringBuilder hat einen mehrfach überladenen Konstruktor, der z.B. einen String als Argument akzeptiert und/oder eine Längenangabe für die erste Pufferplatzreservierung.
Die folgende Tabelle gibt die Properties und Methoden der StringBuilder-Klasse an:
Methode
Capacity
Chars
Length
MaxCapacity
Append()
AppendFormat()
AppendLine()
CopyTo()
EnsureCapacity()
Equals()
GetHashCode()
GetType()
Insert()
Aufgabe
Liest oder setzt die aktuelle Puffergröße (darf nicht kleiner sein als die
Stringlänge).
Der Indexer: Auf StringBuilder-Instanzen kann man über die ArraySchreibweise lesend und schreibend zugreifen.
Kürzt den String auf die angegebene Länge bzw. verlängert ihn mit
Leerzeichen.
Liefert Maximalgröße des Puffers. In einigen Implementationen ist dies
der mit einer Int32 maximal darstellbare Wert.
Hängt die Stringdarstellung des übergebenen Objekts an die Darstellung des aktuellen Objekts an.
Hängt die Stringdarstellung des übergebenen Formatstrings und seiner
Argumente an die Darstellung des aktuellen Objekts an.
Hängt ein Zeilenende oder den übergebenen String gefolgt von einem
Zeilenende an.
Kopiert einen Teilstring und gibt ein Character-Array zurück.
Vergrößert den internen Buffer auf mindestens den gewünschten Kapazitätswert, falls nötig.
Vergleicht auf Gleichheit mit einem Objekt oder einer StringBuilderInstanz.
Liefert einen möglichst string-eindeutigen Wert.
Liefert eine Instanz von System.Type, mit deren Hilfe das Laufzeitsystem mehr über den StringBuilder erfahren kann.
Fügt den übergebenen String an der angegebenen Position in den aktuellen String ein.
Fortsetzung auf der nächsten Seite Û
6 Strings
54
Methode
ReferenceEquals()
Remove()
Replace()
ToString()
Aufgabe
Prüft, ob zwei Referenzen auf das selbe Objekt zeigen.
Löscht die angegeben Zahl von Zeichen ab der angegebenen Position.
Ersetzt alle Vorkommen des einen Strings durch den andern String.
Konvertiert den Pufferinhalt des StringBuilder-Objekts in einen String.
Tabelle 6.5: Methoden und Properties von StringBuilder
55
7 Collections
Oft gehören mehrere Objekte logisch zusammen in eine Sammlung (Collection). Das array ist
die wohl bekannteste Collection. Mit der Einführung von .NET 2.0 stehen der Programmiererin
eine Reihe neuer typisierter Sammlungen zur Verfügung: List, Dictionary, SortedList,
LinkedList, Queue und Stack. Sie finden sich im Namespace System.Collections.Generic.
7.1 List<T>
Die Klasse List<T> ist eine array-ähnliche Sammlung von Objekten, die dynamisch wachsen
oder schrumpfen kann. Sie implementiert die Interfaces IList<T>, ICollection<T> und IEnumerable<T>. Darüber hinaus unterstützt sie Sortieren und Umdrehen der Sammlung.
Methode
Capacity
int Count
Item
void Add(element)
void AddRange(collection)
int BinarySearch(element)
void Clear()
bool Contains(element)
void CopyTo(array, index)
bool Exists(Bedingung<T>)
Knoten Find(element)
List<T> FindAll(Bedingung<T>)
int FindIndex(Bedingung<T>)
T FindLast(Bedingung<T>)
int FindLastIndex(Bedingung<T>)
void ForEach(Action<T>)
Aufgabe
Liest oder schreibt die maximale Zahl der Listeneinträge, die die Liste ohne Größenveränderung fassen
kann.
Liefert die Zahl der Listeneinträge zurück
Liest oder setzt ein Element an einer bestimmten Position: Der Indexer.
Hängt ein Element an die Liste an.
Hängt alle Elemente in der Sammlung ans Ende der
Liste
Sucht das Element in der Liste.
Leert die Liste.
Stellt fest, ob das Element in der Liste ist.
Kopiert alle Elmente der Liste ab einem bestimmten
Index in das übergebene Array.
Stellt fest, ob die Liste Elemente enthält, die der
angegebenen Bedingung genügen.
Findet den ersten Knoten, der das Element speichert.
Liefert eine Liste mit allen Elementen, die der angegebenen Bedingung genügen.
Liefert die Position des Elements, das der angegebenen
Bedingung genügt.
Liefert das letzte Element, das der angegebenen Bedingung genügt.
Liefert die Position des letzten Elements, das der
angegebenen Bedingung genügt.
Führt das angegebene Action-Delegate für jedes Element der Liste durch.
Fortsetzung auf der nächsten Seite Û
7 Collections
56
Methode
List<T> GetRange(index,
count)
int IndexOf(element))
void Insert(position, element)
void InsertRange(position,
sammlung)
int LastIndexOf(element)
bool Remove(element)
int RemoveAll(bedingung)
void RemoveAt(position)
void RemoveRange(position,
anzahl)
voidReverse()
void Sort()
T[] ToArray()
void TrimExcess(groeße)
bool TrueForAll(bedingung)
Aufgabe
Liefert eine flache Kopie der Elemente aus dem
angegebenen Teil der Liste.
Gibt die Position des ersten Auftretens des ELements
in der Liste zurück.
Fügt das Element an der angegebenen Position ein.
Fügt eine Sammlung von Elementen an der angegebenen Position ein.
Gibt die Position des letzten Auftretens des Elements
in der List an.
Entfernt das erste Auftreten des Elements aus der Liste
und liefert bei Erfolg true.
Entfernt alle Elemente, die einer bestimmten Bedingung
gehorchen und liefert die Zahl der entfernten Elemente
zurück.
Entfernt das Element an der angegebenen Position.
Entfernt eine gegebene Anzahl von Elementen ab einer
bestimmten Position.
Dreht die Reihenfolge der Elemente in der Liste um.
Sortiert die Elemente der Liste. Überladen für eigene
Sortierkriterien.
Liefert die Elemente der Liste als Array zurück.
Kürzt die Liste auf die angegebene Größe, wenn sie zu
weniger als 90% gefüllt ist.
Prüft, ob eine bestimmte Bedingung für alle Elemente
der Liste erfüllt ist.
Tabelle 7.1: Wichtigste Methoden und Properties von List<T>
Im folgenden Beispiel nimmt die neue Instanz der List-Klasse nur Elemente vom Typ Punkt
auf. Da die Klasse IEnumerable unterstützt, funktioniert das foreach-Konstrukt bei Console.WriteLine(). Das Überschreiben von Equals() ist notwendig, damit das Laufzeitsystem
beim Suchen nach einem Element bei Remove() nicht nur die Referenzen vergleichen kann,
sondern auch auf die Inhalte achten.
using System ;
using System . Collections . Generic ;
class Program
{
static void Main ( string [] args )
{
List < Punkt > Dreieck = new List < Punkt >() ;
Punkt p3 = new Punkt (5 ,6) ;
Dreieck . Add ( new Punkt (3 , 4) ) ;
Dreieck . Add ( new Punkt (1 , 2) ) ;
Dreieck . Add ( p3 ) ;
foreach ( Punkt p in Dreieck )
Console . WriteLine ( " Punkt : {0: p } " , p ) ;
Dreieck . Remove ( new Punkt (3 , 4) ) ;
7.1 List<T>
57
System . Console . WriteLine () ;
foreach ( Punkt p in Dreieck )
Console . WriteLine ( " Punkt : {0: p } " , p ) ;
Dreieck . Remove ( p3 ) ;
System . Console . WriteLine () ;
foreach ( Punkt p in Dreieck )
Console . WriteLine ( " Punkt : {0: p } " , p ) ;
}
}
class Punkt : IFormattable
{
public double x , y ;
public Punkt ( double x , double y )
{
this . x = x ; this . y = y ;
}
public string ToString ( string format ,
IFormatProvider formatProvider )
{
if ( format == null ) return ToString () ;
if ( format == " p " )
return String . Format ( " ({0} ,{1}) " ,
x, y);
return ToString () ;
}
public override bool Equals ( object obj )
{
Punkt other = obj as Punkt ;
return ( other . x == this . x && other . y == this . y ) ;
}
}
ergibt folgende Ausgabe:
Punkt : (3 ,4)
Punkt : (1 ,2)
Punkt : (5 ,6)
Punkt : (1 ,2)
Punkt : (5 ,6)
Punkt : (1 ,2)
Selbstgeschriebene Bedingungen beim Durchlaufen der Liste haben den Returntyp bool und
als Argumenttyp den Typ eines Listenelements:
using System ;
using System . Collections . Generic ;
class Program
{
static void Main ( string [] args )
{
7 Collections
58
List < string > Liste = new List < string >() ;
Liste . Add ( " Ene " ) ;
Liste . Add ( " Mene " ) ;
Liste . Add ( " Muh " ) ;
Liste . Add ( " und drauß bist du ! " ) ;
foreach ( string s in Liste )
Console . WriteLine ( s ) ;
Liste . RemoveAll ( ContainsE ) ;
Console . WriteLine () ;
foreach ( string s in Liste )
Console . WriteLine ( s ) ;
}
private static bool ContainsE ( string s )
{
return s . Contains ( " e " ) ;
}
}
ergibt folgende Ausgabe:
Ene
Mene
Muh
und drauß bist du !
Muh
und drauß bist du !
7.2 LinkedList<T>
In der LinkedList<T> trägt jedes Element außer seinem Wert jeweils eine Referenz auf das davor
kommende und auf das danach folgende Element. Das ermöglicht, zur Laufzeit schnell Elemente
aus dem Innern der Liste entfernen oder neue Elemente hinzufügen zu können. Anders als z.B.
bei der Queue brauchen hier die andern Elemente nicht aufzurücken oder Platz zu machen. Dafür
erfolgt der Zugriff auf Elemente im Innern der Liste langsamer, weil sich das Laufzeitsystem erst
über die Vorwärts- bzw. Rückwärts-referenzen bis zu der gewünschten Stelle hinhangeln muss.
Die LinkedList hat 3 Properties zum Zugriff auf den Anfang, das Ende und die Elementzahl
der Liste sowie eine Reihe von Methoden zum Einfügen, Löschen und Suchen in der Liste. Die
folgende Aufstellung zeigt die wichtigsten von ihnen:
Methode
int Count
T First
T Last
T AddAfter(knoten, element)
T AddBefore(knoten, element)
Aufgabe
Liefert die Zahl der Listeneinträge zurück
Referenz auf den Listenanfang
Referenz auf das Listenende
Fügt ein neues Element oder einen neuen Knoten nach
dem angegebenen Knoten in die LinkedList ein.
Fügt ein neues Element oder einen neuen Knoten vor
dem angegebenen Knoten in die LinkedList ein.
Fortsetzung auf der nächsten Seite Û
59
7.2 LinkedList<T>
Methode
void AddFirst(element)
Aufgabe
Fügt ein Element oder einen Knoten als erstes Element
der Liste ein.
Fügt ein Element oder einen Knoten als letztes Element
der Liste ein.
Leert die Liste
Prüft, ob ein Element in der Liste gespeichert ist.
Kopiert alle Elmente der Liste ab einem bestimmten
Index in das übergebene Array.
Findet den ersten Knoten, der das Element speichert.
Findet den letzten Knoten, der das Element speichert.
Entfernt das erste Auftreten des Elements aus der Liste
und liefert true, falls die Operation erfolgreich war.
Entfernt das erste Element der Liste
Entfernt das letzte Element der Liste
void AddLast(element)
void Clear()
bool Contains(element)
void CopyTo(array, index)
Knoten Find(element)
Knoten FindLast(element)
bool Remove(element)
void RemoveFirst()
void RemoveLast()
Tabelle 7.2: Methoden und Properties von LinkedList<T>
Die einzelnen Elemente der LinkedListNode<T> Previous stecken dabei in Objekten vom
Typ LinkedListNode<T>, die ihrerseits Properties zum Zugriff auf den Element-Inhalt sowie
das vorhergehende und nachfolgende Element bieten:
LinkedList<T> List
Liefert die LinkedList zurück, zu der der Knoten gehört.
LinkexListNode<T> Next
Ergibt das nachfolgende Element in der Liste.
LinkedListNode<T> Previous Ergibt das vorhergehende Element in der Liste.
T Value
Liefert das in der Liste gespeicherte Element.
Das folgende Beispielprogramm
ˆ Fügt mit AddLast() das erste Element (1,1) der Liste ein und merkt sich dieses Element
in Knoten n.
ˆ Hängt mit AddLast() das nächste Element (2,2) an die Liste an
ˆ Hängt mit AddLast() noch ein Element (3,3) an die Liste an
ˆ Fügt nach dem ersten Element (1,1) mit AddAfter() das vierte Element (4,4) ein
ˆ Stellt vor das allererste Element der Liste mit AddFirst() das fünfte Element (5,5)
using System ;
using System . Collections . Generic ;
class Program
{
static void Main ( string [] args )
{
LinkedList < Complex > Liste = new LinkedList < Complex >() ;
LinkedListNode < Complex > n ;
n = Liste . AddLast ( new Complex (1 , 1) ) ;
Liste . AddLast ( new Complex (2 , 2) ) ;
Liste . AddLast ( new Complex (3 , 3) ) ;
Liste . AddAfter (n , new Complex (4 , 4) ) ;
Liste . AddFirst ( new Complex (5 , 5) ) ;
7 Collections
60
foreach ( Complex c in Liste )
Console . WriteLine ( c ) ;
}
}
class Complex
{
protected float re , im ;
public Complex ( float re , float im )
{
this . re = re ; this . im = im ;
}
public override string ToString ()
{
return " ( " + re + " ," + im + " i ) " ;
}
}
Die Liste hat danach folgenden Aufbau
(5 ,5 i )
(1 ,1 i )
(4 ,4 i )
(2 ,2 i )
(3 ,3 i )
7.3 Queue<T>
Die Queue<T>-Klasse implementiert einen First-In-First-Out-Speicher (FiFo) oder auch
Warteschlange. Die wichtigsten Methoden und Properties von Queue<T> sind:
Methode
Count
void Clear()
bool Contains(element)
void CopyTo(array, index)
T Dequeue()
void Enqueue(element)
T Peek()
T[] ToArray()
void TrimExcess()
Aufgabe
Property, die die Zahl der Einträge in der Schlange
zurückliefert
Löscht alle Elemente in der Schlange.
Prüft, ob ein Element in der Schlange gespeichert ist.
Kopiert alle Elmente der Liste ab einem bestimmten
Index in das übergebene Array.
Entfernt das Element am Anfang der Schlange und
liefert es zurück.
Hängt ein Element an das Ende der Schlange an.
Liefert eine Vorschau auf das erste Element der
Schlange ohne es zu entfernen.
Liefert ein Array mit den Elementen in der Schlange
zurück.
Verkürzt die Schlangenlänge auf die Zahl der aktuellen
Einträge, falls die Schlange zu weniger als 90% voll ist.
Fortsetzung auf der nächsten Seite Û
61
7.3 Queue<T>
Methode
Aufgabe
Tabelle 7.3: Methoden und Properties von Queue<T>
Das folgende Beispielprogramm
ˆ legt eine neue Schlange für komplexe Zahlen an
ˆ holt das vorderste Element der Schlange heraus und zeigt es an
ˆ gibt eine Leerzeile aus
ˆ gibt den Inhalt der restlichen Schlange aus
using System ;
using System . Collections . Generic ;
class Program
{
static void Main ( string [] args )
{
Queue < Complex > Schlange = new Queue < Complex >() ;
Schlange . Enqueue ( new Complex (1 , 1) ) ;
Schlange . Enqueue ( new Complex (2 , 2) ) ;
Schlange . Enqueue ( new Complex (3 , 3) ) ;
Console . WriteLine ( Schlange . Dequeue () ) ;
Console . WriteLine () ;
foreach ( Complex c in Schlange )
Console . WriteLine ( c ) ;
}
}
class Complex
{
protected float re , im ;
public Complex ( float re , float im )
{
this . re = re ; this . im = im ;
}
public override string ToString ()
{
return " ( " + re + " ," + im + " i ) " ;
}
}
und es ergibt sich die Ausgabe
(1 ,1 i )
(2 ,2 i )
(3 ,3 i )
7 Collections
62
7.4 Stack<T>
Die Klasse Stack<T> stellt einen First-In-Last-Out-Speicher oder Stapel dar. Das zuletzt zugefügte Element ist das erste Element, das den Stack verlässt.
Methode
void CopyTo(array, index)
T Peek()
T Pop()
void Push(element)
T[] ToArray()
void TrimExcess()
Aufgabe
Kopiert alle Elmente des Stacks ab einem bestimmten
Index in das übergebene Array.
Liefert eine Vorschau auf das erste Element der
Schlange ohne es zu entfernen.
Entfernt das zuletzt gespeicherte Element und gibt es
zurück.
Speichert ein Element auf dem Stack.
Liefert ein Array mit den Elementen im Stack zurück.
Verkürzt die Schlangenlänge auf die Zahl der aktuellen
Einträge, falls die Schlange zu weniger als 90% voll ist.
Tabelle 7.4: Methoden und Properties von Stack<T>
Das folgende Programm
ˆ schreibt drei Werte auf den Stack
ˆ zeigt sie an
ˆ entnimmt einen Wert und zeigt ihn an
ˆ zeigt die verbleibende Liste an
using System ;
using System . Collections . Generic ;
class Program
{
static void Main ( string [] args )
{
Stack < string > Stapel = new Stack < string >() ;
Stapel . Push ( " Eins " ) ;
Stapel . Push ( " Zwei " ) ;
Stapel . Push ( " Drei " ) ;
foreach ( string s in Stapel )
Console . WriteLine ( s ) ;
Console . WriteLine ( " \ nEntnommen : " +
Stapel . Pop () + " \ n " ) ;
foreach ( string s in Stapel )
Console . WriteLine ( s ) ;
}
private static bool ContainsE ( string s )
{
return s . Contains ( " e " ) ;
}
}
und es ergibt sich die Ausgabe
63
7.5 Dictionary<TKey,TValue>
Drei
Zwei
Eins
Entnommen : Drei
Zwei
Eins
7.5 Dictionary<TKey,TValue>
Ein Dictionary speichert Schlüssel-Werte-Paare und erlaubt assoziativen Zugriff auf die Werte
über Schlüssel. Die generischen Typen beim Anlegen des Dictionaries geben Typ vom Schlüssel
und Typ des Werts an. Die wichtigsten Properties und Methoden sind:
Methode
int Count
T Item[schlüssel]
Keys
Values
void Add(schlüssel, wert)
void Clear()
bool ContainsKey(schlüssel)
bool ContainsValue(wert)
bool Remove(schlüssel)
bool TryGetValue(schlüssel,
out wert)
Aufgabe
Liefert die Anzahl der Schlüssel-/Werte-Paare im Dictionary.
Setzt und liest das Element mit dem angegebenen
Schlüssel.
Liefert eine Sammlung mit allen Schlüsseln.
Liefert eine Sammlung mit allen Werten.
Fügt den Wert unter dem angegebenen Schlüssel ein.
Leert das Dictionary.
Bestimmt, ob ein Schlüssel im Dictionary auftaucht.
Bestimmt, ob ein Wert im Dictionary auftaucht.
Entfernt das Element mit dem angegenen Schlüssel und
meldet Erfolg.
Liefert den Wert unter dem angegebenen Schlüssel und
meldet Erfolg.
Tabelle 7.5: Methoden und Properties von Dictionary<T>
7.6 SortedList
Die sortierte Liste ist identisch zum Dictionary, nur dass die einzelnen Einträge im Dictionary
nach dem Schlüssel geordnet sind.
7.7 foreach über eigene Sammlungen
Zusätzlich zu den normalen Schleifen-Konstruktionen wie for und while besitzt C# auch noch
die foreach-Schleife, die durch alle Elemente einer Sammlung iteriert.
String [] getraenke = { " Bier " , " Milch " , " Wasser " };
foreach ( String g in getraenke )
{
Console . WriteLine ( g ) ;
}
Damit derartige Iterationen auch über selbsterstellte Klassen möglich sind, stellt das .NET Framework 2.0 folgende Interfaces zur Verfügung:
64
7 Collections
public interface IEnumerable < ItemType >
{
IEnumerator < ItemType > GetEnumerator () ;
}
public interface IEnumerator < ItemType > : IDisposable
{
ItemType Current { get ;}
bool MoveNext () ;
}
Dabei gibt es keine Reset-Methode mehr wie noch im .NET Framework 1.0.
Der Code für das Getränke-Beispiel sieht für generische Klassen folgendermaßen aus:
public class CityCollection : IEnumerable < string >
{
string [] m_Cities = { " New York " ," Paris " ," London " };
public IEnumerator < string > GetEnumerator ()
{
for ( int i = 0; i < m_Cities . Length ; i ++)
yield return m_Cities [ i ];
}
}
65
8 Delegates, Events und Exceptions
Von C und C++ kennt die Programmiererin den Einsatz von Funktionszeigern, um sogenannte
Callback-Funktionen einzurichten, die das Programm beim Eintreten eines bestimmten Ereignisses zurückruft. Das Windows-Betriebssystem arbeitet sehr viel mit Callback-Funktionen. Der CCompiler hat diesen Aufruf in den Aufruf einer Funktion an einer bestimmten Adresse im Speicher
übersetzt. Dabei geriet der Rückgabewert der Funktion und deren Parameter in Vergessenheit.
C# realisiert diese Aufrufe als Methodenaufrufe typ-sicher in Form von Delegates und deren
Spezialform, den Events.
8.1 Delegates
Meist verwenden Methoden übergebene Zahlenwerte oder Objekte als Parameter, um mit diesen
zu arbeiten. In einigen Fällen soll eine Methode nicht einen Wert (Zahl oder Objekt) erhalten,
sondern eine übergebene Methode aufrufen, die von Methodenaufruf zu Methodenaufruf unterschiedlich sein kann. Dies kann z.B. eine Rückruf-Funktion (Callback-Function) sein, die das
Programm aufrufen soll, wenn ein Ereignis eintritt oder beispielsweise eine Vergleichsfunktion, die
einer Sortier-Routine das Vergleichen abnimmt. Delegates dienen zum typ-sicheren Übergeben
von Methoden an andere Methoden, indem sie einer Methoden-Signatur einen Namen geben. Ein Delegate
Dazu muss zur Methoden-Signatur selbst noch ein verträglicher Rückgabewert dazukommen.
gibt einer
MethodenSignatur
8.1.1 Deklaration eines Delegates
einen Namen
Wie bei normalen Klassen auch unterscheidet man bei Delegates in die Deklaration und in die Instantiierung. Die Deklaration kann dort erfolgen, wo auch Klassendeklarationen erfolgen können.
Bei der Deklaration erfährt der Compiler, welche Art von Methode das Delegate darstellt.
Im folgenden Beispiel für die Deklaration eines Delegates erfährt der Compiler, dass das Delegate Methoden vertreten kann, die einen Integerwert als Rückgabewert haben und zwei Integerwerte als Parameter. Das so deklarierte Delegate kann nun als Stellvertreter für alle Methoden
stehen, die 2 Integerparameter haben und einen Integerwert zurückliefern. Im Fall des Beispiels
steht das Delegate einmal für die Addition und einmal für die Multiplikation:
using System ;
class Program
{
delegate int ZweiIntOP ( int x , int y ) ;
static void Main ( string [] args )
{
ZweiIntOP o ;
o = new ZweiIntOP ( Addiere ) ;
Console . WriteLine ( " Addition :
" + o (4 , 2) ) ;
o = new ZweiIntOP ( Multiplizere ) ;
Console . WriteLine ( " Multiplikation : " + o (4 , 2) ) ;
8 Delegates, Events und Exceptions
66
}
public static int Addiere ( int a , int b )
{
return a + b ;
}
public static int Multiplizere ( int a , int b )
{
return a * b ;
}
}
ergibt folgende Ausgabe, die belegt, dass o(4,2) einmal wie ein Additions-Operator und einmal
wie ein Multiplikations-Operator gewirkt hat:
Addition :
6
Multiplikation : 8
Seit .NET 2.0 ist auch das direkte Zuweisen von Operationen ohne den new-Operator möglich:
o = Addiere ;
Console . WriteLine ( " Addition :
" + o (4 , 2) ) ;
o = Multiplizere ;
Console . WriteLine ( " Multiplikation : " + o (4 , 2) ) ;
Hinter
der Und letztendlich brauchen die Methoden auch keine Namen mehr zu haben. Dann handelt es
sich um anonyme Methoden. Das Schlüsselwort delegate kennzeichnet solche Methoden.
anonymen
Methode
Da dies eigentlich ein normales Statement und kein Block ist, muss ein Semikolon folgen
muss
ein
o = delegate ( int a , int b )
Semikolon
{
folgen
return a + b ;
};
Console . WriteLine ( " Addition :
" + o (4 , 2) ) ;
o = Multiplizere ;
Console . WriteLine ( " Multiplikation : " + o (4 , 2) ) ;
Das abschließende Beispiel verwendet eine Liste mit Methoden, um sie der Reihe nach auf zwei
Zahlen anzuwenden. So könnten z.B. einfach Tabellen mehrerer mathematischer Funktionen
entstehen:
using System ;
using System . Collections . Generic ;
class Program
{
delegate int IntOP ( int x ) ;
static void Main ( string [] args )
{
List < IntOP > OpList = new List < IntOP >() ;
OpList . Add ( Quadrat ) ;
OpList . Add ( Kubik ) ;
OpList . Add ( Quart ) ;
Console . WriteLine ( " Zahl , Quadrat , Kubik , Quart " ) ;
67
8.1 Delegates
for ( int i = 0; i <= 10; i ++)
{
Console . Write ( " \ n {0 ,4}
", i);
foreach ( IntOP o in OpList )
Console . Write ( " {0 ,5} " , o ( i ) ) ;
}
}
public static int Quadrat ( int a )
{
return a * a ;
}
public static int Kubik ( int a )
{
return a * a * a ;
}
public static int Quart ( int a )
{
return a * a * a * a ;
}
}
ergibt die Funktionstabelle
Zahl , Quadrat , Kubik , Quart
0
1
2
3
4
5
6
7
8
9
10
0
1
4
9
16
25
36
49
64
81
100
0
1
8
27
64
125
216
343
512
729
1000
0
1
16
81
256
625
1296
2401
4096
6561
10000
8.1.2 Multicast-Delegates
Bis hierher hat ein Delegate immer für eine einzige Method gestanden. Der Aufruf des Delegates
entsprach dem Aufruf der gespeicherten Methoden. Der Aufruf mehrerer Methoden erfolgte durch
das Aufrufen mehrerer Delegates.
Ein Delegate kann jedoch auch mehr als eine Methode aufnehmen. Dann handelt es sich um ein
Multicast-Delegate. Beim Aufruf eines Multicast-Delegates führt dieses jede darin gespeicherte
Methode nacheinander aus. Da dann nicht klar ist, wessen Rückgabewert das Delegate dann
zurückgeben soll, darf keine der Methoden einen Rückgabewert liefern. Der Compiler geht sogar
automatisch davon aus, dass ein Delegate, das void-Methoden kapselt, ein Multicast-Delegate
ist.
Im folgenden Beispiel führt der Aufruf von p(i) zur Ausführung aller vier gespeicherten Funktionen.
using System ;
class Program
MulticastDelegates
kapseln voidMethoden
8 Delegates, Events und Exceptions
68
{
delegate void VoidPrint ( int x ) ;
static void Main ( string [] args )
{
VoidPrint p ;
p
p
p
p
= PrintZahl ;
+= PrintQuadrat ;
+= PrintKubik ;
+= PrintQuart ;
Console . WriteLine ( " Zahl , Quadrat , Kubik , Quart " ) ;
for ( int i = 0; i <= 10; i ++)
{
p(i);
Console . WriteLine () ;
}
}
public static void PrintZahl ( int a )
{
Console . Write ( " {0 ,6} " , a ) ;
}
public static void PrintQuadrat ( int a )
{
Console . Write ( " {0 ,6} " ,a * a ) ;
}
public static void PrintKubik ( int a )
{
Console . Write ( " {0 ,6} " , a * a * a ) ;
}
public static void PrintQuart ( int a )
{
Console . Write ( " {0 ,6} " , a * a * a * a ) ;
}
}
8.2 Events
Windows-Applikationen sind nachrichten-basiert, d.h. die Applikation und Windows kommunizieren über Nachrichten in Schlangen. Bei den Entwurfsmustern der objektorienterten Programmierung warten z.T. Klassen darauf, über Änderungen von Fenstern und Forms informiert
zu werden. Diese Information geschieht über Events.
C# kapselt Events in Delegates und so haben wir bereits das Handwerkszeug gelernt, um Events
zu verwenden. Das .NET Framework hat dafür bereits ein Delegate definiert, den EventHandler
aus System. Dieser EventHandler kapselt Methoden, die keinen Rückgabewert liefern und zwei
Parameter haben: das Objekt, das den Event auslöst und benutzerspezifische Daten.
Namens-Konvention beim Programmieren von Eventhandlern ist object_event, wobei objekt das den Event auslösende Objekt ist und event das ausgelöste Ereignis, z.B. button1_press.
69
8.3 Exceptions
Im folgenden Beispiel findet sich in der partiellen Klasse Form1 die Methode button_Click(object sender, EventArgs e), die eine MessageBox öffnet, in der die Bezeichnung des Buttons steht, der den Event gesendet hat.
using
using
using
using
using
using
using
System ;
System . Collections . Generic ;
System . ComponentModel ;
System . Data ;
System . Drawing ;
System . Text ;
System . Windows . Forms ;
namespace FormSamples
{
public partial class Form1 : Form
{
public Form1 ()
{
InitializeComponent () ;
}
private void Form1_Load ( object sender , EventArgs e )
{
}
private void button_Click ( object sender , EventArgs e )
{
MessageBox . Show ((( Button ) sender ) . Name + " gedrückt " ) ;
}
}
}
Im zweiten Teil der partiellen Klasse sorgen
this . button1 . Click += new System . EventHandler ( this .
button_Click ) ;
this . button2 . Click += new System . EventHandler ( this .
button_Click ) ;
dafür, dass das Laufzeitsystem den selbstgeschriebenen Eventhandler für beide Buttons aufruft.
8.3 Exceptions
Fehler in Applikationen treten nicht nur aufgrund falscher Programmierung auf, sondern z.B.
weil der Anwender inkonsistente Daten eingegeben hat oder weil eine Netzverbindung zusammengebrochen ist. Das Programm sollte diese Fälle vorhersehen können und entsprechend darauf
reagieren. C# macht es der Programmiererin leicht, das Programm auf solche Fälle vorzubereiten:
ˆ C# hat eine Reihe vordefinierter Exception-Klassen, die sich für Fehlerbehandlungen einsetzen lassen
ˆ Programmiererinnen können eigene Exception-Klassen von der C# -eigenen ExceptionKlasse ableiten
ˆ der try-catch-finally-Mechanismus sorgt für einen systematischen Umgang mit den
Exceptions
8 Delegates, Events und Exceptions
70
Oft reicht das einfache Zurückgeben eines Fehlercodes nicht, um angemessen auf eine Ausnahmesituation zu reagieren. Möglicherweise steckt das Programm ja gerade in einem mehrfach
verschachtelten Gebirge aus Methodenaufrufen, die nun alle auf den Fehlercode abprüfen und
entsprechend darauf reagieren müssten. Exceptions in C# sind der einfachere Weg, gerade auch
im Vergleich mit andern Programmiersprachen: nur die Methode, die die Exception wirft und die
Methode, die sie fängt, müssen sich um die Exception kümmern.
Die Grundlagen für das Exception-System bilden zwei Klassen aus dem Namespace System:
SystemException
Exceptions dieser oder davon abgeleiteter Klassen
ˆ gehören zur Funktion des .NET-Runtime-Systems oder
ˆ sind so allgemein, dass die Art von Ausnahmen in allen Klassen
auftreten kann
ApplicationException Diese Klasse bildet die Grundlage für benutzerdefinierte Exceptions.
Eigene Klassen leiten von ihr ab zur Definition neuer und anwendungsspezifischer Ausnahmen.
8.3.1 Exceptions werfen
Das C# -Schlüsselwort throw löst eine Exception aus:
using System ;
class Program
{
static void Main ( string [] args )
{
int d = Convert . ToInt16 ( Console . ReadLine () ) ;
int e = Convert . ToInt16 ( Console . ReadLine () ) ;
Console . WriteLine ( teile (d , e ) ) ;
}
static int teile ( int dividend , int divisor )
{
if ( divisor == 0)
throw new DivideByZeroException () ;
return dividend / divisor ;
}
}
Hier reagiert das Programm mit einer Exception auf die Eingabe von 0 als Divisor:
4
0
Unhandled Exception : System . DivideByZeroException : Attempted to divide
by zero .
at Program . teile ( Int32 dividend , Int32 divisor ) in Program . cs : line
16
at Program . Main ( String [] args ) in Program . cs : line 10
8.3.2 Exceptions fangen
Code, der mit Exceptions umgeht, teilt sich in drei Blöcke:
8.3 Exceptions
71
try
Im try-Block steht der normale Programmcode, bei dessen Ausführung möglicherweise
ein Fehler auftreten kann.
catch
Ein catch-Block behandelt eine Fehlersituation, die bei Ausführung des try-Blocks
aufgetreten ist – falls sie aufgetreten ist. Mehrere catch-Blöcke fangen mehrere
mögliche Exceptionarten ab.
finally Den Code im finally-Block führt das Laufzeitsystem auf jeden Fall aus, entweder
nach regulärem Durchlaufen des try-Blocks oder in Anschluss an die Ausnahmebehandlung im catch-Block. Ein finally-Block ist nicht nötig, falls das Laufzeitsystem
keine “Aufräumarbeiten” zu erledigen hat.
Im folgenden Bispiel fängt der erste catch-Block gezielt die Division durch Null ab. Der zweite
catch-Block sammelt alle Exceptions ein, die im try-Block aufgetreten sind, wie z.B. die leere
Eingabe, mit der Convert.ToInt16 nichts anfangen kann.
static void Main ( string [] args )
{
int d , e , f ;
try
{
d = Convert . ToInt16 ( Console . ReadLine () ) ;
e = Convert . ToInt16 ( Console . ReadLine () ) ;
f = teile (d , e ) ;
}
catch ( DivideByZeroException ex )
{
Console . WriteLine ( " DivideByZeroException aufgetreten ! " ) ;
f = int . MaxValue ;
}
catch
{
Console . WriteLine ( " Andere Exception aufgetreten ! " ) ;
}
Console . WriteLine ( f ) ;
}
Exceptions, für die kein catch-Block existiert, fängt das Laufzeitsystem ab.
8.3.3 Eigene Exceptions
Eigene Exceptions leiten sich von der Klasse System.Exception ab und können einen selbstdefinierten Text mit sich tragen, der die Ursache für die Ausnahme weiter detailliert.
using System ;
class Program
{
public enum getraenk : byte
{
Bier , Wein , Milch ,
Met , Wasser , Absynth
}
static void Main ( string [] args )
{
string [] gs = Enum . GetNames ( typeof ( getraenk ) ) ;
8 Delegates, Events und Exceptions
72
foreach ( String s in gs )
trinke ( s ) ;
}
static void trinke ( string getraenk )
{
if ( getraenk != " Wasser " )
Console . WriteLine ( " Trinke " + getraenk ) ;
else throw new UndrinkableException ( getraenk ) ;
}
}
class UndrinkableException : ApplicationException
{
public UndrinkableException ( string getraenk ) :
base ( " Untrinkbar : " + getraenk )
{
}
}
8.3.4 Verschachtelte Exceptions
Oft kann ein catch-Block auf eine Exception nicht abschließend reagieren, hat aber neue Erkenntnisse, die er an die endgültige Fehlermeldung anhängen kann. Ohne Verlust von Exception-Details
funktioniert das mit “inneren Exceptions”. Der Konstruktor der System.Exception-Klasse ist
dazu überladen mit einer Version mit zwei Argumenten, dem Exception-Text und einer Exception, die von weiter unten aus der Verschachtelungshierarchie kommt.
using System ;
class Program
{
static void Main ( string [] args )
{
DoOuter () ;
}
public static void DoOuter ()
{
try
{
DoInner () ;
}
catch ( Exception e )
{
throw new OuterException ( " in Catch ! " , e ) ;
}
}
public static void DoInner ()
{
throw new InnerException ( " von Hand " ) ;
}
}
8.3 Exceptions
73
class OuterException : ApplicationException
{
public OuterException ( string grund , Exception e )
: base ( "O - Exception : " + grund , e )
{
}
}
class InnerException : ApplicationException
{
public InnerException ( string grund )
: base ( "I - Exception : " + grund )
{
}
}
ergibt folgende Ausgabe:
Unhandled Exception : OuterException : O - Exception : in Catch ! --->
InnerException :
I - Exception : von Hand
at Program . DoInner () in Program . cs : line 24
at Program . DoOuter () in Program . cs : line 14
--- End of inner exception stack trace --at Program . DoOuter () in Program . cs : line 18
at Program . Main ( String [] args ) in Program . cs : line 7
Nun könnte das Programm auch die Exceptions selbst abfangen, statt das dem Laufzeitsystem
zu überlassen:
static void Main ( string [] args )
{
try
{
DoOuter () ;
}
catch ( Exception e )
{
while ( e != null )
{
Console . WriteLine ( " Uuups : " + e . Message ) ;
e = e . InnerException ;
}
}
}
und das Ergebnis zeigt, dass das Programm alle Exceptions erkannt hat und gegebenenfalls auf
jede reagieren könnte:
Uuups : O - Exception : in Catch !
Uuups : I - Exception : von Hand
74
9 Threads
Bis hierher starteten alle Programme bei der Main-Methode und es gab einen einzigen
Ausführungs-Pfad durch das Programm. Ein Thread ist ein unabhängiger Ausführungs-Pfad in
einem Programm. Threads sorgen beispielsweise dafür, dass ein Programm auf Benutzereingaben
reagieren kann, während es eine Datei lädt oder eine längerdauernde Berechnung anstellt.
Das .NET-Laufzeitsystem kann mehrere unabhängige Ausführungs-Pfade (Threads) gleichzeitig verwalten. Den ersten Ausführungspfad startet das Laufzeitsystem bei Programmstart, indem
es ihn mit der Main()-Methode anfangen lässt.
Methode
Start()
Name
Suspend()
Resume()
Abort()
Join()
CurrentThread
Sleep(zeit)
Priority
Aufgabe
Startet die Ausfühung des Threads.
Name des Threads. Für’s Debugging empfiehlt sich hier ein verständlicher Name.
Hält den Thread zeitweilig an.
Setzt die Ausführung eines angehaltenen Threads fort.
Bricht die Ausführung eines Threads komplett ab. Das Laufzeitsystem
wirft dazu eine Ausnahme vom Typ ThreadAbortException, die
keine Methode abfängt - gibt so aber den entsprechenden finallyBlöcken Gelegenheit, ihren Nachlass zu ordnen.
Wartet, bis ein mit Abort() abgebrochener Thread wirklich zu Ende
ist.
Statische Property, die das Thread-Objekt des gerade laufenden
Threads zurückliefert.
Statische Methode, die den aktuellen Thread für eine bestimmte Zeit
(in Millisekunden) blockiert.
Property die die Priorität des Threads angibt oder verändert: Highest, AboveNormal, Normal, BelowNormal und Lowest.
Tabelle 9.1: Die wichtigsten Methoden und Properties von Thread
9.1 Threads erzeugen und verwalten
Die Programmiererin erzeugt einen neuen Thread, indem sie ein neues Objekt der Klasse Thread
aus dem Namespace System.Threading anlegt. Damit der Thread an der richtigen Stelle mit
der Ausführung anfängt, erhält er als Konstruktor-Parameter den Startpunkt mitgegeben. Der
Startpunkt ist ein Delegate vom Typ ThreadStart ohne Parameter und mit einem leeren Rückgabewert.
Das nachfolgende Beispielprogramm legt erst einen Einsprungpunkt fest, dann erzeugt es den
eigentlichen Thread, gibt ihm einen verständlichen Namen und startet dann den Thread:
using System ;
using System . Threading ;
class Program
{
static void Main ( string [] args )
{
9.1 Threads erzeugen und verwalten
75
// Einsprungpunkt festlegen
ThreadStart SchleifenThreadStart
= new ThreadStart ( Schleife ) ;
// Thread erzeugen
Thread SchleifenThread
= new Thread ( SchleifenThreadStart ) ;
// Menschenlesbarer Name
SchleifenThread . Name = " Schleifen - Thread " ;
// Thread starten
SchleifenThread . Start () ;
}
static void Schleife ()
{
for ( int i = 0; i < 1000; i ++)
Console . WriteLine (i , i * 2 * Math . PI / 360) ;
}
}
Unter Verwendung anonymer Methoden für das Delegate ThreadStart:
using System ;
using System . Threading ;
class Program
{
static void Main ( string [] args )
{
// Thread erzeugen
Thread SchleifenThread
= new Thread ( delegate ()
{
for ( int i = 0; i < 1000; i ++)
Console . WriteLine ( " {0 ,3}:{1} " ,
i , Math . Sin ( i * 2 * Math . PI / 360) ) ;
}) ;
// Menschenlesbarer Name
SchleifenThread . Name = " Schleifen - Thread " ;
// Thread starten
SchleifenThread . Start () ;
}
}
Das folgende Beispielprogramm startet in einem zweiten Thread eine Schleife, die im Sekundentakt von 10 aus rückwärts bis 0 zählt, bricht diese Schleife nach 5 Sekunden ab und wartet, bis
der abgebrochene Thread fertig ist, um dann eine Programm-Ende-Meldung abzugeben:
using System ;
using System . Threading ;
class Program
{
static void Main ( string [] args )
{
9 Threads
76
// Einsprung angeben
ThreadStart ts = new ThreadStart ( CountDown ) ;
// Thread erzeugen
Thread CountDownThread
= new Thread ( ts ) ;
// Menschenlesbarer Name
CountDownThread . Name = " CountDown - Thread " ;
// Thread starten
CountDownThread . Start () ;
// 5 Sekunden warten
Thread . Sleep (5000) ;
// Andern Thread abbrechen
CountDownThread . Abort () ;
// Auf Abbruch warten
CountDownThread . Join () ;
Console . WriteLine ( " Programm - Ende " ) ;
}
static void CountDown ()
{
for ( int i = 10; i >= 0; i - -)
{
Console . WriteLine ( " {0 ,2} " , i ) ;
Thread . Sleep (1000) ;
}
}
}
9.2 Prioritäten von Threads
Oft sollen Programme im Hintergrund Aufgaben niedriger Priorität durchführen, währen z.B. das
Programm auf Eingaben der Anwenderin wartet. Wenn die Anwenderin dann eine Eingabe macht,
soll das Programm diese mit hoher Priorität abarbeiten. C# kennt den Aufzählungstyp ThreadPriority, um einzelnen Threads ihre Wichtigkeit zuweisen zu können: Highest, AboveNormal,
Normal, BelowNormal und Lowest.
9.3 Synchronisation von Threads
Greifen mehrere Threads auf Variable zu, hängt es oft vom zufälligen Timing ab, welcher Thread
die Variable verändert. So könnten z.B. zwei Threads beide i = i + 1 rechnen und es könnte
- bei einem ursprünglichen Wert von i folgendes passieren:
1. der erste Thread liest i und erhält 3
2. ein Threadwechsel passiert und der zweite Thread setzt fort
3. der zweite Thread liest i und erhält 3
9.3 Synchronisation von Threads
77
4. der zweite Thread schreibt 4
5. ein Threadwechsel passiert und der erste Thread setzt fort
6. der erste Thread schreibt 4
So wird aus der Variablen i mit dem Startwert von 3 nach 2 Additionen von 1 der Wert 4!
Je länger eine Operation auf einer Variablen dauert, um so größer ist die Gefahr eines gleichzeitigen Zugriffs auf die Variable von einem andern Thread aus. Vor allem große Collections
sind hier sehr gefährdet.
Das folgende Beispielprogramm schreibt einen Wert in ein größeres Array und schaut direkt
danach nach, ob der Wert im Array noch drin steht. Bei längeren Schleifendurchläufen kommt
es durchaus zu Schreib-/Lese-Kollisionen.
using System ;
using System . Threading ;
class Program
{
static int [] ia = new int [10000];
static void Main ( string [] args )
{
ThreadStart ts
= new ThreadStart ( Konkurrenz ) ;
Thread KonkurrenzThread
= new Thread ( ts ) ;
KonkurrenzThread . Name
= " Konkurrenz - Thread " ;
KonkurrenzThread . Start () ;
for ( int i = 1000; i < 2000; i ++)
if (! FuelleTeste (1) )
Console . WriteLine ( " Clash in Main bei " + i ) ;
}
static void Konkurrenz ()
{
for ( int i = 2000; i < 3000; i ++)
if (! FuelleTeste (2) )
Console . WriteLine ( " Clash in Konkurrenz bei " + i ) ;
}
static bool FuelleTeste ( int wert )
{
bool ret = true ;
// optimistisch
// Füllen
for ( int i = 0; i < ia . Length ; i ++)
ia [ i ] = wert ;
// Testen
for ( int i = 0; i < ia . Length ; i ++)
{
if ( ia [ i ] != wert )
{
ret = false ;
break ;
}
Zugriff von
Threads auf
Collections
sicher regeln!
9 Threads
78
}
return ret ;
}
}
In C# gibt es mit dem lock-Mechanismus ein einfaches Mittel, um Zugriffe auf Variable zu
synchronisieren. Ein mit lock geklammerter Block erlaubt innerhalb des Blocks exklusiven Zugriff
auf eine Variable. Das Laufzeitsystem suspendiert andere Threads, die versuchen, gleichzeitig auf
die Variable zuzugreifen.
lock ( variable )
{
Statement ( s ) ;
}
Mit diese kleinen Änderung läuft das Programm ohne Konflikte beim Zugriff auf das Array:
static bool FuelleTeste ( int wert )
{
bool ret = true ;
// optimistisch
// das neue lock - Konstrukt
lock ( ia )
{
// Füllen
for ( int i = 0; i < ia . Length ; i ++)
ia [ i ] = wert ;
// Testen
for ( int i = 0; i < ia . Length ; i ++)
{
if ( ia [ i ] != wert )
{
ret = false ;
break ;
}
}
}
return ret ;
}
79
10 Dateien
Alle wichtigen Klassen des Dateisystems befinden sich im Namespace System.IO.
10.1 Die Laufwerke
Die DriveInfo-Klasse von C# informiert über die an einem System angeschlossenen Laufwerke.
Sie hat eine relevante statische Methode, GetDrives(), die ein Array von DriveInfo-Objekten
zurückliefert. Zu jedem dieser DriveInfo-Objekte existieren eine Reihe von Properties, die über
das einzelne Laufwerk informieren:
Property
AvailableFreeSpace
DriveFormat
DriveType
IsReady
Name
RootDirectory
TotalFreeSpace
TotalSize
VolumeLabel
Aufgabe
Gibt den freien Speicher für den aktuellen Benutzer an.
Liefert das Format des Dateisystems, z.B. NTFS oder FAT32.
Liefert den Laufwerkstyp: Fest, Entfernbar, CD-ROM, etc.
Ist das Laufwerk bereit?
Liefert den Namen bzw. Laufwerksbuchstaben.
Gibt das Root-Verzeichnis des Laufwerks zurück.
Gibt den freien Speicher für alle Benutzer an.
Gibt die Laufwerkskapazität zurück.
Liest oder schreibt den Laufwerksbezeichnung.
Tabelle 10.1: Die Properties von DriveInfo
Vor dem Zugriff auf die Laufwerkseigenschaften sollte das Programm mit IsReady sicherstellen, dass das Laufwerk überhaupt die gewünschten Daten liefern kann.
using System ;
using System . IO ;
class Program
{
static void Main ( string [] args )
{
DriveInfo [] dis = DriveInfo . GetDrives () ;
foreach ( DriveInfo di in dis )
{
if ( di . IsReady )
{
Console . WriteLine ( " ===== Laufwerk " + di . Name
+ " ( " + di . DriveFormat + " , " + di . DriveType + " ) " ) ;
Console . WriteLine ( " Name :
" + di . VolumeLabel ) ;
Console . WriteLine ( " Speicher : " + di . AvailableFreeSpace
+ " von " + di . TotalSize ) ;
}
}
}
}
10 Dateien
80
10.2 Verzeichnisse und Dateien
Zum Zugriff auf Verzeichnisse und Dateien kennt C# zwei verschiedene Klassen:
ˆ Die Klassen Directory und File, die nur statische Methoden enthalten. Deren Aufruf
mit Pfad oder Filesystem-Objekt führt dazu, dass sie jedesmal die benötigte Information
neu aus dem Dateisystem einlesen. Dafür muss das Programm nicht erst eine Klasse des
Dateisystems instantiieren.
ˆ Die Klassen DirectoryInfo und FileInfo, die bei mehrfacher Verwendung effektiver
sind, weil sie sich die Informationen aus dem Dateisytem merken.
Methode
Attributes
CreationTime
Exists
Extension
FullName
LastAccessTime
LastWriteTime
Name
Parent
Root
Create()
CreateSubdirectory()
Delete()
GetAccessControl()
GetDirectories()
GetFiles()
MoveTo()
Refresh()
SetAccessControl()
Aufgabe
Setzt oder löscht die Attribute.
Liest oder setzt das Erzeugungsdatum.
Prüft, ob das Objekt existiert.
Liefert die Namenserweiterung.
Liefert den kompletten Pfadnamen zurück.
Datum des letzten Zugriffs.
Datum des letzten Schreibzugriffs.
Name des Objekts.
Mutterverzeichnis.
Wurzelverzeichnis des Pfades.
Verzeichnis erstellen.
Unterverzeichnis erstellen, auch relativ zum aktuellen DirectoryObjekt.
Löscht ein Verzeichnis.
Liefert die Zugriffsrechte.
Liste der Unterverzeichnisse.
Liste der Dateien im aktuellen Verzeichnis.
Verschiebt das ganze Verzeichnis an einen andern Ort.
Aktualisiert die Verzeichnis-Informationen.
Ändert die Zugriffsrechte.
Tabelle 10.2: Die Properties und Methoden von DirectoryInfo
Das folgende Programm gibt, beginnend beim Home-Verzeichnis der Userin, alle Dateien und
Verzeichnisse aus und geht dabei durch alle Unterverzeichnisse:
using System ;
using System . IO ;
class Program
{
static void Main ( string [] args )
{
string Home = Environment . GetFolderPath (
Environment . SpecialFolder . MyDocuments ) ;
RecurseDirs ( Home , 0) ;
}
static void RecurseDirs ( string dir , int depth )
{
DirectoryInfo di = new DirectoryInfo ( dir ) ;
10.2 Verzeichnisse und Dateien
81
DirectoryInfo [] dirs = di . GetDirectories () ;
String indentication = new String ( ’ ’, depth ) ;
foreach ( DirectoryInfo d in dirs )
{
Console . WriteLine ( indentication + d ) ;
RecurseDirs ( d . FullName , depth +2) ;
}
FileInfo [] files = di . GetFiles () ;
Console . ForegroundColor =
( ConsoleColor ) Enum . Parse ( typeof ( ConsoleColor ) , " DarkGray " )
;
foreach ( FileInfo d in files )
{
Console . WriteLine ( indentication + d ) ;
}
Console . ForegroundColor =
( ConsoleColor ) Enum . Parse ( typeof ( ConsoleColor ) , " White " ) ;
}
}
Methode
Attributes
CreationTime
Directory
DirectoryName Stringdarstellung des vollen
Directory-Pfads.
Exists
Extension
FullName
IsReadOnly
LastAccessTime
LastWriteTime
Length
Name
CopyTo()
Create()
Delete()
GetAccessControl()
MoveTo()
Refresh()
Replace()
SetAccessControl()
Aufgabe
Setzt oder löscht die Attribute.
Liest oder setzt das Erzeugungsdatum.
Das dazugehörige Directory-Object.
Prüft, ob das Objekt existiert.
Liefert die Namenserweiterung.
Liefert den kompletten Pfadnamen zurück.
Liest oder setzt die Schreibbererechtigung auf “nur Lesen”.
Datum des letzten Zugriffs.
Datum des letzten Schreibzugriffs.
Dateigröße.
Name des Objekts.
Kopiert die Datei in eine neue Datei.
Datei erstellen.
Löscht eine Datei.
Liefert die Zugriffsrechte.
Verschiebt die Datei an einen andern Ort.
Aktualisiert die Verzeichnis-Informationen.
Überschreibt die Datei durch die aktuelle.
Ändert die Zugriffsrechte.
Tabelle 10.3: Einige Properties und Methoden von FileInfo
10.2.1 Kopieren, Verschieben, Löschen
Ein Programm verschiebt Dateien und Verzeichnisse mit der MoveTo()-Methode der FileInfooder FolderInfo-Klasse und löscht sie mit deren Delete()-Methode. Dateien kopieren geht
10 Dateien
82
mir der CopyTo()-Methode, jedoch gibt es keine Methode zum Kopieren ganzer Verzeichnisse.
using System ;
using System . IO ;
class Program
{
static void Main ( string [] args )
{
string Home = Environment . GetFolderPath (
Environment . SpecialFolder . MyDocuments ) ;
FileInfo tf = new FileInfo ( Home + @ " \ test . txt " ) ;
FileInfo of = new FileInfo ( Home + @ " \ test2 . txt " ) ;
if ( of . Exists ) of . Delete () ;
tf . CopyTo ( Home + @ " \ test2 . txt " ) ;
}
}
10.2.2 Dateien lesen und schreiben
Die File-Klasse bietet schon einige statische Methoden zum Lesen und Schreiben von Dateien:
Methode
OpenRead()
OpenText()
OpenWrite()
ReadAllBytes()
ReadAllLines()
ReadAllText()
WriteAllBytes()
WriteAllLines()
WriteAllText()
Aufgabe
Öffnet eine Datei zum Lesen.
Öffnet eine existierende UTF-8-Datei zum Lesen.
Öffnet eine existierende Datei zum Schreiben.
Liest eine binäre Datei in ein Byte-Array.
Liest alle Zeilen zeilenweise in ein String-Array.
List eine ganze Datei in einen String.
Schreibt das Byte-Array in eine binäre Datei.
Schreibt ein String-Array als Zeilen in eine Datei.
Schreibt einen String in eine Datei.
Tabelle 10.4: Einige Methoden der File-Klasse
using System ;
using System . IO ;
using System . Text ;
class Program
{
static void Main ( string [] args )
{
int x , y ;
StringBuilder EinMalEins = new StringBuilder () ;
for ( y = 1; y <= 10; y ++)
{
for ( x = 1; x <= 10; x ++)
EinMalEins . Append ( String . Format ( " {0 ,3} " , x * y ) ) ;
EinMalEins . Append ( Environment . NewLine ) ;
}
Console . WriteLine ( EinMalEins ) ;
10.3 Streams
83
string Home = Environment . GetFolderPath (
Environment . SpecialFolder . MyDocuments ) ;
File . WriteAllText ( Home + @ " \ test . txt " , EinMalEins . ToString () ) ;
}
}
Environment.NewLine ist der vom Betriebssystem unabhängige Zeilenvorschub.
10.3 Streams
Streams sind ein Schritt zur einheitlichen Behandlung von Daten aus Dateien, über Netze übertragene Daten, Interprozess-Strömen (Pipes) und Shared Memory. Die beiden wichtigsten Klassen
von C# sind:
FileStream zum Lesen und Schreiben von binären Daten in binäre Files.
StreamReader und StreamWriter zum Lesen und Schreiben von Textfiles.
10.3.1 Lesen und Schreiben von Textfiles
StreamReader beinhaltet die Methode Readline und und StreamWriter das entsprechende
WriteLine. Diese Methoden unterstützen die Formatierung wie bei Format und verhalten sich
so wie Console.WriteLine.
using System ;
using System . IO ;
using System . Text ;
class Program
{
static void Main ( string [] args )
{
string Home = Environment . GetFolderPath (
Environment . SpecialFolder . MyDocuments ) ;
StreamReader sr = new StreamReader ( Home + @ " \ test . txt " ) ;
string line ;
int linecount = 0;
while (( line = sr . ReadLine () ) != null )
{
Console . WriteLine ( " {0 ,5} {1} " ,++ linecount , line ) ;
}
}
}
10.3.2 Lesen und Schreiben von Binärdateien
BinaryReader beinhaltet Methoden zum Lesen aller Arten von Typen. Die am häufigsten verwendeten sind ReadByte() und ReadChar().
84
10 Dateien
FileStream fs
= new FileStream ( " input . bin " , FileMode . Open , FileAccess . Read ) ;
BinaryReader br = new BinaryReader ( fs ) ;
for ( int c = 0; c < fi . Length ; c ++)
DoSomethingWith ( br . ReadByte () ) ;
10.3.3 XML-Serialisierung
Platform#
unabhängiges C kann Klasseninstanzen und deren Inhalte als XML-Strom schreiben und lesen. So kann ein
Programm z.B. Datenmengen in einem platformunabhängigen Format schreiben und lesen.
Schreiben
von
Daten auf Datei schreiben:
ProgrammEinstellungen XmlSerializer serializer = new XmlSerializer ( typeof ( UserData ) ) ;
FileStream fs = new FileStream ( " userdata . xml " , FileMode . Create ) ;
serializer . Serialize ( fs , this . userData ) ;
fs . Close () ;
Daten von Datei lesen:
XmlSerializer serializer = new XmlSerializer ( typeof ( UserData ) ) ;
FileStream fs = new FileStream ( " userdata . xml " , FileMode . Open ) ;
this . userData = ( UserData ) serializer . Deserialize ( fs ) ;
fs . Close () ;
Dabei kann der Serializer zur Laufzeit auf alle öffentlichen Felder und Eigenschaften zugreifen.
Speziell der Zugriff auf Eigenschaften ermöglicht die Beibehaltung der Kapselung interner Information (”Black-Box-Prinzip”).
85
11 Forms und Controls
11.1 Label
Ein Label ist ein Textfeld zur Erläuterung von Eingabefeldern oder zur Ausgabe dynamischer Daten. Beim Ändern seiner Text-Property ändert sich auch der Text des Labels.
11.2 MessageBox
Eines der wichtigsten Werkzeuge zur Kommunikation zwischen Programm und Anwenderin
ist die MessageBox. Von der einfachen Ausgabe eines Textes bis zur komplexen Box mit
mehreren Knöpfen stehen alle Box-Arten zur Verfügung. Box ist die statische Klasse System.Windows.Forms.MessageBox des .NET-Laufzeitsystems. Außer dem ersten Parameter,
dem Nachrichtentext, sind alle weitere Parameter optional.
MessageBox . Show ( NachrichtenText , Fenster Überschrift , Knöpfe , Icon ) ;
Die Auswahl der Knöpfe ist recht reichhaltig und verwirrend, wobei, je nach
Sprachversion von Windows, die Knopf-Beschriftungen in Landessprache erfolgen:
Element
Bedeutung
AbortRetryIgnore
Abort-, Retry- und Ignore-Knopf.
OK
OK-Knopf.
OKCancel
OK- und Cancel-Knopf.
RetryCancel
Retry- und Cancel-Knopf.
YesNo
Yes- und No-Knopf.
YesNoCancel
Yes-, No- und Cancel-Knopf.
Tabelle 11.1: Knöpfe der MessageBoxButtons-Enumeration
Auch stehen eine
Element
Asterisk
Error
Exclamation
Hand
Information
None
Question
Stop
Reihe von Hinweis- und Warnsymbolen
Bedeutung
Kleines i im Kreis.
Weisses X im roten Kreis.
Ausrufezeichen in gelbem Dreieck.
Weisses X im roten Kreis (!).
Kleines i im Kreis.
Kein Symbol.
Fragezeichen im Kreis.
Weisses X im roten Kreis (!).
zur
Verfügung:
Fortsetzung auf der nächsten Seite Û
11 Forms und Controls
86
Element
Warning
Bedeutung
Ausrufezeichen in gelbem Dreieck.
Tabelle 11.2: Symbole der MessageBoxIcons-Enumeration
Ein komplexerer Aufruf der MessageBox sieht etwa folgendermaßen aus:
using System ;
using System . Collections . Generic ;
using System . Windows . Forms ;
namespace ControlDemo
{
static class Program
{
static void Main ()
{
MessageBox . Show ( " Es gibt viel zu wenig verschiedene Icons " ,
" Icon - Armut " ,
MessageBoxButtons . YesNo ,
MessageBoxIcon . Question ) ;
}
}
}
und führt zu folgender Anzeige:
11.3 Button
Button ist ein einfacher Knopf, dessen Parameter die Programmiererin im Properties-Feld einstellen kann.
Interessant ist hier vor allem die Zuordnung unter dem Reiter Events zum Click-Eventhandler
des Knopfes, den das Laufzeitsystem aufruft, wenn die Benutzerin den Knopf drückt.
using
using
using
using
using
using
using
System ;
System . Collections . Generic ;
System . ComponentModel ;
System . Data ;
System . Drawing ;
System . Text ;
System . Windows . Forms ;
namespace ControlDemo
{
public partial class Form1 : Form
11.4 CheckBox
{
public Form1 ()
{
InitializeComponent () ;
}
private void buttonClick ( object sender , EventArgs e )
{
Button btn = sender as Button ;
MessageBox . Show ( " Knopf " + btn . Name + " gedrückt " ) ;
}
}
}
11.4 CheckBox
CheckBox ist das Feld zum Ankreuzen, das bei C# drei Zustände annehmen kann:
Checked Angekreuzt: Das Feld hat ein Häkchen.
Unchecked Leer: Das Feld hat kein Häkchen.
Indeterminate Unbestimmt: Das Feld ist grau hinterlegt.
Der folgende Programmausschnitt zeigt den Zustand von 3 unabhängigen Checkboxen an:
private void buttonClick ( object sender , EventArgs e )
{
String msg =
" Box 1: " + checkBox1 . CheckState + " \ n " +
" Box 2: " + checkBox2 . CheckState + " \ n " +
" Box 3: " + checkBox3 . CheckState ;
MessageBox . Show ( msg ) ;
}
und liefert folgendes Ergebnis:
87
88
11 Forms und Controls
11.5 RadioButton
Der RadioButton sieht in einer seiner Darstellungsarten aus wie die CheckBox, aber mit runden
Feldern, die sich gegenseitig auslösen. Die zweite Darstellungsform sieht aus wie normale Buttons,
aber auch diese lösen sich gegenseitig aus:
private void buttonClick ( object sender , EventArgs e )
{
String msg =
" Button 1: " + radioButton1 . Checked + " \ n " +
" Button 2: " + radioButton2 . Checked + " \ n " +
" Button 3: " + radioButton3 . Checked ;
MessageBox . Show ( msg ) ;
}
Einfacher als jedesmal das Drücken des “Auslesen”-Knopfes abzuwarten geht es, indem das Programm einen Handler für den CheckedChange-Event der RadioButton-Objekte implementiert.
Im folgenden Beispiel laufen alle Radiobuttons auf den gleichen Event-Handler, der den Namen und den Zustand des Radio-Buttons ausgibt. Diesen Handler ruft das Laufzeitsystem gleich
zu Programmstart auf, weil der erste der RadioButtons automatisch selektiert wird. Bei jedem
Knopfdruck durch die Bedienerin geht ein Event für das Abwählen des “alten” Knopfes an den
Eventhandler und ein Event für das Selektieren des “neuen” Knopfes.
private void r adi oBu tto nCh ec ked Cha nge d ( object sender , EventArgs e )
{
RadioButton btn = sender as RadioButton ;
MessageBox . Show ( btn . Text + " ist " + btn . Checked ) ;
}
11.6 ComboBox, ListBox und CheckedListBox
89
11.6 ComboBox, ListBox und CheckedListBox
11.6.1 ComboBox
Die ComboBox ist geeignet für die Auswahl von genau einem Element aus einer Liste, die ListBox
für die Auswahl von einem oder mehreren Elementen und die CheckedListBox darüberhinaus
zum Ankreuzen der gewünschten Einträge.
Im folgenden Beispiel füllt der Eventhandler für RadioButtons, je nach Wahl des RadioButtons,
eine ComboBox mit einer Liste aus Farben oder einer Liste aus Formen und wählt das erste
Element vor:
using
using
using
using
using
using
using
System ;
System . Collections . Generic ;
System . ComponentModel ;
System . Data ;
System . Drawing ;
System . Text ;
System . Windows . Forms ;
namespace ControlDemo
{
public partial class Form1 : Form
{
private static string [] farbenListe =
{ " rot " , " orange " , " gelb " , " grün " , " blau " , " violett " };
private static string [] formenListe =
{ " Punkt " , " Linie " , " Dreieck " , " Parallelogramm " ,
" Rechteck " , " Quadrat " , " Ellipse " , " Kreis " };
public Form1 ()
{
InitializeComponent () ;
}
private void r adi oBu tto nCh eck edC han ge d ( object sender , EventArgs e )
{
RadioButton btn = sender as RadioButton ;
comboBox1 . Items . Clear () ;
if ( btn . Name == " farben " && btn . Checked )
{
foreach ( String f in farbenListe )
comboBox1 . Items . Add ( f ) ;
comboBox1 . SelectedIndex = 0;
}
else if ( btn . Name == " formen " && btn . Checked )
{
foreach ( String f in formenListe )
comboBox1 . Items . Add ( f ) ;
comboBox1 . SelectedIndex = 0;
}
}
}
}
11 Forms und Controls
90
11.6.2 ListBox
Die ListBox lässt die Auswahl von einem oder mehreren Elementen zu, je nach Parameter für
SelectionMode:
None
Keine Auswahl möglich.
One
Auswahl von nur einem Element möglich. Die ListBox verhält sich hierbei wie
eine ComboBox, nur dass mehr Einträge gleichzeitig sichtbar sein können, wenn
das Anzeigefeld groß genug ist.
MultiSimple
Mehrfachanwahl ist möglich.
MultiExtended Mehrfachauswahl ist möglich. Darüberhinaus ist auch die Anwahl mit Shift,
CTRL und den Pfeiltasten möglich.
Im folgenden Beispiel füllt der Eventhandler für RadioButtons, je nach Wahl des RadioButtons,
eine ListBox mit einer Liste aus Farben oder einer Liste aus Formen:
private void r adi oBu tto nCh ec ked Cha nge d ( object sender , EventArgs e )
{
RadioButton btn = sender as RadioButton ;
listBox1 . Items . Clear () ;
if ( btn . Name == " farben " && btn . Checked )
{
foreach ( String f in farbenListe )
listBox1 . Items . Add ( f ) ;
}
else if ( btn . Name == " formen " && btn . Checked )
{
foreach ( String f in formenListe )
listBox1 . Items . Add ( f ) ;
}
}
11.7 ListView
91
11.6.3 CheckedListBox
Die CheckedListBox erweitert die ListBox um Kästchen zum Ankreuzen bei ansonstem gleichen Verhalten.
11.7 ListView
Ein ListView kann Daten auf vier verschiedene Arten darstellen:
1. als Text mit einem optionalen großen Icon,
2. als Text mit einem optinalen kleinen Icon,
3. oder als Text oder kleine Icons in vertikalen Listen,
4. oder als Detailansicht mit mehreren Spalten.
Der Datei-Explorer vewendet z.B. einen ListView zur Anzeige.
Die einzelnen Einträge in ListView leiten sich von ListViewItem ab und besitzen eine TextProperty, die den anzuzeigenden Text enthält, und eine SubItems-Property, die die Texte für die
Detailansicht enthält. Die Detailansicht benötigt darüberhinaus noch die Überschriften über die
einzelnen Spalten mittels myListView.Columns.Add(). Die folgenden Programm-Ausschnitte
leiten eine Klasse FileItem von ListViewItem ab und setzen im Konstruktor den Namen für
die Anzeige sowie Dateigröße und letztes Zugriffsdatum für die Detailansicht:
11 Forms und Controls
92
using
using
using
using
System ;
System . Collections . Generic ;
System . Text ;
System . IO ;
namespace ControlDemo
{
class FileItem : System . Windows . Forms . ListViewItem
{
private FileInfo fileInfo ;
public FileItem ( FileInfo fi )
{
fileInfo = fi ;
base . Text = fileInfo . Name ;
base . SubItems . Add ( fileInfo . Length . ToString () ) ;
base . SubItems . Add ( fileInfo . LastWriteTime . ToShortDateString
() ) ;
}
}
}
Der Initializer der Form füllt den ListView mit einer Liste der Dateien im Heimatverzeichnis und
setzt die Spaltenüberschriften für die Detailansicht. Ein Klick auf die RadioButtons schaltet die
Ansicht des Listviews um.
using
using
using
using
using
using
using
using
System ;
System . Collections . Generic ;
System . ComponentModel ;
System . Data ;
System . Drawing ;
System . Text ;
System . Windows . Forms ;
System . IO ;
namespace ControlDemo
{
public partial class Form1 : Form
{
public Form1 ()
{
InitializeComponent () ;
string Home = Environment . GetFolderPath (
Environment . SpecialFolder . MyDocuments ) ;
DirectoryInfo di = new DirectoryInfo ( Home ) ;
FileInfo [] files = di . GetFiles () ;
foreach ( FileInfo f in files )
listView1 . Items . Add ( new FileItem ( f ) ) ;
listView1 . Columns . Add ( " Dateiname " ) ;
listView1 . Columns . Add ( " Größe " ) ;
listView1 . Columns . Add ( " Modifikation " ) ;
}
93
11.8 TextBox
private void r adi oBu tto nCh eck ed Cha nge d ( object sender ,
EventArgs e )
{
RadioButton btn = sender as RadioButton ;
if ( btn . Checked )
{
switch ( btn . Name )
{
case " LargeIcon " : listView1 . View = View . LargeIcon ;
break ;
case " SmallIcon " : listView1 . View = View . SmallIcon ;
break ;
case " List " : listView1 . View = View . List ; break ;
case " Details " : listView1 . View = View . Details ;
break ;
default : MessageBox . Show ( " Unbekannter Radiobutton "
) ; break ;
}
}
}
}
}
11.8 TextBox
Die TextBox ist das wohl verbreiteste Controlelement. Sie stellt einen String oder ein Array aus
Strings zeilenweise dar. Einige der wichtigeren Eigenschaften der TextBox sind:
Eigenschaft
AcceptsReturn
BackColor
BorderStyle
Aufgabe
Schaltet um zwischen RETURN innerhalb der TextBox und dem
Aktivieren des Default-Knopfes.
Hintergrundfarbe der Box.
Rahmenart der Box.
Fortsetzung auf der nächsten Seite Û
11 Forms und Controls
94
Eigenschaft
Enabled
Focused
Font
Height
Lines
Location
MaxLength
Multiline
Name
PasswordChar
ReadOnly
ScrollBars
SelectedText
SelectionLength
SelectionStart
Size
TabIndex
TabStop
Text
TextLength
Visible
Width
Funktion
An und Abschalten der Eingabemöglichkeit.
Liefert zurück, ob die Box den Focus hat.
Setzt oder liest die aktuelle Font in der Box.
Setzt oder liest die Box-Höhe.
Liest oder schreibt die Textzeilen in der Box mittels eines StringArrays.
Gibt den Punkt der linken oberen Ecke im einschließenden Container an.
Maximale Zeichenzahl die die Benutzerin eintippen darf.
Nimmt die Box eine oder mehrere Zeilen auf.
Lesen und Schreiben des Box-Namens.
Setzt oder liest das Zeichen, das Passworte in einer einzeiligen
Box verdeckt.
Auslesen oder Ändern des “nur-Lesen”-Status der Box.
Liest oder setzt die Rollbalken: Horizontal, Vertikal, Beide oder
keinen.
Liest den selektierten Text aus oder ersetzt ihn durch einen neuen
Text.
Liest oder setzt die Zahl der selektierten Buchstaben.
Setzt den Index des ersten selektierten Buchstabens.
Gibt die Breite und Höhe der Box an.
Liest oder schreibt die Tab-Reihenfolge der Box.
Bestimmt, ob Tab überhaupt zur Box führt.
Liest oder schreibt den Text in der Box.
Liest die Länge des Textes in der Box.
Liest und ändert die Sichtbarkeit der Box.
Liest und schreibt die Breite der Box.
Tabelle 11.3: Wichtige Eigenschaften der TextBox
Der folgende Beispielcode erzeugt auf einem Formular, das nur das Label mit der Beschriftung
“TextBox-Beispiel” enthält, eine Textbox per Programm:
using
using
using
using
using
using
using
using
System ;
System . Collections . Generic ;
System . ComponentModel ;
System . Data ;
System . Drawing ;
System . Text ;
System . Windows . Forms ;
System . IO ;
namespace ControlDemo
{
public partial class Form1 : Form
{
public Form1 ()
{
InitializeComponent () ;
InitializeComponent () ;
// Textbox - Demo für die C # Vorlesung
TextBox textBox = new TextBox () ;
textBox . Location = new Point (13 , 30) ;
11.8 TextBox
95
textBox . Name = " DemoBox " ;
textBox . Size = new Size (100 , 20) ;
textBox . TabIndex = 2;
this . Controls . Add ( textBox ) ;
}
}
}
SelectionStart legt den Anfang einer Ausfahl fest, SelectionLength die Länge einer
Auswahl. Der folgende Programmabschnitt sucht auf Knopfdruck den String TextBox im
eingegebenen Text, wählt ihn aus und macht ihn mit Focus sichtbar:
private void button1_Click ( object sender , EventArgs e )
{
String searchText = " TextBox " ;
int selStart = textBox1 . Text . IndexOf ( searchText ) ;
textBox1 . SelectionStart = selStart ;
textBox1 . SelectionLength = searchText . Length ;
textBox1 . Focus () ;
}
11.8.1 ErrorProvider
Der ErrorProvider ist keine Control sondern eine Komponente und erscheint so im Designer
nicht auf der Form-Oberfläche sondern in der Sammlung in der Fußzeile. Pro Formular ist nur ein
ErrorProvider nötig. Der ErrorProvider besitzt eine Methode SetError(), die die Control
anwählt, neben der das Fehlersymbol erscheinen soll und einen Hilfetext, den das Laufzeitsystem
beim Überfahren mit der Maus ausgibt.
Im folgenden Beispiel implementiert die TextBox einen Event-Handler für jede Änderung des
Textes in der Box und registriert ihn beim Laufzeitsystem mit folgendem Code:
this . textBox1 . TextChanged
+= new System . EventHandler ( this . textBox1TextChanged ) ;
11 Forms und Controls
96
Der Eventhandler prüft nach jeder Änderung über einen regulären Ausdruck, ob in der Box genau
5 Zahlen stehen und setzt die Fehlermeldung auf leer, wenn die Eingabe der PLZ korrekt ist:
using
using
using
using
using
using
using
using
using
System ;
System . Collections . Generic ;
System . ComponentModel ;
System . Data ;
System . Drawing ;
System . Text ;
System . Windows . Forms ;
System . IO ;
System . Text . RegularExpressions ;
namespace ControlDemo
{
public partial class Form1 : Form
{
public Form1 ()
{
InitializeComponent () ;
}
public void textBox1TextChanged ( object sender , EventArgs e )
{
TextBox tb = sender as TextBox ;
Regex rexp = new Regex ( @ " \ d {5} " ) ;
if ( rexp . IsMatch ( tb . Text ) )
errorProvider1 . SetError ( tb , " " ) ;
else
errorProvider1 . SetError ( tb , " PLZ besteht aus 5 Zahlen " ) ;
}
}
}
11.8.2 Eingaben Abfangen und Verändern
Die TextBox erzeugt mit jedem Tastendruck KeyPress-Events, die auch eine Möglichkeit zur
Vermeidung von Fehleingaben bieten: Ein eigener KeyPress-Handler kann Buchstaben verändern
oder filtern und selbst eigene oder andere Zeichen an den Eingabetext anhängen. Dieser Hander
hat ein von EventArgs abgeleitetes zweites Methodenargument, das das gedrückte Zeichen
enthält. Der Handler signalisiert durch Setzen von e.Handled, dass sich das Laufzeitsystem
nicht weiter um die Eingabe zu kümmern braucht.
using System ;
using System . Collections . Generic ;
using System . ComponentModel ;
11.8 TextBox
using
using
using
using
using
using
97
System . Data ;
System . Drawing ;
System . Text ;
System . Windows . Forms ;
System . IO ;
System . Text . RegularExpressions ;
namespace ControlDemo
{
public partial class Form1 : Form
{
public Form1 ()
{
InitializeComponent () ;
helpProvider1 . SetHelpString ( textBox1 ,
" Postleitzahl : 5 Ziffern " ) ;
}
public void textBox1KeyPress ( object sender ,
KeyPressEventArgs e )
{
TextBox tb = sender as TextBox ;
String c = e . KeyChar . ToString () ;
if ( " 0123456789 " . Contains ( c ) )
if ( tb . Text . Length < 5)
tb . AppendText ( c ) ;
e . Handled = true ;
}
}
}
11.8.3 HelpProvider
Der HelpProvider ist eine Komponente, wie der ErrorProvider. Zum Anschalten eines Hilfetextes erhält der HelpProvider zwei Argumente:
ˆ Die Referenz einer Control, zu der der Hilfetext gehört.
ˆ Den Hilfetext, der erläutert, welche Eingabe die Control erwartet.
Wenn sich nun der Cursor im Eingabefeld befindet und die Anwenderin F1 drückt, erscheint der
angegebene Hilfstext. Der ErrorProvider kann noch viel mehr: Er lässt sich mit einer Hilfedatei
verknüpfen und springt innerhalb dieser ein wählbares Stichwort an, wenn die Anwenderin F1
drückt.
using
using
using
using
using
using
using
using
using
System ;
System . Collections . Generic ;
System . ComponentModel ;
System . Data ;
System . Drawing ;
System . Text ;
System . Windows . Forms ;
System . IO ;
System . Text . RegularExpressions ;
11 Forms und Controls
98
namespace ControlDemo
{
public partial class Form1 : Form
{
public Form1 ()
{
InitializeComponent () ;
helpProvider1 . SetHelpString ( textBox1 ,
" Postleitzahl : 5 Ziffern " ) ;
}
public void textBox1TextChanged ( object sender ,
EventArgs e )
{
TextBox tb = sender as TextBox ;
Regex rexp = new Regex ( @ " \ d {5} " ) ;
if ( rexp . IsMatch ( tb . Text ) )
errorProvider1 . SetError ( tb , " " ) ;
else
errorProvider1 . SetError ( tb , " PLZ besteht aus 5 Zahlen " ) ;
}
}
}
11.9 RichTextBox
Die RichTextBox unterstützt das Rich Text Format (RTF) und bietet weitere Möglichkeiten
zum Formatieren von Texten. Sie bietet unter anderem
ˆ Änderungen des Zeichensatzes über SelectFont.
ˆ Farbänderungem mit SelectColor.
ˆ Listen durch SelectBullet.
Alle diese Änderungen wirken auf wie bei der TextBox selektierte Textbereiche.
private void button1_Click ( object sender , EventArgs e )
{
String searchText = " RichTextBox " ;
int selStart = richTextBox1 . Text . IndexOf ( searchText ) ;
richTextBox1 . SelectionStart = selStart ;
richTextBox1 . SelectionLength = searchText . Length ;
richTextBox1 . SelectionColor = Color . Blue ;
}
11.10 ProgressBar
99
11.10 ProgressBar
Der ProgressBar ist eine intuitive Anzeige, wieviel Zeit etc. von einer Tätigkeit schon vergangen ist und wie lange es wohl noch dauern kann. Der Progressbar ist einfach einzustellen und
bereichert die meisten Anwendungen um eine Rückmeldung, dass grade etwas vor sich geht und
vieviel schon abgearbeitet ist.
Minimum Unterer Wert des Balkens.
Maximum Oberer Wert des Balkens.
Step Schrittweite, in der PerformStep() weiterschaltet.
Value Liest oder setzt den aktuellen Wert des Balkens.
PerformStep() Schaltet einen Step weiter.
pf . progressBar1 . Minimum = 0;
pf . progressBar1 . Maximum = 100;
pf . progressBar1 . Value = 0;
pf . progressBar1 . Step = 10;
11.11 StatusStrip
Ein StatusStrip nimmt unter anderem die wichtigsten Elemente zum Anzeigen von Fortschritt
auf, den ProgressBar und ein StatusLabel. Die Textmeldung im StatusLabel informiert
darüber, was das Programm gerade tut, der Fortschrittsbalken im ProgressBar darüber, wie
weit das Programm gerade damit ist. Das einfachste Vorgehen:
ˆ StatusStrip aus der Auswahlleiste in die aktuelle Form einfügen.
11 Forms und Controls
100
ˆ StatusLabel im StatusStrip anwählen.
ˆ ProgressBar im StatusStrip anwählen.
Elemente rechts neben dem StatusLabel verschieben sich zur Laufzeit mit der Länge des Textes
im StatusLabel
11.12 ContextMenuStrip
Ein ContextMenuStrip kann zu jeder Control ein Context-Menu anbieten, das die rechte Maustaste aufruft. Dazu den ContextMenuStrip aus der Auswahlbox in die Form ziehen und innerhalb der Control deren ContextMenuStrip-Eigenschaft auf den Namen des ContextMenuStrip
setzen.g
11.13 OpenFileDialog
Der OpenFileDialog öffnet eine eigenständige Dialogbox, in der die Anwenderin zur Laufzeit die
Datei, die das Programm einlesen soll, angeben kann. Eine Reihe von Eigenschaften ermöglichen
eine einfache Konfiguration des Dialogs:
Eigenschaft
AddExtension
CheckFileExists
CheckPathExists
DefaultExt
FileName
FileNames
Filter
Aufgabe
Soll die Dialogbox automatisch Dateinamenserweiterungen anfügen?
Überprüfung, ob die angebebene Datei existiert.
Überprüfung, ob der angegebene Pfad existiert.
Setzt oder liest die voreingestellte Dateinamenserweiterung.
Setzt und vor allem liest den gewählten Dateinamen aus.
Liest alle Dateinamen bei Mehrfachselektion aus.
Liest oder setzt den aktuellen Dateinamenfilter-String: z.B.
"Text files (*.txt)|*.txt|All files (*.*)|*.*"
oder
"Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All
files (*.*)|*.*".
FilterIndex
InitialDirectory
Multiselect
Title
Liest oder schreibt den derzeit aktuellen Filterwert.
Verzeichnis, in dem die OpenFileDialog-Box mit der Suche beginnt.
Sind Mehrfachauswahlen zulässig?
Überchrift der Dialogbox.
Tabelle 11.4: Wichtige Eigenschaften von OpenFileDialog
Der Programmabschnitt zum Anzeigen einer Dialogbox für eine zu lesende .tex-Datei sieht damit
folgendermaßen aus:
11.14 Eigenständige Forms
101
private void button1_Click ( object sender , EventArgs e )
{
openFileDialog1 . Title = " C # Vorlesung Demo " ;
openFileDialog1 . Filter = " Tex - Dateien (*. tex ) |*. tex " ;
openFileDialog1 . FileName = " " ;
openFileDialog1 . InitialDirectory = @ " C :\ Documents and Settings \ Admin
\ My Documents \ HS - Esslingen \ Vorlesungen \ C # " ;
DialogResult result = openFileDialog1 . ShowDialog () ;
if ( result == DialogResult . OK )
{
tbFile . Text = openFileDialog1 . FileName ;
}
}
Die Dialoge zum Speichern einer Datei (SaveFileDialog) und
eines
Ordners
(FolderBrowserDialog)
verhalten
sich
ganz
zur Auswahl
entsprechend.
11.14 Eigenständige Forms
Eigenständige Forms lassen sich als weitere Forms in Projekte einbinden und mit new instantiieren.
Danach ist der normale Zugriff auf öffentliche Methoden, Properties oder Variable möglich. So
ist beispielsweise ein Popup-Fenster mit einem Progress-Bar einfach zu realisieren. Der folgende
Programmabschnitt öffnet auf Knopfdruck eine neue Form:
private void button1_Click ( object sender , EventArgs e )
{
pf = new ProgressForm () ;
pf . progressBar1 . Minimum = 0;
pf . progressBar1 . Maximum = 100;
pf . progressBar1 . Value = 0;
pf . progressBar1 . Step = 10;
pf . Show () ;
}
102
11 Forms und Controls
11.15 Panels
Das Panel ist eine Control, die andere Controls beinhaltet. Das kann das Programm nutzen,
indem es beispielsweise alle Controls innerhalb eines Panels abschalten kann, indem es das Panel
abschaltet. Ebenfalls kann das Programm bei Platzmangel den Panel-Rand auf AutoScroll
schalten und die Benutzerin kann durch alle Controls durchscrollen.
Die Sonderformen FlowLayoutPanel und TableLayoutPanel ermöglichen das Layout von
Controls, das sich besser an Größenveränderungen der umgebenden Form anpasst.
103
12 Tipps und Tricks
12.1 Dokumentierende Kommentare
Mit C# hat ein neuer Stil von Kommentaren Einzug gehalten, der das automatische Erzeugen von Code- und Klassendokumentation unterstützt. Diese Kommentare beginnen mit drei
Schrägstrichen (///). Damit sind sie rückwärtskompatibel zu bisherigen Kommentaren mit zwei
Schrägstrichen und wohl trotzdem nicht allzu oft zufällig zu finden.
Beispiel:
// / < summary >
// / The constructor gets the server to connect to
// / and the user credentials to log in
// / </ summary >
// / < param name =" servername " > The Server to connect to
// / A < see cref =" System . String "/ >
// / </ param >
// / < param name =" username " > User login name
// / A < see cref =" System . String "/ >
// / </ param >
// / < param name =" password " > Password to log in
// / A < see cref =" System . String "/ >
// / </ param >
public Jabber ( string servername , string username , string password )
{
this . servername = servername ;
this . username = username ;
this . password = password ;
contacts = new Hashtable () ;
}
Es gibt zwei Arten von XML-Tags in Dokumentations-Kommentaren: Die primären Tags, die
eine Gruppe von Tags einleiten, und die sekundären Tags, die Dokumentations-Texte verändern.
12.1.1 Primäre Tags
Die primären Tags sind <remarks>, <summary>, <example>, <exception>, <param>, <value>,
<permission>, <returns>, <seealso> und <includes>.
<remarks>
Beschreibt einen Typ, wie z.B. eine Klasse oder eine Struktur. Microsoft Visual Studio und MonoDevelop fügen beide bei Eingabe von drei Kommentarstrichen über einer Klasse fälschlicherweise
das <summary>-Tag ein.
// / < remarks >
// / Class to handle all Jabber specific issues .
// / </ remarks >
public class Jabber
{
12 Tipps und Tricks
104
private string servername ;
private string username ;
<summary>
Für die Beschreibung von Elementen innerhalb von Typen, also Methoden, Eigenschaften und
Felder. Zur Beschreibung von Typen nicht empfohlen aber oft verwendet, weil die Entwicklungsumgebungen dieses Tag automatisch erzeugen.
// / < summary >
// / The constructor gets the server to connect to
// / and the user credentials to log in
// / </ summary >
public Jabber ( string servername , string username , string password )
{
this . servername = servername ;
this . username = username ;
this . password = password ;
<example>
Beispiel für einen Einsatz der Klasse oder Methode:
// / < example >
// / < code >
// / Jabber j = new Jabber (" xmpp . hs - esslingen . de " , " melcher " , " passwd ")
;
// / </ code >
// / </ example >
<exception>
Beschreibt die Ausnahmen, die die Methode oder Klasse möglicherweise wirft. Attribut-Parameter
cref für dieses Tag ist die Klasse der Ausnahme, die existieren muss, damit das Dokumentationsprogramm automatisch darauf verweisen kann.
// / < exception cref =" JabberException " >
// / Thrown , when the constructor can ’ t connect to the specified Jabber
server
// / </ exception >
<param>
Gibt Details zu Methoden-Parametern an. Der Attribut-Parameter name gibt dabei den Parameternamen an:
// / < param name =" servername " > The Server to connect to
// / </ param >
<value>
Gibt Details zum Eigenschaften-Parameter an:
// / < value >
// / The Length property contains number of Servers in the Serverlist
// / </ value >
105
12.1 Dokumentierende Kommentare
<permission>
Wie andere Programmteile auf einen Typ oder eine Methode, Eigenschaft, Feld zugegreifen
dürfen, gibt das <permission>-Tag an.
// / < permission cref =" System . Security . PermissionSet " > Public
// / Access </ permission >
<returns>
Dieses Tag beschreibt den Rückgabewert von Methoden oder Eigenschaften:
// / < returns > Success or failure of login procedure
// / A < see cref =" System . Boolean "/ >
// / </ returns >
<seealso/>
Ein alleinstehendes Tag, das auf ein artverwandtes Thema verweist. Der Dokumentationscompiler
verwandelt das cref-Attibut in einen Link.
// / < seealso cref =" Server "/ >
<include/>
Falls Dokumentation und Code in getrennten Dateien untergebracht sind, weist das <include/>Tag den Dokumentationscompiler an, die angegebene Datei einzulesen und die Dokumentatinskommentare so zu verarbeiten, als stünden sie in der aktuellen Datei.
// / < include file = ’ MyCommentFile . xml ’
// / path = ’ doc / exmpl [ @name =" M : ExampleClass "]/* ’/ >
12.1.2 Sekundäre Tags
Sekundäre Tags bestimmen die Formatierung von Elementen innerhalb der primären Tags. Das
aufwändigste sekundäre Tag ist die Liste <list>:
// /
// /
// /
// /
// /
// /
// /
// /
// /
// /
// /
// /
< list type =" table " >
< listheader >
< term > Overview </ term >
< description > Function </ description >
</ listheader >
< item > < term > Types </ term >
< description > Generate type list </ description > </ item >
< item > < term > Members </ term >
< description > Generate member list </ description > </ item >
< item > < term > Type </ term >
< description > Generate doc for a single type </ description > </ item >
</ list >
Tag
<c>
<code>
Funktion
Schliesst eine Code-Zeile ein. Verwendet für Inline-Code.
Schliesst mehrere Code-Zeilen ein.
Fortsetzung auf der nächsten Seite Û
12 Tipps und Tricks
106
Tag
<list>
<listhead>
<item>
<term>
<description>
<para>
<paramref>
<see>
Funktion
Definiert eine formatierte Liste.
Gibt die Überschrift der formatierten Liste an.
Ein einzelner Eintrag der Liste.
Gibt das Thema des Listeneintrags an.
Gibt die Beschreibung zum Thema (s.o.) an.
Erzeugt einen Paragrafen-Umbruch.
Bewirkt spezielle Formatierung eines Parameters.
Erzeugt einen Hyperlink auf ein anderes existierendes Symbol.
Tabelle 12.1: Sekundäre Dokumetations-Tags
12.2 Laufzeitmessung
Die Laufzeit eines Programms lässt sich am einfachsten mit den eingebauten DateTime-Objekten
und den TimeSpan-Objekten bewerkstelligen:
// ...
// Zeitmessung in die Wege leiten
DateTime tStart = DateTime . Now ;
//
// Hier der zu messende Programmabschnitt
//
// ...
// Zeitmessung ausgeben
DateTime tEnde = DateTime . Now ;
TimeSpan tDiff = new TimeSpan ( tEnde . Ticks - tStart . Ticks ) ;
Console . WriteLine ( " Rechenzeit : " + tDiff . ToString () ) ;
12.3 Zufallszahlen
Die Random-Klasse unterstützt eine Reihe von Methoden zum Erzeugen von Zufallzahlen.
Nach Instantiieren eines Objekts (mit einer eigenen Zufallszahlenfolge) liefert die Methode
Next(limit) die nächste Zufallszahl im Bereich von 0 bis kleiner Limit:
Random rnd = new Random () ;
// ...
int zZahl = rnd . Next ( max +1) ;
12.4 Besondere Verzeichnisse
Das .NET-Laufzeitsystem kennt eine Reihe von außergewöhnlichen Verzeichnissen, so z.B. das
Heimat-Verzeichnis der Benutzerin. Das Heimatverzeichnis ist in der MyDocuments-Property der
SpecialFolder-Klasse im Namespace Environment.
string Home = Environment . GetFolderPath
( Environment . SpecialFolder . MyDocuments ) ;
107
Index
Symbols
LinkedList . . . . . . . . . . . . . . . . . . . . . . . . . . 58
for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
foreach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
A
G
anonyme Methode . . . . . . . . . . . . . . . . . . . . 66
as-Operator . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Ausdruck
regulärer . . . . . . . . . . . . . . . . . . . . . . . . . 50
Garbage Collector . . . . . . . . . . . . . . . . . . . . . 20
generische Methode . . . . . . . . . . . . . . . . . . . 35
B
Bedingung
if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
switch . . . . . . . . . . . . . . . . . . . . . . . . . . 11
break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
C
Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Copy-Kontruktor . . . . . . . . . . . . . . . . . . . . . . 19
D
delegate . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Delegates
Multicast . . . . . . . . . . . . . . . . . . . . . . . . 67
Destruktor . . . . . . . . . . . . . . . . . . . . . . . . 20, 21
Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
E
Eigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . 31
Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Exception
eigene . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
innere . . . . . . . . . . . . . . . . . . . . . . . . . . . .72
Expression
regular . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Extension-Methoden . . . . . . . . . . . . . . . . . . 30
F
FiFo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .60
Finalize() . . . . . . . . . . . . . . . . . . . . . . . . . . 20
I
cdif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Initialisierung
von Variablen . . . . . . . . . . . . . . . . . . . . . 7
Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .37
ICloneable . . . . . . . . . . . . . . . . . . . . . 38
ICollection . . . . . . . . . . . . . . . . . . . . 40
IComparable . . . . . . . . . . . . . . . . . . . . 38
IConvertible . . . . . . . . . . . . . . . . . . . 38
IEnumerable . . . . . . . . . . . . . . . . 37, 39
IEnumerator . . . . . . . . . . . . . . . . . . . . 37
IFormatable . . . . . . . . . . . . . . . . . . . . 38
IList . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
is-Operator . . . . . . . . . . . . . . . . . . . . . . . . . . 25
K
Klasse
sealed . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Klassen, partielle . . . . . . . . . . . . . . . . . . . . . . 23
Konstruktor
Copy- . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
statischer . . . . . . . . . . . . . . . . . . . . . . . . 18
Kontruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
L
Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Liste
verlinkt . . . . . . . . . . . . . . . . . . . . . . . . . . 58
M
Main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Methode
Index
108
anonyme . . . . . . . . . . . . . . . . . . . . . . . . . 66
generische . . . . . . . . . . . . . . . . . . . . . . . .35
mit variabler Parameterzahl . . . . . . . 28
sealed . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Methoden überschreiben
Extension Methoden . . . . . . . . . . . . . . 30
virtual . . . . . . . . . . . . . . . . . . . . . . . . . 29
Multicast Delegates . . . . . . . . . . . . . . . . . . . 67
O
öffentlich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Operator
as . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
is . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
P
Parameter
variable Anzahl . . . . . . . . . . . . . . . . . . . 28
partielle Klassen . . . . . . . . . . . . . . . . . . . . . . 23
Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
public . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Q
struct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
cdswitch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Synchronisation von Threads . . . . . . . . . . 76
T
Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
abbrechen . . . . . . . . . . . . . . . . . . . . . . . . 75
Synchronisation . . . . . . . . . . . . . . . . . . 76
Threads
erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . 74
throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
txt:delegate . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
U
unmanaged Ressouce . . . . . . . . . . . . . . . . . . 20
using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
V
Variable
statische . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Variablen-Initialisierung . . . . . . . . . . . . . . . . . 7
Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
R
Referenztyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
regulärer Ausdruck . . . . . . . . . . . . . . . . . . . . 50
Ressource
unmanaged . . . . . . . . . . . . . . . . . . . . . . 20
S
Sammlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Schleife
break . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
continue . . . . . . . . . . . . . . . . . . . . . . . . 15
foreach . . . . . . . . . . . . . . . . . . . . . . . . . 15
for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
while . . . . . . . . . . . . . . . . . . . . . . . . 13, 14
sealed
Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Methode . . . . . . . . . . . . . . . . . . . . . . . . . 27
Signatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
SortedList . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Statischer Kontruktor . . . . . . . . . . . . . . . . . 18
Statische Variable . . . . . . . . . . . . . . . . . . . . . . 9
StringBuilder . . . . . . . . . . . . . . . . . . . . . . 53
Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
W
Wertetyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
while . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13, 14

Documentos relacionados