programmierung ii einführung in c# - dblabor.fh …dblabor.fh-stralsund.de/skripte/skript...
TRANSCRIPT
Skript zur Vorlesung
Programmierung II
Einführung in C#
Fachbereich Elektrotechnik & Infromatik Prof. Dr. Uwe HartmannFachhochschule Stralsund
Programmierung II - 2 -
1 Einleitung
C C++ C#
keine OOP OOP möglich nur OOP
AlgolPascalFortran
Algol68 (Überladung)Simula (Klassen) Java (OOP, virtuelle Maschine)
Visual C++ Visual Studio.NET Framework
1.1 Objektorientierte Softwareentwicklung
Funktionale Programmierung (Softwareentwicklung)
Abläufe eines Anwendungsbereiches werden schrittweise in Algorithmen umgesetzt
Objektorientierte Programmierung (Softwareentwicklung)
Objekte eines Anwendungsbereiches und ihre Beziehungen zueinander werden beschrieben und klassifiziert
Merkmale von Objekten
− statische Merkmale: Zustände (--> Daten)
− dynamische Merkmale: Verhalten (-->Algorithmen)
Klasse (extensional)
Zusammenfassung von Objekten
Klasse (intensional) = abstrakter Datentyp
Beschreibung der Merkmale der Klassenobjekte durch
− Datentypen (statische Merkmale, Zustände) und
− Funktionen (dynamische Merkmale, Algorithmen)
Problemstellung bei der objektorientierten Softwareentwicklung
Definition/Finden geeigneter Klassen und ihrer Beziehungen zueinander zur Abbildung der relevanten Objekte eines Anwendungsbereiches in einer Anwendung
Programmierung II - 3 -
1.2 .NET-Framework
Klassischer Übersetzungsprozeß
Quellcode (test.cpp)
Compiler/Linker
Maschinencode (test.exe)
.NET-Framework
Sprachunabhängigkeit
− durch Common Language Spezification (CLS) und Common Type System (CTS).
− Erzeugung von Zwischencode (ähnlich Java)
Plattformunabhängigkeit
Spezifikation der Common Language Runtime (CLR) für unterschiedliche Plattformen
Speicherverwaltung im Hintergrund
Garbage Collector gibt nicht mehr benötigten Heap-Speicherplatz selbständig frei
Einheitliches Fehlerkonzept
Verwendung von Exceptions
Objektorientiertheit
.NET ist 100%ig objektorientiert. Alle Elemente werden auf Objekte zurückgeführt.
Übersetzungsprozeß in .NET
Quellcode (test.cs)
CompilerAssembly (test.exe)
Quellcode (test.vb)
.
.
. Maschinencode(sofort ausgeführt)JITter
Bibliotheken (.dll)
Assembly
Programmierung II - 4 -
− enthält MSIL-Code (Microsoft Intermediate Language; kurz IL-Code)
− enthält Manifest, das Informationen über
− Name, Version, Speicherort des Assembly
− Namen aller Dateien, die zur Komponente gehören
− Beschreibung aller Typen, die im Assembly definiert sind
− Namen der assemblies, auf die zugegriffen wird
enthält
− enthält Metadaten mit Informationen über alle Klassen incl. Methoden und Eigenschaften und sonstige Datentypen
JITter
Just-In-Time-Compiler überführt IL-Code in Maschinencode
1.3 Programmaufbau
Beispiel (C/C++)
#include <stdio.h>int main(int argc, char* argv[]){
printf("Hello World");return 0;
}
Beispiel 1.3.a
using System;using System.Collections.Generic;using System.Text;namespace Beispiel{
class Program{
static void Main(string[] args){
Console.WriteLine("Hello World");}
}}
• jedes ausführbare Programm besitzt genau eine Klasse mit einer Methode Main() als Einstiegspunkt für die Programmausführung
Syntax
Programmierung II - 5 -
compilation-unit
using-directive namespace-declaration
enum-declaration
struct-declaration
class-declaration
interface-declaration
delegate-declaration
type-declaration
using-directive
namespace-nameusing ;
namespace-declaration
using-directive namespace-declaration
type-declaration
namespace-namenamespace { }
type-declaration
Programmierung II - 6 -
Namensraum (namespace)
• Ein Namensraum ist eine logische Organisationsstruktur, die verschiedene Typdefinitionen (insbes. Klassendeklarationen) zusammenfaßt
− Stilregel: Ein Typ sollte immer zu einem Namensraum gehören
− Namensräume können unabhängig von der Klassenhierarchie selbst hierarchisch geordnet sein
− Wurzel aller Namensräume: 'System'
• alle in namespace definierten Typen (insbes. Klassen) gehören zu dem Namensraum
• vollständiger Name eines Typs ist dann
<namespace-name> . [ <namespace-name> . ]... <type-name>
System.Console.WriteLine ("Hallo World")
Namensraum Klasse Methode
• Namensräume können dem Programm durch using-Klausel bekanntgemacht werden. Dann kann die Angabe des Namensraumes im Typnamen entfallen
Beispiel
Aufruf der Methode WriteLine bei bekanntem Namensraum System (using System;):
Console.WriteLine("Hello World")
• innerhalb von Namensräumen lassen sich nur Aufzählungen, Strukturen, Klassen, Schnittstellen und Delegaten deklarieren
• Felder (Daten) und Methoden (Funktionen) können ausschließlich in Klassen (im Spezialfall in Strukturen) definiert werden
• Stilregel: Eine Klasse oder Struktur sollte immer in einer eigenen gleichnamigen Datei implementiert werden
Programmierung II - 7 -
Zugriffsmodifizierer
Syntax
access-modifier
public
protected
internal
protected internal
private
• Der Zugriffsmodifizierer (access-modifier) gibt an, wieweit sich die Sichtbarkeit einer Typdeklaration (struct, class, ...) oder einer Memberdeklaration (field, method, ...) erstreckt:
publickeine Zugriffsbeschränkungen
protectedder Zugriff beschränkt sich auf die Klasse, in der das Member deklariert wurde (umgebende Klasse) und die von dieser Klasse abgeleiteten Typen
internalder Zugriff ist auf die Anwendung (Assembly) beschränkt, in der der Typ definiert ist
protected internalder Zugriff ist auf die Anwendung (Assembly) und hier auf die Klasse, in der das Member deklariert wurde (umgebende Klasse) und die von dieser Klasse abgeleiteten Typen beschränkt
privateZugriff nur innerhalb der deklarierenden Klasse oder der Struktur möglich
• Regel
Eine direkt in einer Namespace-Deklaration enthaltene Typ-Deklaration kann nur die Zugriffsmodifizierer public oder internal besitzen. Default ist internal.
Programmierung II - 8 -
• Betrachten eines Assembly mit dem Tool MSIL-Disassembler (ILDasm) möglich
Beispiel
Manifest des Beispiel-Programms
MSIL-Code der Main-Methode
Metadaten der Main-Methode
Programmierung II - 9 -
Kommentare
• /// vor einer Definition erzeugt im Visual Studio einen Kommentarrahmen, der dann selbständig ausgefüllt werden kann. Diese Kommentare dienen der Erzeugung von XML-Dokumentationen
Beispiel 1.3.b
Beispiel.csnamespace Beispiel{
/// <summary>/// Das ist eine Testklasse/// </summary>class Test{
/// <summary>/// Das ist eine Testmethode/// </summary>/// <param name="x"></param>/// <param name="y"></param>/// <returns></returns>public int TestMethod(int x, int y){
return x+y;}static void Main(string[] args){}
}}
Beispiel.XML
Programmierung II - 10 -
2. C#-Typsystem
2.1 Typhierarchie
Datentypen (object)
Wertetypen
Referenztypen
Einfache Typen (außer string und object)
Aufzählungstypen (enum)
Strukturen (struct)
Schnittstelle (interface)
Arrays (array)
Delegate (delegate)
Klassentypen
object
Zeichenketten (string)
Klassen (class)
• Unterscheidung
− Elementare Typen sind Bestandteil der Sprache C# (int, double, string ...)
− Benutzerdefinierte Typen (struct, array, class, ...)
• alle Datentypen sind von dem elementaren Datentyp object abgeleitet
Programmierung II - 11 -
2.2 Elementare Datentypen
• strikten Objektorientierung in C#: auch elementare Datentypen sind Objekte
Datentyp CLS Größe Bereichbyte ja 1 0 ... 255sbyte nein 1 -128 ... 127short ja 2 -32768 ... 32767ushort nein 2 0 ... 65536int ja 4 -231 ... 231-1uint nein 4 0 ... 232-1long ja 8 -263 ... 263-1ulong nein 8 0 ... 264-1float ja 4 1,4 * 10-45 ... 3,4 * 1038
double ja 8 5,0 * 10-324 ... 1,7 * 10308
decimal ja 16 1,0 * 10-28 ... 7,9 * 1028
char ja 2 Unicode-Zeichen 0 ... 65536bool ja 1 true | falsestring ja Ref max. ca. 231 Unicode-Zeichenobject ja Ref kann jeden anderen Typ enthalten
• CLS (Common Language Specifikation) Kompatibilität: Datentypen werden von allen .NET-Sprachen verwendet
Beispiel 2.2
static void Main(string[] args){
Console.WriteLine("{0}", (float)10.0-(float)9.9); // 0,1000004Console.WriteLine("{0}", (double)10.0-(double)9.9); // 0,0999999999999996Console.WriteLine("{0}", (decimal)10.0-(decimal)9.9); // 0,1
}
Programmierung II - 12 -
2.3 Variablendeklarationen und Zuweisungen für elementare Datentypen
Syntax
variable-declaration
name =
,
;expressiontype
• Variablen können nur innerhalb von Methoden- und Anweisungsblöcken deklariert werden
• Der Gültigkeitsbereich einer (lokalen) Variablen ist der Block, in dem die Deklaration erfolgt.
• Eine (lokale) Variable wird nicht automatisch initialisiert und verfügt folglich über keinen Standardwert.
Syntax
value-assign
name = ;expression
• die Verwendung einer nicht instantiierten Variablen führt zu Compilerfehler
Beispielint i = 0;Console.WriteLine(i);
int j;Console.WriteLine(j);
Programmierung II - 13 -
2.4 Konstantendeklarationen für elementare Datentypen
Syntax
constant-declaration
name =
,
;expressiontypeconst
• expression ist ein Konstantenausdruck
Beispielint j = 4;const int i = 3 + j;
2.5 Werte- und Referenztypen
• Wertetypen
− Variablen vom Wertetyp speichern ihre Daten im Stack
− Wertetypen: alle einfachen Datentypen, dazu Stukturen (struct) und Aufzählungen (enum) als nutzerdefinierte Datentypen
• Referenztypen
− Variablen vom Referenztyp enthalten Verweise (Zeiger) auf ihre Daten im Heap
− Variablen vom Referenztyp selbst werden im Stack oder im Heap gespeichert
− Referenztypen: object und string, dazu Arrays (array), Klassen (class), Schnittstellen (interface) und Delegaten (delegate) als nutzerdefinierte Datentypen
− es existiert keine "Referenzarithmetik"
Programmierung II - 14 -
2.6 Variablendeklarationen und Zuweisungen für Referenztypen
Syntax
variable-declaration
name =
,
;type new constructor
• ein (Standard-)Konstruktor hat die Form: <type>( )
• eine Referenzvariable muß vor ihrer Verwendung instanziiert werden
Syntax
variable-instanciation
name = ;new constructor
• Besonderheiten
− string- und object-Variablen brauchen nicht instanziiert zu werden
− struct-Variablen können instanziiert werden
Beispiel 2.6.a
namespace Beispiel{
struct MyStruct{
public int x;public int y;
}class MyClass{
Programmierung II - 15 -
public int v;public int w;
}class Program{
static void Main(string[] args){
int i = 3;
string s = "Hallo";object o = 17;
MyStruct t;t.x=6;
MyClass c1 = new MyClass();c1.w = 8;
MyClass c2 = c1;}
}}
2.7 Boxing und Unboxing
Beispiel
class MyClass{
public int v;public int w;
}. . .MyClass c1 = new MyClass();c1.w = 8;Console.WriteLine("{0}", c1.w);MyClass c2 = c1;Console.WriteLine("{0} {1}", c1.w, c2.w);c2.w = 3;Console.WriteLine("{0} {1}", c1.w, c2.w);
Beispiel
object o1 = new object(); // auch object o1;o1=8;Console.WriteLine("{0}",o1);object o2=o1;Console.WriteLine("{0} {1}",o1,o2);o2=3;Console.WriteLine("{0} {1}",o1,o2);
Programmierung II - 16 -
• Boxing
Implizite Konvertierung eines Wertetyps in den Typ Object durch die .NET-Laufzeitumgebung bei der Zuweisung eines Wertes an eine Objekt-Referenz
<object-variable> = <value-variable>
• intern wird Speicherplatz in Heap allokiert und der Wert der Wertevariable dort abgelegt (Kopie)
• Unboxing
Explizite Konvertierung eines Objekttyps in einen Wertetyp
<value-variable> = ( <value-type> ) <object-variable>
• Wertetyp muß mit dem Typ der Wertevariablen entsprechend Konvertierungsregeln übereinstimmen
• .NET-Laufzeitsystem kopiert Wert, den die object-variable referenziert, in werte-variable
• auch Methode Convert.To<value-type>( <object-variable> ) möglich
Beispiel
object o = 1; // Boxingint i = (int) o; // Unboxingint i = Convert.ToInt32(o); // Unboxing
Programmierung II - 17 -
3 Konsolen-Eingabe und -Ausgabe
• Ein-/Ausgabemethoden der Klasse Console definiert im namespace System:
− Console.WriteLine
− Console.Write
− Console.ReadLine
− Console.Read
3.1 Formatierte Ausgabe
• Deklarationen in der Klasse Console:
public static void WriteLine (string format, params Object[] arg)public static void Write (string format, params Object[] arg)
• geben Werte der Argumente entsprechend der Formatzeichenkette aus
• WriteLine schließt Ausgabe mit '\n' ab, Write nicht
• für WriteLine und Write existieren weitere, nicht CLS-compatible Überladungen der Methoden
Syntax
<formatierte ausgabe> ::=Console.WriteLine ( " <zeichenfolge> " [, <ausdruck> ]... )Console.Write ( " <zeichenfolge> " [, <ausdruck> ]... )
<zeichenfolge> ::=<zeichen > [ <zeichenfolge> ]...
| <formatausdruck> [ <zeichenfolge> ]...
<formatausdruck> ::={ <n> [ , <m> ] [ : <format> ] }
• <n>: nullbasierter Zähler für Ausdrucksliste
• <m>: Breite der Ausgabe des n-ten Ausdrucks
<format> ::=
D - Anzeige als Integer | E - Anzeige in Exponentialschreibweise | F - Anzeige im Festpunktformat | X - Anzeige Hexadezimal | C - Anzeige im lokalenWährungsformat | ...
Programmierung II - 18 -
• Ausgabe von Escape-Zeichen möglich: \n, \t, ...
Beispiel
Console.WriteLine("Hello World!");
string s1 = "Hello";string s2 = "World";Console.WriteLine("{0} {1}!", s1, s2);
double d = 3.14;int i = 5;Console.WriteLine("i={1} d={0}", d, i);
Console.WriteLine("d={0,15:F}\nd={0,15:E}", d);
3.2 Werteeingabe
Zeichenketteneingabe
• Deklaration in der Klasse Console
public static string Console.ReadLine( )
• liest ein oder mehrere Zeichen aus dem Eingabestrom (Tastatur)
• liefert Zeichenkette zurück
• Eingabe endet nach Einlesen von \n, \n nicht Bestandteil der Zeichenkette
Beispiel
static void Main(string[] args){
string s;s = Console.ReadLine();Console.WriteLine("{0}", s);
}
• für jeden einfachen Datentyp existiert überladene Methode zur Konvertierung eines String in einen Datentyp:
public static <datentyp>Parse( <string> )
Programmierung II - 19 -
Beispiel
static void Main(string[] args){
int i;i = Int32.Parse(Console.ReadLine());Console.WriteLine("{0}", i + 3);
double d; d = Double.Parse(Console.ReadLine());
Console.WriteLine("{0:E}", d);
}
Zeicheneingabe
• Deklaration in der Klasse Console
public static int Console.Read( )
• liest ein Zeichen aus Eingabestrom und gibt dessen ASCII-Code als int zurück
• liest, solange noch Zeichen im Eingabestrom vorhanden
Beispiel
static void Main(string[] args){
int i;i = Console.Read(); Console.WriteLine("{0}", i);
i = Console.Read();Console.WriteLine("{0}", i); Console.ReadLine();i = Console.Read();Console.WriteLine("{0}", i);
}
Programmierung II - 20 -
4. Operatoren
Primär x.y -> f(x) a[x] x++ x-- new typeof checked uncheckedUnär + - ! ~ ++x --x (type)xArithmetisch * / % + -Verschiebung << >>Relational und Typtest < > <= >= is asGleichheit == !=Logisch & ^ |Bedingt && || ?:Zuweisung = += -= *= /= %= &= |= ^= <<= >>= =>
• alle Operatoren aus C++ auch in C# verfügbar
• neue Operatoren: typeof, checked/unchecked, is/as und =>
typeof-Operator
Liefert das System.Type-Objekt für einen Datentyp.
is/as-Operator
Der is-Operator überprüft, ob ein Objekt mit einem bestimmten Typ kompatibel ist. Der as-Operator wird verwendet, um Konvertierungen zwischen kompatiblen Typen auszuführen.
=> Operator
Lambda-Operator
Programmierung II - 21 -
checked/unchecked-Operator
Syntax
<checked-statement> ::=
checked <block>
• aktiviert explizit die Überlaufprüfung für arithmetische Operationen und Konvertierungen mit ganzzahligen Typen innerhalb des Anweisungsblockes
Syntax
<unchecked-statement> ::=
unchecked <block>
• unterdrückt explizit die Überlaufprüfung für arithmetische Operationen und Konvertierungen mit ganzzahligen Typen innerhalb des Anweisungsblockes
Beispiel
static void Main(string[] args){
int i=2147483647; // Bereich int (Int32): -2 147 483 648 ... 2 147 483 647int j=1;checked{
Console.WriteLine("{0}",i+j);}
}
Beispiel
static void Main(string[] args){
int i=2147483647; // Bereich int (Int32): -2 147 483 648 ... 2 147 483 647int j=1;unchecked{
Console.WriteLine("{0}",i+j);}
}
Programmierung II - 22 -
5. Anweisungen
• alle Steueranweisungen aus C++ auch in C# verfügbar
− if-statement
− switch-statement
− while-statement
− do-while-statement
− for-statement
• neue Anweisungen in C#:
foreach-statement
Syntax
foreach-declaration
foreach name( type )arrays-name blockin
• für jedes array-Element wird der Anweisungsblock ausgeführt
• Zugriff auf die Elemente durch variable
Beispiel
int[] arr = {1,2,3,4,5};foreach (int i in arr)
Console.WriteLine("{0}", i);
try/catch/finally-statement
dient der Ausnahmebehandlung (Exceptions)
throw-statement
explizites Werfen von Ausnahmen in einer Anwendung
Programmierung II - 23 -
using-statement
Impliziter Aufruf des Garbage Collectors zum Freigeben von Objekten
Modifikation switch-statement
• Regel: enthält ein case-Zweig eine Anweisung, dann muß er mit einer Sprunganweisung (break oder goto) abschließen
Beispiel (in C++ zulässig)
int _tmain(int argc, _TCHAR* argv[]){
char c='A';switch(c){case 'A': case 'B':printf("A oder B");case 'C': printf("C");break;default:printf("D");}return 0;
}
Besipiel (in C# nicht zulässig)
static void Main(string[] args){
char c='A';switch(c){
case 'A': case 'B':
Console.WriteLine("A oder B");case 'C':
Console.WriteLine("C");break;
default:Console.WriteLine("D");
}}
Programmierung II - 24 -
Beispiel
static void Main(string[] args){
char c='A';switch(c){
case 'A': case 'B':
Console.WriteLine("A oder B");break;
case 'C': Console.WriteLine("C");break;
default:Console.WriteLine("D");break;
}}
• neben break weitere Sprunganweisung: goto
Syntax
<goto-statement> ::=
goto case <const-expression>
Beispiel
static void Main(string[] args){
char c='A';switch(c){
case 'A': Console.Write("A");goto case 'B';
case 'B':Console.Write("B");goto case 'C';
case 'C': Console.Write("C");break;
}}
Programmierung II - 25 -
6 Komplexe Wertetypen
6.1 Aufzählungen
• Ein Enumeration-Typ deklariert eine Reihe von benannten Konstanten
Syntax
enum-declaration
enum-modifier enum name
{ }
enum-modifier
type:
name =
,
expression
access-modifier
• Variablen eines Enumeration-Typs können die in der Aufzählungsliste definierten Konstanten-Werte annehmen
• alle Konstanten-Werte sind vom Basistyp, der nur ein ganzzahliger Typ sein kann (byte ... ulong). Default: int
• wird kein Wert explizit zugewiesen, erhalten die Konstanten aufsteigend von 0 beginnend int-Werte
• Enumeration-Deklarationen sind nur innerhalb von Namespace-Deklarationen, Klassen-Deklarationen und Struktur-Deklarationen zulässig.
• Außerhalb einer Klassen- oder Struktur-Dekaration ist als Zugriffsmodifizierer nur public oder internal zulässig. Default: internal
Programmierung II - 26 -
• Innerhalb einer Struktur-Deklaration (Strukturmember) sind nur die Zugriffsmodifizierer public, internal oder private erlaubt. Default: private
• Innerhalb einer Klassen-Deklaration (Klassenmember) sind alle Zugriffsmodifizierer erlaubt. Default: private
Beispiel 6.1.a
namespace Beispiel{
class Program{
public enum Geld : byte{
einer = 1,zweier, // implizit 2fünfer = 5,zehner = 2 * fünfer,fünfziger = 5 * zehner
}static void Main(string[] args){
int meinGeld = (int)Geld.zehner + 2 * (int)Geld.fünfziger;Console.WriteLine("{0}", meinGeld);
}}
}
• die Werte eines Enumeration-Typs sind nicht auf die Werte der Konstanten beschränkt
Programmierung II - 27 -
6.2 Strukturen
6.2.1 Strukturdeklaration
Syntax
struct-declaration
struct-modifier struct name {
struct-member
}
struct-modifier struct-member
constant-field-declaration
field-declarationaccess-modifier
struct-declaration
• beginnt mit Schlüsselwort struct gefolgt von einem Strukturtyp-Namen und einem Block mit den Strukturelementen
• Struktur-Deklarationen sind nur innerhalb von Namespace-Deklarationen, Klassen-Deklarationen und Struktur-Deklarationen zulässig.
• Außerhalb einer Klassen- oder Struktur-Dekaration ist als Zugriffsmodifizierer nur public oder internal zulässig. Default: internal
• Innerhalb einer Klassen-Deklaration (Klassenmember) sind alle Zugriffsmodifizierer erlaubt. Default: private
• Innerhalb einer Struktur-Deklaration (Strukturmember) sind nur die Zugriffsmodifizierer public, internal oder private erlaubt. Default: private
Programmierung II - 28 -
• Variablen-Felder können in Strukturen nicht initialisiert werden
Beispiel 6.2.1.a
namespace Beispiel{
public struct Person{
public string name;public int alter;public struct Adresse{
public string ort;public string strasse;
}public Adresse anschrift;
} class Program { static void Main(string[] args) {
Person student;student.name="Meier";student.alter=25;student.anschrift.ort="HST";student.anschrift.strasse="Hafenweg";Console.WriteLine("{0}, {1}, {2}, {3}",student.name, student.alter,
student.anschrift.ort, student.anschrift.strasse);}
}}
6.2.2 Methoden als Strukturmember (encapsulation)
• Kapselung: Verfahren, bei dem Daten und Funktionalität von Objekten zu einem Typ zusammengefaßt werden.
komplexer Datentyp (struct) ⇒ abstrakter Datentyp (class)
Beispiel 6.2.2.a
namespace Beispiel{
class Program{
static void Main(string[] args){
int zaehler1 = 1;int nenner1 = 2;int zaehler2 = 2;
Programmierung II - 29 -
int nenner2 = 3;int zaehler3;int nenner3;zaehler3 = zaehler1 * nenner2 + zaehler2 * nenner1;nenner3 = nenner1 * nenner2;Console.WriteLine("{0}/{1}", zaehler3, nenner3);
}}
}
Beispiel 6.2.2.b
namespace Beispiel{
class Program{
struct Bruch{
public int zaehler;public int nenner;
}static Bruch Init(int z, int n){
Bruch b;b.zaehler = z;b.nenner = n;return b;
}static Bruch Addiere(Bruch b1, Bruch b2){
Bruch b;b.zaehler = b1.zaehler * b2.nenner + b2.zaehler * b1.nenner;b.nenner = b1.nenner * b2.nenner;return b;
}static void Main(string[] args){
Bruch bruch1 = Init(1,2);Bruch bruch2 = Init(2,3);Bruch bruch3;bruch3=Addiere(bruch1, bruch2);Console.WriteLine("{0}/{1}", bruch3.zaehler, bruch3.nenner);
}}
}
Programmierung II - 30 -
• Ein abstrakter Datentyp ist eine Struktur/Klasse, deren Komponenten
− Daten und
− Funktionen, die auf die Datenkomponenten zugreifen
sind.
• gekapselte Funktionen heißen Methoden
Syntax
struct-member
constant-field-declaration
field-declaration
struct-declaration
method-declaration
Beispiel 6.2.2.c
namespace Beispiel{
class Program{
struct Bruch{
public int zaehler;public int nenner;public void Init(int z, int n){
zaehler = z;nenner = n;
}public Bruch Addiere(Bruch b2){
Bruch b;b.zaehler = zaehler * b2.nenner + b2.zaehler * nenner;b.nenner = nenner * b2.nenner;return b;
}}static void Main(string[] args){
Bruch bruch1 = new Bruch();
Programmierung II - 31 -
Bruch bruch2 = new Bruch();Bruch bruch3 = new Bruch();
bruch1.Init(1, 2);bruch2.Init(2, 3);bruch3 = bruch1.Addiere(bruch2);Console.WriteLine("{0}/{1}", bruch3.zaehler, bruch3.nenner);
}}
}
• Strukturen sollten nur dann verwendet werden, wenn deren Komplexität gering ist
6.2.3 Einfache Typen als Strukturen
• zu jedem einfachen Datentyp ist in C# eine Struktur deklariert. Ausnahme: für string und object sind Klassen deklariert
Datentyp Strukturbyte Bytesbyte SByteshort Int16ushort UInt16int Int32uint UInt32long Int64ulong UInt64float Singledouble Doubledecimal Decimalchar Charbool BooleanDatentyp Klassestring Stringobject Object
• Auf die Member dieser Strukturen (Methode, Eigenschaften) können Anwendungsprogramme zugreifen
Beispiel
char c = 'a';Console.WriteLine("{0} {1}", c, Char.ToUpper(c)); // Ausgabe: a A
MyStruct structObject = new MyStruct();Console.WriteLine("{0}", structObject.GetType()); // Ausgabe: Beispiel.Program+MyStruct
• Datentypen und zugehörige Strukturen können gleichberechtigt verwendet werden
Programmierung II - 32 -
7 Klassen und Klassenmember
• allgemeine Merkmale von Objekten
− Zustände
− Fähigkeiten, zu agieren
− Fähigkeit, zu reagieren
• Eine Klasse ist ein Datentyp (Datenstruktur), in der Zustände als Felder und Fähigkeiten als Methoden gekapselt werden.
• Die Felder und Methoden einer Klasse bezeichnet man als Member
• Ein Objekt ist eine dynamisch erstellte Instanz einer Klasse
7.1 Einführung UML: Klassen
• Softwareentwicklungsprozeß
− Anforderungsanalyse
− Entwuf
− Implementierung
Unified Modelling Language
• UML
− Standardisierte Sprache zur Modellierung von Software- und anderen Systemen
− Definiert die dafür erforderlichen Begriffe und Begiffsbeziehungen
• entwickelt von der Object Management Group (OMG), Standard seit 1998
• Modellelemente der UML
− Anwendungsfalldiagramm
− Klassendiagramm
− Aktivitätsdiagramm
− Kollaborationsdiagramm
− Sequenzdiagramm
− Zustandsdiagramm
− Komponentendiagramm
− Einsatzdiagramm
Programmierung II - 33 -
• Klassendiagramm
Grafische Darstellung von Klassen und deren Beziehungen untereinander zur (statischen) Modellierung objektorientierter Softwaresysteme
UML: Klassen
• Klassen bestehen aus Membern:
− Felder (auch Datenmember, Eigenschaften, Attribute, Datenelemente, Instanzvariablen ...)
− Methoden (auch Operationen, Prozeduren, Routinen, Funktionen, Services, ...)
klassenname
felder
methoden
oder
klassenname
felder oder
klassenname
methoden
• klassenname
− Name der Klasse im Singular
− Darstellung in PascalConvention: beginnt mit Großbuchstaben
• felder
− Folge von Feldbeschreibungen der Form
modifizierer feldname : typ = initialwert { eigenschaft }
− feldname in camelConvention: beginnt mit Kleinbuchstaben
− mögliche modifizierer:
+ für public- für private# für protected
/ für abgeleitetes (berechnetes) Feld
− eigenschaft ist zusätzliche Eigenschaft, z.B. {ReadOnly}
− alle Bestandteile außer feldname sind optional
Programmierung II - 34 -
• methoden
− Folge von Methodenbeschreibungen der Form
modifizierer methodenname ( argumentliste ) : rückgabetyp { eigenschaft }
− modifizierer wie bei Feldern außer /
− argumentliste ist kommagetrennte Folge von Argumentbeschreibungen:
richtung argumentname : typ = initialwert
− richtung: in-, out- oder inout-Parameter
− methodenname in PascalConvention: beginnt mit Großbuchstaben
− alle Bestandteile außer methodenname sind optional
• Notizen
− Anmerkungen zu Klassen, Feldern oder Methoden
− als Rechteck mit Eselsohr dargestellt
Beispiel
Konto
- kontonr : int {ReadOnly}- inhaber : String- kontostand : float = 0,0
+ InhaberÄndern ( inhaber : string ) : void+ Einzahlung ( betrag : float ) : void+ Auszahlung ( betrag : float ) : void+ Kontostand ( ) : float+ Kontendaten ( out kontonr : int, out inhaber : String ): void
Kontoinhaber sollvolljährig sein
Programmierung II - 35 -
7.2 Klassendeklaration
Syntax
class-declaration
class-modifier class name
{
class-member
}
class-modifier
class-base:
static
sealed
abstract
partialaccess-modifier
• class-base
Basis-Klasse oder (-Interface), von der die Klasse abgeleitet ist
• access-modifier
− eine außerhalb einer Klasse deklarierte Klasse kann nur die Zugriffsmodifizierer public oder internal besitzen
− sonst: alle
• weitere class-modifier:
abstractDefinition einer abstrakten Klasse, keine Klassenobjekte erzeugt
staticDefinition einer statischen Klasse, nur einmal pro Klasse initialisiert
sealed Definition einer versiegelten Klasse, nicht abgeleitet
partialüber mehrere Dateien verteilt definierte Klasse
Programmierung II - 36 -
Syntax (Fortsetzung)
class-member
constant-field-declaration
field-declaration
method-declaration
property-declaration
event-declaration
indexer-declaration
operator-declaration
constructor-declaration
destructor-declaration
static-constructor-declaration
type-declaration
enum-declaration
struct-declaration
class-declaration
interface-declaration
delegate-declaration
type-declaration
• Klassenmember:
Felderimplementieren den statischen Zustand eines Klassenobjektes
Methodenimplementieren die Funktionalität von Klassenobjektenspezielle Methoden:
− Konstruktoren und Destruktoren
− Eigenschaften
− Indexer
− (überladene) Operatoren
− Ereignisse
Programmierung II - 37 -
• Klassenobjekte werden generell durch Aufruf eines Konstruktors mit dem new-Operator erzeugt (instanziiert) und die Referenz auf den Speicher einer Variablen zugewiesen
class-name variable;variable = new class-name();oder
class-name variable = new class-name();
• jede deklarierte Klasse erhält vom System einen Standardkonstruktor class-name(), der nach Aufruf alle Klassenfelder initialisiert
• der Zugriff auf Klassenmember von außerhalb einer Klasse erfolgt durch Punktnotation
variable.member
7.3 Felder
• Felder sind Variablen- oder Konstantendeklarationen innerhalb von Klassen
Syntax
field-declaration
variable-declaration
static
volatile
readonly arrays-declarationaccess-modifier
• access-modifier
public, protected, private, internal, protected internal
• static
Statische Felder sind nicht Teil einer bestimmten Instanz, sondern existieren für eine Klasse nur einmal
• readonly
Schreibgeschützte Felder
• volatile
Flüchtige Felder
Beispiel 7.3.a
Programmierung II - 38 -
namespace Beispiel{
class ValueSet{
public int count;public int[] values=new int[100];
}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.values[0]=33;vs.count++;vs.values[1]=-6;vs.count++;for(int i=0; i<vs.count; i++)
Console.WriteLine("{0}", vs.values[i]);}
}}
Syntax
constant-field-declaration
constant-declaration
access-modifier
Programmierung II - 39 -
7.4 Methoden
7.4.1 Methodendeklaration
Syntax
method-declaration
method-modifier
void
name
{
constant-declaration
}
method-modifier
parameter(
static
virtual
sealed
new
override
abstract
extern
type )
variable-declaration
arrays-declaration
;
block
block
access-modifier
statement
Programmierung II - 40 -
• der Methodenblock (-rumpf) enthält ausschließlich Konstanten-, Variablen- und Arraydeklarationen sowie Anweisungen, also weder Feld- noch weitere Methoden- oder Typdeklarationen
• new
explizites Verdecken der Methode einer Basisklasse durch eine gleichnamige Methode einer abgeleiteten Klasse
• sealed
versiegelte Methode, verhindert ein erneutes Überschreiben der Methode durch eine abgeleitete Klasse
• abstract
abstrakte Methode, besitzt keinen Methodenrumpf und muß in einer abgeleiteten Klasse implementiert werden
• static
statische Methode, bezieht sich nicht auf ein Klassenobjekt, sondern auf die Klasse als Ganzes
• virtual
virtuelle Methode, bei der beim Aufruf der Laufzeittyp der Instanz, für die der Aufruf ausgeführt wird, die tatsächlich aufzurufende Methodenimplementierung bestimmt.
• extern
Externe Methode, wird extern implementiert (meist eine andere Sprache als C#)
• Regeln
− new und override dürfen nicht zusammen auftreten
− abstract und extern dürfen nicht zusammen auftreten
− abstract und extern haben ; statt block {}
− private darf nicht zusammen mit virtual, override oder abstract auftreten
Beispiel 7.4.1.a
namespace Beispiel{
class ValueSet{
public int count;public int[] values = new int[100];public void PrintValues(){
for (int i = 0; i < count; i++)Console.Write("{0} ", values[i]);
Console.WriteLine();}
}class Program{
Programmierung II - 41 -
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.values[0] = 33;vs.count++;vs.values[1] = -6;vs.count++;vs.PrintValues();
}}
}
7.4.2 Parameter einer Methode
Syntax (Fortsetzung)
parameter
type name
ref
out
,
params type name[ ]
• void als Parameter existiert nicht
Werteparameter
• Parameterübergabe call-by-value: Initialisierung des Parameters durch
datentyp variablen-name = ausdruck und damit Übergabe einer Kopie des Parameterwertes an den Methodenrumpf
Beispiel 7.4.2.a
namespace Beispiel{
class ValueSet{
public int count;public int[] values = new int[100];public void PrintValues() {. . .}
public bool AddValue(int value)
Programmierung II - 42 -
{for(int i=0; i<count; i++)
if(values[i]==value) return false;values[count]=value;count++;return true;
}}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValue(33);vs.AddValue(-6);vs.AddValue(15);vs.PrintValues();
}}
}
Beispiel
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValue(33);vs.AddValue(-6);vs.AddValue(15);vs.values[vs.count++]=33;vs.PrintValues();
}
Beispiel
class ValueSet{
private int count;private int[] values = new int[100];public bool AddValue(int value) {. . .}public void PrintValues() {. . .}
}
• eine korrekte Klassendefinition verbirgt ihre Felder nach außen
Programmierung II - 43 -
Referenzparameter
Beispiel
class MyClass{
public void MyMethod(int i, string s){s = "World";i = 7;}
}class Program{
static void Main(string[] args){MyClass C = new MyClass();string z = "Hello";int j = 3; Console.WriteLine("{0} {1}", z, j); // Ausgabe: 'Hello 3C.MyMethod(j, z);Console.WriteLine("{0} {1}", z, j); // Ausgabe: 'Hello 3}
}
• call-by-value wirkt auf Werteobjekte und String-Referenzobjekte in der gleichen Weise
• Parameterübergabe call-by-reference: Initialisierung des Parameters
ref datentyp variablen-name = variablen-name mit einer Objektreferenz
• alle mit ref als Referenzparameter definierten Parameter müssen mit ref aufgerufen werden
• als Parameter dürfen nur Variablen übergeben werden
• Variablen müssen aktuell ein Objekt referenzieren
Beispiel 7.4.2.b
namespace Beispiel{
class ValueSet{
private int count;private int[] values = new int[100];public void PrintValues() {. . .}
public bool AddValue(int value) {. . .}public bool NextGreaterValue(ref int value){
Programmierung II - 44 -
if (count == 0) return false;int nextValue = 0;int i = 0;while (i < count) {
if (values[i] > value){
nextValue = values[i];break;
}i++;
}if (i == count) return false;while (i < count){
if ((values[i] > value) && (values[i] < nextValue))nextValue = values[i];
i++;}value = nextValue;return true;
}}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValue(33);vs.AddValue(-6);vs.AddValue(15);vs.AddValue(-15);
int value = -15;while (vs.NextGreaterValue(ref value) == true)
Console.WriteLine("{0} ", value); // Ausgabe: -6 15 33}
}}
Beispiel
class MyClass{
public void MyMethod(ref int i, ref string s){
s = "World";i = 7;
}}class Program{
static void Main(string[] args){
MyClass C = new MyClass();
Programmierung II - 45 -
string z = "Hello";int j = 3;Console.WriteLine("{0} {1}", z, j); // Ausgabe: 'Hello' 3C.MyMethod(ref j, ref z);Console.WriteLine("{0} {1}", z, j); // Ausgabe: 'World' 7
}}
• call-by-reference wirkt auf Werteobjekte und String-Referenzobjekte in der gleichen Weise
• bei Übergabe von Werteobjekten als Referenzparameter: Boxing
Out-Parameter
• Out-Parameter sind spezielle Referenzparameter, die bei der Übergabe an eine Methode noch kein Objekt referenzieren müssen
• Parameterübergabe call-by-reference: Initialisierung des Parameters
out datentyp variablen-name = variablen-name mit einer Objektreferenz
• alle mit out als Referenzparameter definierten Parameter müssen mit out aufgerufen werden
• als Parameter dürfen nur Variablen übergeben werden
Beispiel 7.4.2.c
namespace Beispiel{
class ValueSet{
private int count;private int[] values = new int[100];public void PrintValues() {. . .}
public bool AddValue(int value) {. . .}public bool NextGreaterValue(ref int value) {. . .}public bool GetMin(out int min){
min = 0;if (count == 0) return false;for (int i = 0; i < count; i++)
if (values[i] < min)min = values[i];
return true;}
}class Program
Programmierung II - 46 -
{static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValue(33);vs.AddValue(-6);vs.AddValue(15);vs.AddValue(-15);vs.PrintValues();
int minimum;if (vs.GetMin(out minimum) == true)
Console.WriteLine("{0} ", minimum);else
Console.WriteLine("Leere Menge");}
}}
Übergabe von Klassenobjekten
• Typen, die auf Klassendefinition basieren, sind Referenztypen
Beispiel
class MyClassA{
public string s = "Hello";public int i = 3;
}class MyClassB{
public void MyMethod(MyClassA obj){obj.s = "World";obj.i = 7;}
}class Program{
static void Main(string[] args){MyClassA objA = new MyClassA();MyClassB objB = new MyClassB();Console.WriteLine("{0} {1}", objA.s, objA.i);objB.MyMethod(objA);Console.WriteLine("{0} {1}", objA.s, objA.i);}
}
Programmierung II - 47 -
Beispiel 7.4.2.d
namespace Beispiel{
class ValueSet{
private int count;private int[] values = new int[100];public void PrintValues() {. . .}
public bool AddValue(int value) {. . .}public bool NextGreaterValue(ref int value) {. . .}public bool GetMin(out int min) {. . .}public void Union(ValueSet v) {
int min;v.GetMin(out min);min--;while(v.NextGreaterValue(ref min)==true)
AddValue(min);}
}class Program{
static void Main(string[] args){
ValueSet vs1 = new ValueSet();vs1.AddValue(9); vs1.AddValue(7); vs1.AddValue(5); vs1.AddValue(-3);vs1.PrintValues();ValueSet vs2 = new ValueSet();vs2.AddValue(-5); vs2.AddValue(5); vs2.AddValue(3);vs2.PrintValues();vs1.Union(vs2);vs1.PrintValues(); // Ausgabe: 9 7 5 -3 -5 3
}}
}
Programmierung II - 48 -
Parameterlisten
• params definiert ein beliebig langes Array von Parametern gleichen Datentyps
params arraytyp variablen-name
• Einschränkungen (siehe Syntax):
− nur ein params-Array in einer Parameterliste
− params-Array ist immer das letzte Element in einer Liste
− Referenzparameter als params-Array nicht erlaubt
Beispiel 7.4.2.e
namespace Beispiel{
class ValueSet{
private int count;private int[] values = new int[100];public void PrintValues() {. . .}
public bool AddValue(int value) {. . .}. . .
public bool AddValues(int cnt, params int[] val){
for (int i = 0; i < cnt; i++)for (int j = 0; j < count; j++)
if (values[j] == val[i]) return false;for (int i = 0; i < cnt; i++)
values[count++] = val[i];return true;
}}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValue(33);vs.AddValue(-6);vs.AddValue(15);vs.AddValue(-15);vs.PrintValues();vs.AddValues(3, 7, 5, -3);vs.PrintValues(); // Ausgabe: 33 -6 15 -15 7 5 -3
}}
}
Programmierung II - 49 -
Selbstreferenz
• Referenz this, mit der innerhalb einer Klasse auf die Klasse selbst verwiesen wird
• Zugriff auf Klassenmember innerhalb der Klasse durch
this. member-name
Beispiel
public bool AddValues(int count, params int[] values){
for (int i = 0; i < count; i++)for (int j = 0; j < this.count; j++)
if (this.values[j] == values[i]) return false;for (int i = 0; i < count; i++)
this.values[this.count++] = values[i];return true;
}
Beispiel
public bool AddValues(params int[] values){
foreach (int element in values)for (int j = 0; j < this.count; j++)
if (this.values[j] == element) return false;foreach (int element in values) // Elemente einfügen
this.values[this.count++] = element;return true;
}
Programmierung II - 50 -
7.4.3 Rückgabewerte einer Methode
Objektreferenzen als Rückgabewert
Beispiel 7.4.3.a
namespace Beispiel{
class ValueSet{
private int count;private int[] values = new int[100];public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}. . .public ValueSet IncAll(ValueSet v){
for (int i = 0; i < count; i++)values[i]++;
return v;}
}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValues(9, 7, 5, -3);vs.PrintValues();
vs.IncAll(vs);vs.IncAll(vs);vs.PrintValues(); // Ausgabe: 11 9 7 -1
vs.IncAll(vs.IncAll(vs));vs.PrintValues(); // Ausgabe: 13 11 9 1
}}
}
Programmierung II - 51 -
Selbstreferenz als Rückgabewert
Beispiel 7.4.3.b
namespace Beispiel{
class ValueSet{
private int count;private int[] values = new int[100];public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}. . .public ValueSet IncAll(){
for (int i = 0; i < count; i++)values[i]++;
return this;}
}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValues(4, 9, 7, 5, -3);vs.PrintValues();
vs.IncAll();vs.IncAll();vs.PrintValues();
vs.IncAll().IncAll();vs.PrintValues();
}}
}
Programmierung II - 52 -
7.4.4 Überladen von Methoden
• Zwei Methoden heißen überladen, wenn sie den gleichen Namen besitzen und
− sich in Anzahl und/oder Typ ihrer Parameter unterscheiden oder
− wenn sich zwei typgleiche Parameter nur darin unterscheiden, daß der eine als Referenz- und der andere als Werteparameter deklariert ist
• Compiler und JITter unterscheiden verschiedene Versionen einer Methode anhand von Typ und Anzahl der Parameter
⇒ (Name, Typen (incl. Wert/Referenz), Anzahl) = Signatur einer Methode
Beispiel
vs.IncAll(vs) ruft public ValueSet IncAll(ValueSet v) aufvs.IncAll() ruft public ValueSet IncAl() auf
• besitzen mehrere überladene Methoden eine Parameterliste sowie mehrere Parameter des gleichen Typs, dann wird (soweit möglich) die Methode mit den meisten Parametern aufgerufen
Beispiel 7.4.4.a
namespace Beispiel{
class MyClass{
public void MyMethod(params int[] i){
Console.WriteLine("M1");}public void MyMethod(int a, params int[] i){
Console.WriteLine("M2");}public void MyMethod(int a, int b, params int[] i){
Console.WriteLine("M3");}public void MyMethod(int a, int b, int c, params int[] i){
Console.WriteLine("M4");}
}class Program{
static void Main(string[] args){
MyClass C = new MyClass();C.MyMethod(1,2); // Ausgabe M3
Programmierung II - 53 -
C.MyMethod(1,2,3,4); // Ausgabe M4}
}}
Beispiel 7.4.4.b
namespace Beispiel{
class MyClass{
public void MyMethod(int i){
Console.WriteLine("M1");}public void MyMethod(ref int i){
Console.WriteLine("M2");}
}class Program{
static void Main(string[] args){
MyClass C = new MyClass();int i=1;C.MyMethod(i); // Ausgabe M1C.MyMethod(ref i); / Ausgabe M2
}}
}
• Wird als Aufrufparameter ein Typ verwendet, der nicht definiert ist, versucht der Compiler in den höherwertigen Typ zu konvertieren
Beispiel 7.4.4.c
namespace Beispiel{
class MyClass{
public void MyMethod(float f){
Console.WriteLine("M1");}public void MyMethod(double d){
Console.WriteLine("M2");}
}class Program{
static void Main(string[] args)
Programmierung II - 54 -
{MyClass C = new MyClass();C.MyMethod(1); // Ausgabe M1C.MyMethod(1.1); // Ausgabe M2
}}
}
7.5 Konstruktoren
Beispiel
class ValueSet{
private int count;private int capacity;private int[] values; public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}. . .
}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValues(4, 9, 7, 5, -3);
}}
Beispiel 7.5.a
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}
public void Initialize(int capacity){
this.capacity = capacity;this.values = new int[capacity];
}
}class Program{
static void Main(string[] args){
Programmierung II - 55 -
ValueSet vs = new ValueSet();vs.Initialize(50);vs.AddValues(4, 9, 7, 5, -3);vs.PrintValues();
}}
}
• Konstruktoren einer Klasse sind spezielle Methoden, die bei der Instanziierung der Klasse mit new aufgerufen werden und die Felder initialisieren
Konstruktordeklaration
Syntax
constructor-declaration
constructor-modifier name
constructor-modifier
public
protected
internal
prot. int.
private
argument
(
extern
)
block
base:
this
parameter( )
Programmierung II - 56 -
• name: ein Konstruktor hat generell den gleichen Namen wie die Klasse
• :this/:base: Verkettung des Konstruktors mit einem andren Konstruktor der gleichen Klasse/der Basisklasse
• Konstruktoren geben keinen Wert zurück
Instanziierung von Klassenobjekten
• die Instanziierung einer Klasse erfolgt generell durch Aufruf eines Konstruktors:
object-reference = new constructor ;
• Instanziierungsprozeß:
1. Speicherallokation für das Objekt im Heap
2. Initialisierung der Felder mit Initialwerten (falls definiert), sonst mit 0, "" oder null
3. Aufruf des Konstruktors
• wurde kein Konstruktor explizit deklariert, erzeugt das System implizit einen parameterlosen Standardkonstruktor: class-name ( )
Beispiel 7.5.b
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;public ValueSet(int capacity){
this.capacity = capacity;this.values = new int[capacity];
}public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}
. . .}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet(50);vs.AddValues(4, 9, 7, 5, -3);vs.PrintValues();
}}
}
• sobald ein Konstruktor definiert wurde, wird der implizite Standardkonstruktor nicht mehr erzeugt
Programmierung II - 57 -
• Konstruktoren können überladen werden
Beispiel 7.5.c
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;public ValueSet(){
capacity = 100;values = new int[100];
}public ValueSet(int capacity) {. . .}
public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}
. . .}class Program{
static void Main(string[] args){
ValueSet vs1 = new ValueSet(50);vs1.AddValues(4, 9, 7, 5, -3);vs1.PrintValues();ValueSet vs2 = new ValueSet();vs2.AddValues(6, 8, 1);vs2.PrintValues();
}}
}
Programmierung II - 58 -
Konstruktorverkettung
Beispiel
public ValueSet(int capacity, params int[] values){
this.capacity = capacity;this.values = new int[capacity];this.AddValues(values);
}
• Über Referenz :this kann für einen Konstruktor die Funktionalität eines anderen, bereits definierter Konstruktors der gleichen Klasse aufgerufen werden
Beispiel 7.5.d
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;public ValueSet() {. . .}public ValueSet(int capacity) {. . .}public ValueSet(int capacity, params int[] values): this(capacity){
this.AddValues(values);}public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}
. . .}
}class Program{
static void Main(string[] args){
ValueSet vs1 = new ValueSet(50);vs1.AddValues(4, 9, 7, 5, -3);vs1.PrintValues();ValueSet vs2 = new ValueSet();vs2.AddValues(6, 8, 1);vs2.PrintValues();ValueSet vs3 = new ValueSet(70, 2, 5, -8);vs3.PrintValues();
}}
}
Programmierung II - 59 -
7.6 Destruktor und Garbage Collection
Garbage Collector
• Garbage Collector (GC): Prozess, der im Hintergrund einer Anwendung abläuft mit der Aufgabe:
Suche nach nichtreferenzierten Objekten im Heap und Freigabe des Speicherplatzes
• wird ausgeführt, wenn
− Anwendung zeitweilig keine Prozessorleistung in Anspruch nimmt oder
− Speicherrecourcen für die Anwendung einen Minimalwert unterschreiten
• Voraussetzungen für das Freigeben von Speicher für ein Objekt durch Garbage Collector
− Gültigkeitsbereich einer Objektreferenz wird verlassen (Block)
− Nullzuweisung: objekt-variable = null;
• Ablauf einer Garbage Collection
− GC ermittelt, ob Destruktoren für freizugebende Objekte definiert wurden
− wurde Destruktoren definiert, werden diese ausgeführt
− GC gibt allokierten Speicher im Heap frei
− GC Komprimiert den Heap
• explizite Ausführung des GC durch Aufruf der statischen Methode GC.Collect() möglich
Destruktordefinition
• Destruktor: spezielle Methode, die durch den Garbage Collector aufgerufen wird
Syntax
destructor-declaration
name
extern
block( )~
• in C# kann der Destruktor für ein Objekt nicht explizit in einer Anweisung zur Ausführung gebracht werden
Programmierung II - 60 -
• Destruktor sollte nur bei echter Notwendigkeit implementiert werden
Beispiel 7.6.a
namespace Beispiel{
class MyClass{
public MyClass(){
Console.WriteLine("DB-Connect");}~MyClass(){
Console.WriteLine("DB-Disconnect");}
}class Program{
static void Main(string[] args){
MyClass myObject = new MyClass();myObject = null;GC.Collect();Console.Read();Console.WriteLine("Main beendet");
}}
using-Anweisung
• Impliziter Aufruf des Garbage Collectors zum Freigeben von Objekten
Syntax
using-declaration
using )variable-declaration block(
,
• bei auftretenden Ausnahmen (Fehlern) oder nach Verlassen des Anweisungsblockes:
1. Aufruf einer Methode Dispose() 2. Ausführung des Garbage Collector für die instanziierten Objekte
• Voraussetzungen zur Verwendung von using: die Klasse, für die Objekte instanziiert wurden, muß:
− vom Interface IDisposable abgeleitet sein
Programmierung II - 61 -
− die Methode Dispose() implementieren
Beispiel 7.6.b
class MyClass : IDisposable{
public MyClass(){
Console.WriteLine("DB-Connect");}void IDisposable.Dispose(){
Console.WriteLine("DB-Disconnect");}
}class Program{
static void Main(string[] args){
using (MyClass myObject = new MyClass()){
Console.WriteLine("myObject wird verwendent");}Console.WriteLine("Main beendet");
}}
Programmierung II - 62 -
7.7 Eigenschaften
Beispiel 7.7.a
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;public ValueSet(int capacity) {. . .}public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}public bool SetCapacity(int newCapacity){
if(newCapacity<this.count) return false;int[] tmpValues = new int[newCapacity];for(int i=0; i < this.count; i++)
tmpValues[i] = this.values[i];this.values = tmpValues;this.capacity = newCapacity;return true;
}
public int GetCapacity(){
return capacity;}
}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet(50);vs.AddValues(4, 9, 7, 5, -3);vs.PrintValues();Console.WriteLine("Kapazität: {0}",vs.GetCapacity());vs.SetCapacity(20);vs.PrintValues();Console.WriteLine("Kapazität: {0}",vs.GetCapacity());
}}
}
• Eine Eigenschaft (property) ist eine spezielle Methode, die es erlaubt, mittels Zuweisungsoperator auf private Felder von Klassen zuzugreifen
Programmierung II - 63 -
Eigenschaftsdeklaration
Syntax
property-declaration
property-modifier name
property-modifier
get-accessor{type }
method-modifier
get/set-accessor
set-accessor
get-accessor set-accessor
set
get block
• sind die Modifikatoren abstract oder extern, dann besteht der Block nur aus dem Semikolon
• der Typ ist ist der Datentyp dieses Feldes, auf das zugegriffen wird
• der Eigenschaft-Name sollte der Name des Feldes, beginnend mit einem Großbuchstaben sein
• der Block enthält Anweisungen zum lesenden/schreibenden Zugriff auf das privates Feld
• der get-Acessor muß eine return-anweisung enthalten
• Eigenschaftsaufruf
− Getter-Aufruf: objekt-variable . eigenschaft-name
− Setter-Aufruf: objekt-variable . eigenschaft-name = ausdruck
• der Setter-Block besitzt eine Variable value zur Übernahme des Wertes von ausdruck
Programmierung II - 64 -
Beispiel 7.7.b
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;public ValueSet(int capacity) {. . .}public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}public int Capacity{
get{
return this.capacity;}set{
if (value < this.count) Console.WriteLine("Fehler");
int[] tmpValues = new int[value];for (int i = 0; i < this.count; i++)tmpValues[i] = this.values[i];this.values = tmpValues;this.capacity = value;
}}
}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet(50);vs.AddValues(4, 9, 7, 5, -3);vs.PrintValues();Console.WriteLine("Kapazität: {0}", vs.Capacity);vs.Capacity = 20;vs.PrintValues();Console.WriteLine("Kapazität: {0}", vs.Capacity);
}}
}
Programmierung II - 65 -
7.8 Partielle Klassen
• ist eine Klassendeklaration in mehrere Teile aufgeteilt (z.B. in mehreren Quelldateien), so muß jeder Teil einen partial-Modifizierer enthalten
• alle Teile müssen in demselben Namespace deklariert werden.
• Wenn eine partielle Klassendeklaration eine Zugriffsspezifikation enthält, muß diese mit allen anderen Teilen übereinstimmen, in denen ebenfalls eine Zugriffsspezifikation angegeben ist.
Beispiel 7.8.a
Datei Program1.csnamespace Beispiel{
partial class ValueSet{
private int count;private int capacity;private int[] values;public ValueSet(int capacity) {. . .}
}class Program{
static void Main(string[] args){. . .}
}}
Datei Program2.csnamespace Beispiel{
partial class ValueSet{
public int Capacity {. . .}public void PrintValues() {. . .}public bool AddValues(params int[] values) {. . .}
}}
Programmierung II - 66 -
7.9 Statische Klassen und Klassenmember
Beispiel 7.9.a
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;private int numberOfObjects;public ValueSet(int capacity){
this.capacity = capacity;this.values = new int[capacity];this.numberOfObjects++;
}}class Program1{
static void Main(string[] args){
ValueSet vs1 = new ValueSet(50);ValueSet vs2 = new ValueSet(50);
}}
}
7.9.1 Statische Member
• statische Member (Modifizierer static) gehören nicht einzelnen Instanzen, sondern allen Instanzen einer Klasse gemeinsam
• statische Member können sein:
− statische Felder (Klassenfelder)
− statische Eigenschaften (Klasseneigenschaften)
− statische Methoden (Klassenmethoden)
• Klassenfelder/-eigenschaften:
sind für alle Objekte einer Klasse identisch
• Instanzfelder/-eigenschaften:
sind objektspezifisch
• Klassenmethoden:
bestimmen das Verhalten einer Klasse unabhängig von einem konkreten Objekt
Programmierung II - 67 -
• Instanzmethoden:
Ausführung ist an ein konkretes Objekt gebunden
• Der Zugriff auf statische Member erfolgt außerhalb einer Klasse durch class-name . member
Beispiel 7.9.1.a
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;private static int numberOfObjects;public ValueSet(int capacity){
this.capacity = capacity;this.values = new int[capacity];numberOfObjects++;
}public int GetNumberOfObjects(){
return numberOfObjects;}
}class Program1{
static void Main(string[] args){
ValueSet vs1 = new ValueSet(50);Console.WriteLine("Anzahl der Objekte: {0}", vs1.GetNumberOfObjects());ValueSet vs2 = new ValueSet(50);Console.WriteLine("Anzahl der Objekte: {0}", vs1.GetNumberOfObjects());
}}
}
• Statische Member sind nicht über Selbstreferenz (this) zugreifbar
• Klassenfelder werden initialisiert, wenn die Klasse zum ersten Mal geladen wird
Programmierung II - 68 -
7.9.2 Statische Methoden
• statische Methoden (Klassenmethoden) dienen der Bereitstellung von Funktionen, die nicht an bestimmte Instanzen der Klasse gebunden sind
Beispiel 7.9.2.a
namespace Beispiel{
class ValueSet{
private int count;private int capacity;private int[] values;private static int numberOfObjects;public ValueSet(int capacity){
this.capacity = capacity;this.values = new int[capacity];numberOfObjects++;
}public static int GetNumberOfObjects(){
return numberOfObjects;}
}class Program1{
static void Main(string[] args){
ValueSet vs1 = new ValueSet(50);Console.WriteLine("Anzahl der Objekte: {0}", ValueSet.GetNumberOfObjects());ValueSet vs2 = new ValueSet(50);Console.WriteLine("Anzahl der Objekte: {0}", ValueSet.GetNumberOfObjects());
}}
}
Statische Konstruktoren
Syntax
static-constructor-declaration
namestatic
extern
block( )
Programmierung II - 69 -
• statische Konstruktoren werden vom System selbst vor der Erzeugung einer ersten Instanz bzw. vor dem Zugriff auf ein erstes statisches Feld aufgerufen
• dürfen weder Sichbarkeit-modifizierer noch Parameter besitzen
7.9.3 Statische Klassen
• statische Klassen dienen der Bereitstellung allgemeingültiger Funktionen
• Regeln für statische Klassen:
− dürfen nur statische Member mit public veröffentlichen
− dürfen keine Konstruktoren enthalten
− können nicht abgeleitet werden
Beispiel 7.9.3.a
namespace Beispiel{
static class MyMath{
public static int Fak(int zahl){
int erg = 1;while (zahl != 0)
erg = erg * zahl--;return erg;
}public static int Quer(int zahl){
int erg = 0;while (zahl != 0)
erg = erg + zahl--;return erg;
}}class Program{
static void Main(string[] args){
Console.WriteLine("{0} {1}", MyMath.Fak(4), MyMath.Quer(4));}
}}
Programmierung II - 70 -
UML: Statische Klassen und -member
• statische Klassen bzw. Klassenmember können durch Unterstreichung kenntlich gemacht werden
Beispiel
ValueSet
- count : int- capacity : int- values : int[]- numberOfObjects : int
+ ValueSet( capacity : int ) : void+ GetNumberOfObjects( ) : int
Programmierung II - 71 -
7.10 Generika
Beispiel 7.10.a
namespace Beispiel{
class ObjectValueSet{
private int count;private int capacity;private object[] values;public ObjectValueSet(int capacity){
this.capacity = capacity;this.values = new object[capacity];
}public void PrintValues(){
for (int i = 0; i < count; i++)Console.Write("{0} ", values[i]);
Console.WriteLine();}public bool AddValues(params object[] values){
foreach (object element in values)for (int j = 0; j < this.count; j++)
if (this.values[j] == element) return false;foreach (object element in values)
this.values[this.count++] = element;return true;
}}class Program1{
static void Main(string[] args){
ObjectValueSet vs1 = new ObjectValueSet(50);vs1.AddValues(4, 9, 7, 5, -3);vs1.PrintValues();ObjectValueSet vs2 = new ObjectValueSet(50);vs2.AddValues("Anna", "Hannes", "Willi", "Paul");vs2.PrintValues();
}}
}
Programmierung II - 72 -
Beispiel 7.10.b
static void Main(string[] args){
ObjectValueSet vs = new ObjectValueSet(50);vs.AddValues(4, "Anna", 9.172, true);vs.PrintValues();
}
Beispiel 7.10.c
namespace Beispiel{
class ObjectValueSet{
private int count;private int capacity;private object[] values;public ObjectValueSet(int capacity) {. . .}public void PrintValues() {. . .}public bool AddValues(params object[] values) {. . .}public object GetValue(int index){
return values[index];}
}class Program1{
static void Main(string[] args){
ObjectValueSet vs = new ObjectValueSet(50);vs.AddValues(4, "Anna", 9.172, true);vs.PrintValues();int v;v = (int)vs.GetValue(0); // hier o.k.v = (int)vs.GetValue(1); // hier Laufzeitfehler
}}
}
• Nachteile der Lösung mit Datentyp object:
− Typprüfung kann nicht vom Compiler geleistet werden
− Boxing/Unboxing führt zu schlechter Performance
Programmierung II - 73 -
7.10.1 Generische Klassen
• Generische Klassen sind Klassen, die Platzhalter für Datentypen besitzen, die erst zur Laufzeit konkretisiert werden (Typparameter)
• Erleichterung, da
− striktere Typprüfung zur Kompilierzeit möglich
− explizite Konvertierungen zwischen Datentypen seltener erforderlich
− keine Notwendigkeit von Boxingvorgängen und Typprüfungen zur Laufzeit
Syntax
generic-class-declaration
class-modifier class name
{
class-member
}
class-base:
type-parameter
,
< >
where type-parameter : condition
,
• Typparameter können innerhalb einer Generischen Klasse an jeder Stelle stehen, an denen ein Datentyp stehen kann
• bei der Instanziierung einer generischen Klasse wird jedem Typparameter in der Reihenfolge seines Auftretens ein konkreter Datentypen zugeordnet und damit jeder in der Klasse auftretende Typparameter durch den konkreten Datentyp ersetzt
Programmierung II - 74 -
Syntax
generic-class-instance
name
= ;
class-type
new constructor
data-type
,
< >
parameter( )
constructor
class-type data-type
,
< >
Beispiel 7.10.1.a
namespace Beispiel{
class ValueSet<T>{
private int count;private int capacity;private T[] values;public ValueSet(int capacity){
this.capacity = capacity;this.values = new T[capacity];
}public void PrintValues() { . . . }public bool AddValues(params T[] values){
foreach (T element in values)for (int j = 0; j < this.count; j++)
if (this.values[j] == element) return false;foreach (T element in values)
this.values[this.count++] = element;return true;
}public void PrintValues() { . . . }
}class Program1{
Programmierung II - 75 -
static void Main(string[] args){
ValueSet<int> vs1 = new ValueSet<int>(50);vs1.AddValues(3, 6, -5, 15);vs1.PrintValues();ValueSet<string> vs2 = new ValueSet<string>(50);vs2.AddValues("Anna", "Hannes", "Willi", "Paul");vs2.PrintValues();
}}
}
• Vergleichsoperatoren können nicht auf generische Typparameter angewendet werden
• die where-Klausel beschränkt die möglichen Ersetzungen eines Typparameters auf diejenigen Typen, die die condition erfüllen
• mögliche condition:
structclass
interface-name
Beispiel 7.10.1.b
namespace Beispiel{
class ValueSet<T> where T : IComparable{
private int count;private int capacity;private T[] values;public ValueSet(int capacity){
this.capacity = capacity;this.values = new T[capacity];
}public bool AddValues(params T[] values){
foreach (T element in values)for (int j = 0; j < this.count; j++)
if (element.CompareTo(this.values[j]) == 0) return false; foreach (T element in values)
this.values[this.count++] = element;return true;
}public void PrintValues() { . . . }
}class Program1{
static void Main(string[] args){
ValueSet<int> vs1 = new ValueSet<int>(8);vs1.AddValues(3, 6, -5, 15);
Programmierung II - 76 -
vs1.PrintValues();ValueSet<string> vs2 = new ValueSet<string>(8);vs2.AddValues("Anna", "Hannes", "Willi", "Paul");vs2.PrintValues();
}}
}
7.10.2 Generische Methoden
• Generische Methoden sind Methoden, die Platzhalter (Typparameter) für Datentypen besitzen, die erst zur Laufzeit konkretisiert werden
Syntax
generic-method-declaration
method-modifier
void
name
parameter(
type
) block
type-parameter
,
< >
• Typparameter können sowohl Bestandteil der Parameterliste oder des Methodenrumpfs als auch der Rückgabetyp sein
• generische Methoden können Member generischer oder nicht-generischer Klassen sein
• es ist nicht erlaubt, in einer Methode den gleichen Typparameter zu definieren, wie in der Klasse
Beispiel 7.10.2.a
namespace Beispiel{
static class MyClass{
public static T Select<T>(T[] w){
Random rnd = new Random();T ret = w[rnd.Next(w.Length)];return ret;
}
Programmierung II - 77 -
}class Program1{
static void Main(string[] args){
int[] f1 = new int[]{3,5,8,2};Console.WriteLine("Erg: {0}",MyClass.Select<int>(f1));string[] f2 = new string[]{"Anna","Hannes","Willi","Paul"};Console.WriteLine("Erg: {0}", MyClass.Select<string>(f2));
}}
• default ( typeparameter ) erzeugt
− 0 für übergebeneWertetypen
− NULL für übergebene Referenztypen
Beispiel 7.10.2.b
namespace Beispiel{
static class MyClass{
public static T Select<T>(T[] w){
if (w.Length == 0) return default(T);Random rnd = new Random();T ret = w[rnd.Next(w.Length)];return ret;
}}class Program1{
static void Main(string[] args){
int[] f1 = new int[] { 3, 5, 8, 2 };Console.WriteLine("Erg: {0}", MyClass.Select<int>(f1)); string[] f2 = new string[] {};Console.WriteLine("Erg: {0}", MyClass.Select<string>(f2));
}}
}
Programmierung II - 78 -
7.11 Überladen von Operatoren
• Neben dem Überladen von Methoden ist in C# auch das Überladen von Operatoren durch den Programmierer möglich
• Überladene Operatoren sind öffentliche statische Methoden und müssen als Parameter das Objekt, auf das sie angewendet werden, besitzen
Syntax
operator-declaration
operator-modifier
unary operator
operator-modifier
public
type(
static
operator
extern
type
)name
binary operator type( )name , type name
unary operator
one of: + - ! ~ ++ true false
binary operator
one of: + - * / % & | ^ << >> == != > < >= <=
block
• Es darf nur die Funktionalität eines Operators geändert werden, nicht die Zahl der Operanden (unär/binär), die Priorität eines Operators bleibt unverändert
• es können keine neuen Operatoren definiert werden (z.B. ** für Exponentialoperator)
• es muß mindestens ein Argument (Operand) eines überladenen Operators ein Objekt der Klasse oder Struktur sein, in der der Operator definiert wurde
Programmierung II - 79 -
• überladene Operatoren müssen einen Typ zurückgeben
• Überladene Operatoren können ihre Parameter nicht verändern
Unäre Operatoren
• die Operatoren ++ und -- müssen den Typ des Parameters zurückgeben
• die unären Operatoren +, -, ! und ~ können einen beliebigen Typ zurückgeben
Beispiel 7.11.a
namespace Beispiel{
class ValueSet{
private int capacity;private int count;private int[] values;public ValueSet(int capacity) { . . . }public void PrintValues() { . . . }public bool AddValues(params int[] values) { . . . }public static ValueSet operator ++(ValueSet v){
for (int i = 0; i < v.count; i++)v.values[i]++;
return v;}
}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet(10);vs.AddValues(2, 3, 4);vs.PrintValues(); // Ausgabe: 2 3 4vs++;vs.PrintValues(); // Ausgabe; 3 4 5
}}
}
Beispiel 7.11.b
class Program{
static void Main(string[] args){
ValueSet vs1 = new ValueSet(10);vs1.AddValues(2, 3, 4);vs1.PrintValues(); // Ausgabe: 2 3 4ValueSet vs2=vs1;vs2.PrintValues(); // Ausgabe: 2 3 4
Programmierung II - 80 -
vs2=++vs1;vs1.PrintValues(); // Ausgabe: 3 4 5vs2.PrintValues(); // Ausgabe: 3 4 5
vs2=vs1++;vs1.PrintValues(); // Ausgabe: 4 5 6vs2.PrintValues(); // Ausgabe: 4 5 6
}}
• Die Parameter (Argumente) eines überladenen Operators dürfen nicht verändert werden
Beispiel 7.11.c
namespace Beispiel{
class ValueSet{
private int capacity;private int count;private int[] values;public ValueSet(int capacity) { . . . }public void PrintValues() { . . . }private bool AddValue(int value) { . . . }public bool AddValues(params int[] values) { . . . }public static ValueSet operator ++(ValueSet v){
ValueSet tmp = new ValueSet(v.count);for (int i = 0; i < v.count; i++)
tmp.AddValue(v.values[i] + 1);return tmp;
}}class Program{
static void Main(string[] args){
ValueSet vs1 = new ValueSet(10);vs1.AddValues(2, 3, 4);vs1.PrintValues(); // Ausgabe: 2 3 4ValueSet vs2 = vs1;vs2.PrintValues(); // Ausgabe: 2 3 4
vs2 = ++vs1;vs1.PrintValues(); // Ausgabe: 3 4 5vs2.PrintValues(); // Ausgabe: 3 4 5
vs2 = vs1++;vs1.PrintValues(); // Ausgabe: 4 5 6vs2.PrintValues(); // Ausgabe: 3 4 5
}}
}
Programmierung II - 81 -
Binäre Operatoren
• Überladene relationale Operatoren müssen bool als Rückgabetyp liefern
Beispiel 7.11.d
namespace Beispiel{
class ValueSet{
private int capacity;private int count;private int[] values;public ValueSet(int capacity) { . . . }public void PrintValues() { . . . }public bool AddValues(params int[] values) { . . . }public static bool operator == (ValueSet v, int value){
for (int i = 0; i < v.count; i++)if(v.values[i]==value) return true;
return false;}
}class Program{
static void Main(string[] args){
ValueSet vs = new ValueSet(10);vs.AddValues(2, 3, 4);if(vs==4)
Console.WriteLine("4 enthalten");else
Console.WriteLine("4 nicht enthalten");}
}}
• Beim Überladen relationaler Operatoren müssen jeweils die korrespondierenden Operatoren überladen werden:
== und != , <= und >= , < und >
Beispiel 7.11.d (erweitert)
public static bool operator != (ValueSet v, int value){
for (int i = 0; i < v.count; i++)if (v.values[i] == value) return false;
return true;}
• wird '==' überladen, sollte auch Equals() und GetHashCode() überladen werden
Programmierung II - 82 -
Beispiel 7.11.d (erweitert)
public override bool Equals(object obj){
if (this == (int)obj)return true;
elsereturn false;
}
Beispiel 7.11.e
namespace Beispiel{
class ValueSet{
private int capacity;private int count;private int[] values;public ValueSet(int capacity) { . . . }public void PrintValues() { . . . }
public bool AddValues(params int[] values) { . . . }private bool AddValue(int value) { . . . } private bool NextGreaterValue(ref int value) { . . . }private bool GetMin(out int min) { . . . }private void Union (ValueSet v) { . . . }public static ValueSet operator + (ValueSet v1, ValueSet v2){
ValueSet tmp = new ValueSet(20);tmp.Union(v1);tmp.Union(v2);return tmp;
}}class Program{
static void Main(string[] args){
ValueSet vs1 = new ValueSet(20);vs1.AddValues(9, 7, 5, -3);vs1.PrintValues();ValueSet vs2 = new ValueSet(20);vs2.AddValues(-5, 5, 3);vs2.PrintValues();ValueSet vs3 = new ValueSet(20);vs3=vs1+vs2;vs3.PrintValues(); // Ausgabe: 9 7 5 -3 -5 3 vs1+=vs2;vs1.PrintValues(); // Ausgabe: 9 7 5 -3 -5 3
}}
}
• wird ein binärer Operator operator überladen, so wird die zugehörige Zuweisung operator = implizit ebenfalls überladen
Programmierung II - 83 -
8. Spezielle Klassen in C#
8.1 Der Datentyp object und die Klasse Object
• alle Objekttypen in C# (einfache Datentypen, Enumerations, Stukturen, Klassen) sind von der Klasse Object abgeleitet
Methoden der Klasse Object
bool ReferenceEquals(object, object)
Vergleich der Referenzen zweier Objekte, liefert true wenn Referenzen gleich sind, sonst false
bool Equals(object)
wie ReferenceEquals, aber überschreibbar
int GetHashCode()
liefert Hash-Code eines Objektes
Type GetType()
liefert den Datentyp eines Objektes
object MemberwiseClone()
kopiert ein Objekt und liefert Referenz darauf zurück
string ToString()
liefert den vollen Namen einer Klasse incl. namespace
void Finalize()
gibt Ressourcen (Speicher) einer Klasse frei
Beispiel
int i = 1;Console.WriteLine(i.GetType()); // Ausgabe: 'System.Int32'
Programmierung II - 84 -
Gleichheit von Objekten/Referenzen
• Zwei Objekte obj1 und obj2 sind typgleich, wenn sie vom selben Typ sind aber unterschiedliche Instanzwerte (Feldwerte) besitzen
Beispiel
class MyClass{
public i=1;}MyClass o1 = new MyClass();MyClass o2 = new MyClass();o2.i = 2;
• Zwei Objekte obj1 und obj2 sind gleich, wenn sie vom selben Typ sind, gleiche Instanzwerte besitzen, aber ihre Referenzen auf unterschiedliche Speicheradressen verweisen
Beispiel
class MyClass{
public i=1;}MyClass o1 = new MyClass();MyClass o2 = new MyClass();
• Zwei Objekte obj1 und obj2 sind identisch, wenn sie vom selben Typ sind und ihre Referenzen auf dieselbe Speicheradresse verweisen
Beispiel
class MyClass{
public i=1;}MyClass o1 = new MyClass();
MyClass o2 = o1;
• Für den Vergleich auf Identität (Referenzvergleich) existieren 3 Möglichkeiten:
− Operator '=='
− Object.ReferenceEquals(Object, Object)
− Equals(Object)
Sie liefern true, wenn die Referenzen beider Objekte gleich sind
Programmierung II - 85 -
Beispiel
MyClass o1 = new MyClass();MyClass o2 = new MyClass();if (Object.ReferenceEquals(o1, o2))
Console.WriteLine("gleich"); else
Console.WriteLine("ungleich"); //Ergebnis: ungleichif (o1 == o2)
Console.WriteLine("gleich"); else
Console.WriteLine("ungleich"); //Ergebnis: ungleich
Beispiel
MyClass o1 = new MyClass();MyClass o2 = o1;if (Object.ReferenceEquals(o1, o2))
Console.WriteLine("gleich"); //Ergebnis: gleichelse
Console.WriteLine("ungleich"); if (o1 == o2)
Console.WriteLine("gleich"); //Ergebnis: gleichelse
Console.WriteLine("ungleich");
Beispiel
int i=1;int j=i;if(Object.ReferenceEquals(i,j))
Console.WriteLine("gleich");else
Console.WriteLine("ungleich"); //Ergebnis: ungleichif(i==j)
Console.WriteLine("gleich"); //Ergebnis: gleichelse
Console.WriteLine("ungleich");
• um zwei komplexe Objekte auf Wertegleichheit zu vergleichen, kann die Methode Equals überladen werden (Unterschied zu ReferenceEquals
• Zur Überprüfung von Typgleichheit: Object.GetType()
Programmierung II - 86 -
8.2 Zeichenketten
8.2.1 Der Datentyp string und die Klasse String
• C#-Syntax definiert Grunddatentyp string
• Datentyp string wird in .NET auf die Klasse String abgebildet
• Zeichenketten sind Referenztypen
• als string/String definierte Zeichenketten sind immutable, d.h. sie können durch ihre Methoden nicht überschrieben werden
Eigenschaften der Klasse String
Chars-Eigenschaft
Zugriff auf einzelne String-Zeichen durch nullbasierten Index
Length-Eigenschaft
Lesender Zugriff auf die Anzahl der char-Zeichen eines String
Beispiel
string s = "Hallo";Console.WriteLine("{0} {1}", s1.Length, s1[1]); // Ausgabe: '5 a'
• eine Änderung des Strings über die Chars-Eigenschaft ist nicht möglich
Beispiel
string s = "Hallo";s[1] = 'e'; // Fehler
⇒ das Objekt selbst würde verändert werden: Verletzung der immutable-Eigenschaft
Programmierung II - 87 -
Überladene Operatoren der Klasse String
Zuweisungsoperator =
− bewirkt das Erzeugen eines String-Objektes (char-Folge + Längenwert) im Heap und Referenz darauf (String-Variable)
− werden mehrere gleiche Strings (identische char-Folgen) erzeugt, so wird nur einmal Speicherplatz im Heap allokiert. Alle String-Referenzen zeigen dann auf denselben Heap-Bereich
Beispiel
string s1 = "Hallo";string s2 = s1;Console.WriteLine("{0} {1}", s1, s2); // Ausgabe: 'Hallo Hallo'string s2 = "World";Console.WriteLine("{0} {1}", s1, s2); // Ausgabe: 'Hallo World'
Beispiel
string s1 = "World";string s2 = "World";Console.WriteLine("{0} {1}", s1.GetHashCode(), s2.GetHashCode());
• Wird der Wert einer String-Variablen geändert, so wird ein neues String-Objekt erzeugt und das alte gelöscht
Stringverkettungsoperator +
− verkettet mehrere Strings miteinander oder Strings mit arithmetischen Ausdrücken
− Verkettungsausdrücke werden von links nach rechts abgearbeitet
Syntax
<stringverkettung> ::=<string> + <string> | <string> + <arithm-ausdruck> | <arithm-ausdruck> + <string>
Beispiel
int i = 3;double d = 3.14;string s = "Hallo";string r;r = s + s +", World";Console.WriteLine("{0}", r); // Ausgabe: 'HalloHallo, World'r = i + " ist nicht " + d;Console.WriteLine("{0}", r); // Ausgabe; '3 ist nicht 3,14'r = i + d +" ist anders ";Console.WriteLine("{0}", r); // Ausgabe: '6,14 ist anders'
Programmierung II - 88 -
Stringverkettungsoperator +=
verkettet einen linken String mit einem rechten oder mit rechtem arithmetischen Ausdruck
Syntax
<stringverkettung> ::=<string> += <string> | <string> += <arithm-ausdruck>
Beispiel
int i = 3;double d = 3.14;string s = "Ergebnis ";s += d;Console.WriteLine("{0}", s); // Ausgabe: 'Ergebnis 3,14's += " ungleich " + i;Console.WriteLine("{0}", s); // Ausgabe: 'Ergebnis 3,14 ungleich 3'
Methoden der Klasse String
String(char[] f)
Konstruktor. Initialisiert eine neue Instanz der String-Klasse mit dem durch ein Array von Zeichen angegebenen Wert.
String(char c, int i)
Konstruktor. Initialisiert eine neue Instanz der String-Klasse mit einer Anzahl i char-Werten
static int Compare(string s1, string s2)
Vergleicht die Strings und gibt int-Zahl zurück, die die lexikalische Beziehung der beiden Strings angibt Wert < 0 wenn s1 kleiner s2 Wert = 0 wenn s1 gleich s2 Wert > 0 wenn s1 größer s2
int IndexOf(string s)
Gibt den Index des ersten Vorkommens des angegebenen String in dieser Instanz an
string Insert(int i, string s)
Fügt einen String s an einer Indexposition i in die Instanz ein.
string Remove(int i1, int i2)
Löscht eine Anzahl i2 von Zeichen ab der Position i1 aus der Instanz.
string PadLeft (int i, char c) / string PadRight (int i, char c)
Richtet die Zeichen dieser Instanz links/rechtsbündig aus und füllt die rechte/linke Seite mit Zeichen c aus, um eine angegebene Gesamtlänge i zu erreichen.
Programmierung II - 89 -
static string Concat(string, string, ...)
Verkettet eine oder mehrere Strings und liefert den Ergebnisstring
string To Lower() / string ToUpper()
Gibt eine in Klein-/Großbuchstaben konvertierte Kopie der Instanz zurück.
string Trim(char[] f)
Entfernt sämtliche Zeichen aus einer in einem Array f angegebenen Menge von Zeichen am Anfang und am Ende der Instanz.
string Substring(int i1, int i2)
Gibt eine Teilzeichenfolge dieser Instanz, beginnend mit Position i1 der Länge i2 zurück.
string[] Split(char[] f)
Gibt ein string-Array zurück, das die Teilzeichenfolgen der Instanz enthält, die durch Elemente eines angegebenen char-Arrays getrennt sind
Beispiel
char[] f = {'H','a','l','l','o'};string s1 = new String(f);Console.WriteLine("{0}", s1); // Ausgabe: 'Hallo'
string s2 = "Welt"; s2 = String.Concat(String.Concat(s1, ", ", s1), " ", s2);Console.WriteLine("{0}", s2); // Ausgabe: 'Hallo, Hallo Welt'
s2 = s2.Remove(s2.IndexOf(" "+s1), s1.Length+1);Console.WriteLine("{0}", s2); // Ausgabe: 'Hallo, Welt'
s2 = s2.Insert(s2.IndexOf(" "), " schöne, neue");Console.WriteLine("{0}", s2); // Ausgabe: 'Hallo, schöne, neue Welt'
char[] fc = {',', ' '};string[] fs = s2. Split(fc);Console.WriteLine("{0} \n{1}", fs[0], fs[2]); // Ausgabe: 'Hallo
neue '
Programmierung II - 90 -
8.2.2 Die Klasse StringBuilder
• im Namensraum System.Text definiert
• StringBuilder-Objekte sind Zeichenketten, die durch die Klassenmethoden selbst geändert werden können
• Kapazität einer StringBuilder-Instanz:
− aktueller Speicherraum zur Aufnahme eines Strings
− beginnt, wenn nicht explizit anders im Konstruktor festgelegt, mit 16 Zeichen
− bei späterer Erweiterung des Strings erweitert sich die Kapazität in den Schritten
32, 64, 128, 256, ... MaxCapacity
− MaxCapacity ist implementierungsabhängig, theoretisch: 231-1 Zeichen möglich
−
Eigenschaften der Klasse StringBuilder
Capacity-Eigenschaft
Zugriff auf die Kapazität einer Klasseninstanz
MaxCapacity-Eigenschaft
Lesender Zugriff auf die maximale Kapazität einer Klasseninstanz
Chars-Eigenschaft
Zugriff auf einzelne Zeichen durch nullbasierten Index
Length-Eigenschaft
Zugriff auf die Anzahl der char-Zeichen einer StringBuilder-Instanz
Methoden der Klasse StringBuilder
StringBuilder(int i)
Konstruktor. Erstellt eine neue Instanz der StringBuilder-Klasse mit der Anfangskapazität i1
StringBuilder(int i1, int i2)
Konstruktor. Erstellt eine neue Instanz der StringBuilder-Klasse mit der Anfangskapazität i1 und der maximalen Kapazität i2
StringBuilder(string s)
Konstruktor. Erstellt eine neue Instanz der StringBuilder-Klasse mit einer Anfangskapazität, die sich aus s.Length ergibt und initialisiert sie mit s
StringBuilder Append(string s)
Fügt eine Kopie von s an das Ende dieser Instanz an. Liefert Referenz auf die Instanz zurück
Programmierung II - 91 -
StringBuilder Insert(int i, string s)
Fügt eine Kopie von s an der Zeichenposition i in die Instanz ein. Liefert Referenz auf die Instanz zurück
StringBuilder Replace(string s1, string s2)
Ersetzt in der Instanz alle Vorkommen einer Zeichenfolge s1 durch die Zeichenfolge s2.
string ToString()
Konvertiert den Wert der Instanz in eine String.
Beispiel
string s = "Hallo Welt";StringBuilder sb = new StringBuilder();sb.Append(s);Console.WriteLine("{0} {1}", sb, sb.Capacity); // Ausgabe: 'Hallo Welt 16'
⇒ Startkapazität 16
sb.Insert(6, "schöne neue");Console.WriteLine("{0} {1}", sb, sb.Capacity); // Ausgabe: 'Hallo schöne neue Welt 32'
sb[sb.Length++] = '!';Console.WriteLine("{0}", sb); // Ausgabe: 'Hallo schöne neue Welt!'
s = sb.ToString();s = s.ToUpper();sb.Replace(sb.ToString(), s);Console.WriteLine("{0}", sb); // Ausgabe: 'Hallo schöne neue Welt 32'
Programmierung II - 92 -
8.3 Arrays
• Array: Feld von Objekten gleichen Typs (homogener Container)
• Arrayelemente können einen beliebigen Typ aufweisen, z. B. auch einen Arraytyp.
• Ein Array kann eindimensional oder mehrdimensional sein
• Ein Array kann unverzweigt oder verzweigt sein
Syntax
arrays-declaration
array-declaration
jagged-array-declaration
Programmierung II - 93 -
8.3.1 Unverzweigte Arrays
• entsprechen strukturell den Feldern in C/C++
Indexnotation für unverzweigte Arrays
Syntax
array-declaration
typenew= [ constant
,
]
typenew= [
,
] array-initializer
;
array-initializer
type [
,
] name
array-initializer
{
,
}expression
,
array-initializer
=
Programmierung II - 94 -
• Varianten:
− Deklaration einer Referenzvariable für ein ein- bis mehrdimensionales Array definiert
− Erzeugen eines Array-Objektes im Heap und Zuweisung der Referenz an die Referenzvariable
− Erzeugen eines Array-Objektes im Heap und gleichzeitige Initialisierung mit Werten möglich
− Erzeugen eines Array-Objektes im Heap und Zuweisung der Referenz an die Referenzvariable bereits bei der Deklaration
• Zugriff auf Array-Elemente durch Index-Notation
<array-variable>[ <index> [ , <index> ]... ]
• Numerische Arrayelemente sind standardmäßig auf 0 festgelegt, Verweiselemente auf NULL, Boolsche auf false
Beispiel
int[ , ] a;a = new int[2,3]; // 2 Zeilen, 3 Spalten Matrix
a[0,2] = 6;// oderint[ , ] a = new int[ , ] {{0,0,6},{0,0,0}};// oderint[ , ] a = {{0,0,6},{0,0,0}};// Fehlerint[ , ] a; a = {{0,0,6},{0,0,0}}
• Die Größe des Arrays kann zur Laufzeit festgelegt werden
Programmierung II - 95 -
Operatoren is und as
Syntax
<is-expression> ::=
<expression> is <type>
• liefert true, wenn der Ausdruck von dem angegebenen Typ ist oder in ihn konvertiert werden kann, sonst false
Syntax
<as-expression> ::=
<expression> as <type>
• liefert den typgewandelten Ausdruck, wenn der Ausdruck in angegebenen Typ konvertiert werden kann, sonst null
• entspricht: <expression> is <type> ? (<type>) <expression> : (<type>) null
Beispiel 8.3.1.a
static void Main(string[] args){
object[] a = new object[10];a[0]=12;a[1]="Hallo";a[2]=22.31;foreach (object o in a){
if(o is int)Console.WriteLine("{0} (Integer-Wert)", o);
if(o is string)Console.WriteLine("{0} (String-Wert)", o);
if(o is double)Console.WriteLine("{0} (Double-Wert)", o);
}}
Programmierung II - 96 -
8.3.2 Die Klasse Array
• Arrays wird in .NET auf die Klasse Array abgebildet
• Array besitzt 7 Eigenschaften und 36 z.T. überladene Methoden
Eigenschaften der Klasse Array
Length-Eigenschaft
Liest die Anzahl der Elemente in allen Dimensionen eines Array
Rank-Eigenschaft
Liest den Rang (Anzahl der Dimensionen) eines Array
Beispiel
int[,] b = new int[2,3];Console.WriteLine("{0} {1}", b.Length, b.Rank); // Ausgabe: '6 2'
Methoden der Klasse Array
int GetLength(int i)
Gibt die die Anzahl der Elemente in der Dimension i des Array zurück.
static int IndexOf(Array a, object o)
Sucht nach dem Objekt(wert) o und gibt den Index des ersten Vorkommens innerhalb des gesamten eindimensionalen Array zurück.
static void Sort(Array a)
Sortiert die Elemente in einem eindimensionalen Array
void CopyTo(Array a, int i)
Kopiert alle Elemente der eindimensionalen Array-Instanz in das eindimensionale Array a, beginnend am angegebenen Index i von a.
Beispiel
int[,] b = new int[2,3]; Console.WriteLine("{0} {1}", b.GetLength(0), b.GetLength(1),); // Ausgabe: '2 3'
string[] a;a = new string[] {"Hallo", "das", "war", "meine", "Welt"};Console.WriteLine("{0} {1}", Array.IndexOf(a, "war"), a.Length); // Ausgabe: '2 5'
Array.Sort(a);for(int i=0; i<a.Length; i++)Console.Write("{0} ", a[i]); // Ausgabe: 'das Hallo meine war Welt'
Programmierung II - 97 -
8.3.3 Verzweigte Arrays
• Verzweigtes Array (jagged array): Array, dessen Elemente Arrays sind
Indexnotation für verzweigte Arrays
Syntax
jagged-array-declaration
typenew= [ constant
,
]
;
type [
,
] name[
,
]
[
,
]
• Deklaration und Instanziierung können zusammengefaßt werden
• Die Verzweigungen können dann wie Arrays Instanziiert bzw. Initialisiert werden
• Elemente verzweigter Arrays sind Verweistypen und werden mit null initialisiert.
Beispiel
int[][] a = new int[3][];a[0] = new int[2];a[1] = new int[4];a[2] = new int[3];a[0][1] = 9;a[1][2] = 4;a[2][0] = 7;
Beispiel
int[,][] m = new int[2, 4][];m[0,0]=new int[]{1,2,3};m[0, 1] = new int[] { 1, 2, 3, 4, 5, 6 };m[1, 2] = new int[7];Console.WriteLine("{0}", m[0,1][4]); //Ausgabe: 5
Beispiel
int[][,] n = new int[4][,];n[0] = new int[,] { { 1, 2 }, { 3, 4 } };n[1] = new int[,] { { 1, 2, 3 }, { 4, 5, 6 } };
Programmierung II - 98 -
n[2] = new int[3,2];Console.WriteLine("{0}", n[0][1,1]); //Ausgabe: 4
8.4 Die Klasse Type
• Reflektion
Abfrage von Typinformationen aus einer Assembly zur Laufzeit
• Die Klasse Type stellt Methoden, Felder und Eigenschaften zur Analyse von Datentypen einer Anwendung bereit
Eigenschaften der Klasse Type
Namespace-Eigenschaft
Liest den Namespace, in der der Typ deklariert ist
BaseType-Eigenschaft
Liest den Basistyp, von der der Typ abgeleitet ist
Methoden der Klasse Type
public FieldInfo[] GetFields()
Gibt die (öffentlichen, nicht-statischen) Felder eines Typs als Objekte der Klasse FieldInfo in ein Array zurück. FieldInfo enthält Eigenschaften und Methoden zur Auswertung der Merkmale eines Feldes
public MethodInfo[] GetMethods()
Gibt die (öffentlichen, nicht-statischen) Methoden eines Typs als Objekte der Klasse MethodInfo in ein Array zurück. MethodInfo enthält Eigenschaften und Methoden zur Auswertung der Merkmale einer Methode
public ParameterInfo[] GetParameters()
Gibt die Parameter einer Methode eines Typs als Objekte der Klasse ParameterInfo in ein Array zurück. ParameterInfo enthält Eigenschaften und Methoden zur Auswertung der Merkmale eines Parameters
Programmierung II - 99 -
• 3 Möglichkeiten zur Erzeugung eines System.Type-Objektes für einen Datentyp:
Methode GetType
• bei Vorliegen einer Instanz eines zu untersuchenden Datentyps kann die von Object geerbte Methode GetType aufgerufen werden
Beispiel
MyClass m = new MyClass();Type type = m.GetType();
statische Methode GetType
• Liegt keine Instanz vor, kann der zu untersuchenden Datentyp durch die statische Methode GetType der Klasse Type aufgerufen werden
Beispiel
Type type = Type.GetType("Beispiel.MyClass");
Operator typeof
• Liegt keine Instanz vor, kann der zu untersuchenden Datentyp auch durch Anwendung des Operators typeof aufgerufen werden
Syntax
<typeof-expression> ::=
typeof ( <type> )
• liefert das System.Type-Objekt für den angegebenen Datentyp
Beispiel 8.4.a
namespace Beispiel{
class MyClass{
public int field1;public char[] field2;private int field3;public bool Method1(int param) { return true; }public int Method2(ref int param1, char param2) { return 1; }
}class Program{
static void Main(string[] args){
Programmierung II - 100 -
Type type = typeof(MyClass); Console.WriteLine("Namespace: {0}", type.Namespace); Console.WriteLine("Basistyp : {0}", type.BaseType); Console.WriteLine("Felder:");foreach(FieldInfo field in type.GetFields())
Console.WriteLine(" {0} {1}", field.FieldType, field.Name);Console.WriteLine("Methoden:");foreach(MethodInfo method in type.GetMethods()){
Console.WriteLine(" {0}", method.Name);foreach(ParameterInfo param in method.GetParameters())
Console.WriteLine(" {0}", param.ParameterType);}
}}
}
Programmierung II - 101 -
9 Klassen-Hierarchien
• Klassenhierarchien sind Beziehungen von Klassen im Sinne von Spezialisierung/Generalisierung
Beispiel
Ober- und Unterbegriffe in der Sprache
Fahrzeug
Landfahrzeug Wasserfahrzeug
PKW LKW
Generalisierung
Spezialisierung
• Eine von einer Basisklasse abgeleitete Klasse erbt alle Member (Felder und Methoden) der Basisklasse. Zusätzlich können weitere Member definiert werden.
Beispiel
Eigenschaften von Fahrzeug (Basisbegriff)
− Halter
− Preis
Eigenschaften von Wasserfahrzeug (abgeleiteter Begriff)
− Halter
− Preis
− Verdrängung
Programmierung II - 102 -
UML: Hierarchien
• Hierarchien werden im Klassendiagramm durch einen nicht ausgefüllten Pfeil von der Unterklasse (abgeleitete Klasse) zur Oberklasse (Basisklasse) dargestellt
Beispiel
PKW
personenzahlkofferraumvolumen
Fahrzeug
halterpreis
Landfahrzeug
kilometerstand
Anfahren()
Wasserfahrzeug
verdrängung
Ankern()
LKW
ladefläche
Beladen()
Programmierung II - 103 -
9.1 Vererbung
9.1.1 Ableitungsdeklaration
Syntax (Vervollständigung: Vererbung)
class-declaration
class-modifier class name
{
class-member
}
class-base:
class-base
class-type
• in C# ist nur Einfachvererbung möglich
• Modifizierer static in der abgeleiteten Klassen nicht zulässig
• Modifizierer sealed in der Basisklasse nicht zulässig
Beispiel
class Fahrzeug{
int halter;float preis;
}class Landfahrzeug : Fahrzeug{
int kilometerstand;Anfahren(){ };
}class PKW : Landfahrzeug{
int personenzahl;int kofferraumvolumen;
}
Programmierung II - 104 -
Beispiel 9.1.1.a
namespace Beispiel{
class ValueSet{
private int count;private int[] values = new int[100];public bool AddValues(params int[] values) { . . . }
}class NamedValueSet: ValueSet{
private string name;public bool AddNamedValues(string name, params int[] values){
this.name=name;foreach (int element in values)
for (int j = 0; j < this.count; j++)if (this.values[j] == element) return false;
foreach (int element in values) // Elemente einfügenthis.values[this.count++] = element;
return true;}
}class Program1{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValues(4, 9, 7, 5, -3);NamedValueSet nvs = new NamedValueSet();nvs.AddNamedValues("Menge 1", 3, 7, -8);
}}
}
• ein mit protected in einer Basisklasse deklariertes Member erlaubt den Zugriff aus allen davon abgeleiteten Klassen
Beispiel (Änderungen im vorhergehenden Beispiel)
protected int count;protected int[] values = new int[100];
Programmierung II - 105 -
9.1.2 Konstruktoren und Destruktoren
• Konstruktoren einer Basisklasse werden nicht an die abgeleitete Klasse vererbt
Beispiel 9.1.2.a
namespace Beispiel{
class ValueSet{
protected int count;protected int capacity;protected int[] values;public ValueSet(int capacity) { . . . }public bool AddValues(params int[] values) { . . . }
}class NamedValueSet : ValueSet{
private string name;public NamedValueSet(int capacity, string name){
this.capacity = capacity;this.values = new int[capacity];this.name = name;
}}class Program1{
static void Main(string[] args){
ValueSet vs = new ValueSet(50);vs.AddValues(4, 9, 7, 5, -3);NamedValueSet nvs = new NamedValueSet(50, "Wertemenge1");nvs.AddValues(3, 7, -8);
}}
}
Implizite Konstruktorverkettung
• implizite Top-down-Konstruktorverkettung
Wird bei der Instanziierung eines Klassenobjektes einer abgeleiteten Klasse keine Konstruktoren der Basisklassen explizit aufgerufen, so werden zunächst die parameterlosen Standard-Konstruktoren aller in einer Hierarchie vorausgehenden Basisklassen aufgerufen
Beispiel
Beispiel
class A{
Programmierung II - 106 -
private int a; public A() { Console.WriteLine("A() aufgerufen"); }}class B : A{ private int b; public B() { Console.WriteLine("B() aufgerufen"); }}class C : B{ private int c; public C(int c) { this.c = c; Console.WriteLine("C(int c) aufgerufen"); }}class Program{ static void Main(string[] args) { C c_obj = new C(3); }}
Beispiel (Ergänzung zum vorhergehenden Beispiel)
class ValueSet{
protected int count;protected int capacity;protected int[] values;public ValueSet() { }public ValueSet(int capacity) { . . . }public bool AddValues(params int[] values) { . . . }
}
explizite Konstruktorverkettung
• explizite Konstruktorverkettung
Aufruf eines Basisklassenkonstruktoren aus einer abgeleiteten Klasse heraus mit base
Programmierung II - 107 -
Syntax (Wiederholung aus Kapitel 7.5)
constructor-declaration
constructor-modifier name
argument
( )
block
base:
this
parameter( )
• base bezeichnet einen Konstruktor der Basisklasse
• Argumente: Liste aktueller Parameter und bestimmt durch ihre Anzahl und den Datentyp den konkreten Konstruktor der Basisklasse
• this bezeichnet einen Konstruktor der eigenen Klasse
Beispiel 9.1.2.b
namespace Beispiel{
class ValueSet{
protected int count;protected int capacity;protected int[] values;public ValueSet(int capacity) { . . . }public bool AddValues(params int[] values) { . . . }
}class NamedValueSet : ValueSet{
private string name;public NamedValueSet(int capacity, string name): base(capacity){
this.name = name;}
}class Program1{
static void Main(string[] args){
ValueSet vs = new ValueSet(50);
Programmierung II - 108 -
vs.AddValues(4, 9, 7, 5, -3);NamedValueSet nvs = new NamedValueSet(50, "Wertemenge1");nvs.AddValues(3, 7, -8);
}}
}
Destruktoren
• Destruktoren einer Basisklasse werden nicht an die abgeleitete Klasse vererbt
• implizite Bottom-up-Destruktorverkettung
Sobald der Garbage Collector auf ein nicht mehr referenziertes Objekt einer abgeleiteten Klasse stößt, wird zunächst vollständig der Destruktor dieser Klasse und danach erst der der Basisklasse ausgeführt
9.1.3 Basisklassen-Member
Überladen von Basisklassen-Methoden
• überladene Basisklassen-Methoden
Methode einer Basisklasse wird in einer abgeleiteten Klasse mit gleichem Namen und anderer Signatur definiert
Verdecken von Basisklassen-Membern
• Verdeckte Member
Member (Felder, Eigenschaften und Methoden), die mit identischem Namen und Parameterliste sowohl in einer Basisklasse als auch in einer abgeleiteten Klasse definiert sind
• Memberf einer Basisklasse werden durch Member mit dem Zugriffsmodifizierer new in der abgeleiteten Klasse verdeckt
Beispiel 9.1.3.a
namespace Beispiel{
class ValueSet{
protected int count;protected int[] values = new int[100];public bool AddValues(params int[] values) { . . . }public void PrintValues(){
for (int i = 0; i < count; i++)Console.Write("{0} ", values[i]);
Console.WriteLine();
Programmierung II - 109 -
}}class NamedValueSet : ValueSet{
private string name;public bool AddNamedValues(string name, params int[] values) { . . . }public new void PrintValues(){
Console.Write("{0}: ", name);for (int i = 0; i < count; i++)
Console.Write("{0} ", values[i]);Console.WriteLine();
}}class Program1{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValues(4, 9, 7, 5, -3);vs.PrintValues(); // Ausgabe: 4 9 7 5 -3NamedValueSet nvs = new NamedValueSet();nvs.AddNamedValues("Menge 1", 3, 7, -8);nvs.PrintValues(); // Ausgabe: Menge 1: 3 7 -8
}}
}
Memberzugriff mit base
• der Zugriff auf ein (verdecktes) Member (Feld, Eigenschaft, Methode)einer Basisklasse aus einer abgeleiteten Klasse heraus erfolgt durch
base . field-namebase . property-namebase . method-name( parameters )
• base ist eine implizite Referenz und damit an eine konkrete Klasseninstanz gebunden
• base ist sinnvoll bei Zugriff auf verdeckte Methoden
Beispiel 9.1.3.b
namespace Beispiel{
class ValueSet{
protected int count;protected int[] values = new int[100];public bool AddValues(params int[] values) { . . . }public void PrintValues() { . . . }
}class NamedValueSet : ValueSet
Programmierung II - 110 -
{private string name;public bool AddNamedValues(string name, params int[] values) { . . . }public new void PrintValues(){
Console.Write("{0}: ", name);base.PrintValues();
}}class Program1{
static void Main(string[] args){
ValueSet vs = new ValueSet();vs.AddValues(4, 9, 7, 5, -3);vs.PrintValues(); // Ausgabe: 4 9 7 5 -3NamedValueSet nvs = new NamedValueSet();nvs.AddNamedValues("Menge 1", 3, 7, -8);nvs.PrintValues(); // Ausgabe: Menge 1: 3 7 -8
}}
}
Programmierung II - 111 -
9.2 Polymorphie
Frühes Binden (statisches Binden)
• Frühes Binden ist die Zuweisung von Routinen (Methoden, Operatoren) zu ihren Objekten (Daten) bereits zum Zeitpunkt der Übersetzung
• In Klassenhierarchien erfolgt die Zuweisung von überladenen oder verdeckten Methoden zu Klassenobjekten durch den Compiler bereits zur Übersetzungszeit.
Beispiel 9.2.a
namespace Beispiel{
class ClassA{
public void Method(){
Console.WriteLine("A-Methode aufgerufen");}
}class ClassB : ClassA{
public new void Method(){
Console.WriteLine("B-Methode aufgerufen");}
}class ClassC : ClassA{
public new void Method(){
Console.WriteLine("C-Methode aufgerufen");}
}class Program{
static void Main(string[] args){
ClassA objA = new ClassA();ClassB objB = new ClassB();ClassC objC = new ClassC();objA.Method(); //Ausgabe: A-Methode aufgerufenobjB.Method(); //Ausgabe: B-Methode aufgerufenobjC.Method(); //Ausgabe: C-Methode aufgerufen
}}
}
Programmierung II - 112 -
Beispiel 9.2.b
namespace Beispiel{
class ClassA{
public void Method(){
Console.WriteLine("A-Methode aufgerufen");}
}class ClassB : ClassA{
public new void Method(){
Console.WriteLine("B-Methode aufgerufen");}
}class ClassC : ClassA{
public new void Method(){
Console.WriteLine("C-Methode aufgerufen");}
}class Program{
static void Main(string[] args){
ClassA obj=null;string ereignis=Console.ReadLine();if (ereignis == "A")
obj = new ClassA();if (ereignis == "B")
obj = new ClassB();if (ereignis == "C")
obj = new ClassC();obj.Method();
}}
}
• jedes Objekt einer abgeleiteten Klasse ist zugleich auch Objekt der Basisklasse
Spätes Binden (dynamisches Binden)
• Binden einer Methode an ein Klassenobjekt erst zur Laufzeit (Polymorphie) durch:
− Deklaration der Methode in der Basisklasse als virtuell (Modifizierer virtual) und
− Überschreiben dieser Methode in der abgeleiteten Klasse (Modifizierer override)
Programmierung II - 113 -
• virtual vor einer verdeckten Methode einer Basisklasse bietet der verdeckenden Methode der abgeleiteten Klasse polymorphes Verhalten an
• override vor einer Methode, die eine virtuelle Methode verdeckt, bewirkt polymorphes Verhalten
Beispiel (Modifikation des vorhergehenden Beispiels)
namespace Beispiel{
class ClassA{
public virtual void Method() { . . . }}class ClassB : ClassA{
public override void Method() { . . . }}class ClassC : ClassA{
public override void Method() { . . . }
}class Program{
static void Main(string[] args){
ClassA obj=null;string ereignis=Console.ReadLine();if (ereignis == "A")
obj = new ClassA();if (ereignis == "B")
obj = new ClassB();if (ereignis == "C")
obj = new ClassC();obj.Method();
}}
}
• die Kombination von virtual mit static oder abstract ist nicht erlaubt
• die Kombination von override mit new nicht erlaubt
• Polymorphie hat besondere Bedeutung bei sog. inhomogenen Kollektionen
Beispiel 9.2.c
namespace Beispiel{
class ValueSet{
protected int count;protected int capacity;public virtual void PrintValues() { }
Programmierung II - 114 -
public virtual bool AddValues(params int[] values) {return false;}public virtual bool AddValues(params string[] values) {return false;}
}class IntValueSet : ValueSet{
private int[] values;public IntValueSet(int capacity){
base.capacity = capacity; this.values = new int[capacity];
}public override void PrintValues(){
for (int i = 0; i < base.count; i++)Console.Write("{0} ", this.values[i]);
Console.WriteLine();}public override bool AddValues(params int[] values){
foreach (int element in values)for (int j = 0; j < base.count; j++)
if (this.values[j] == element) return false;foreach (int element in values)
this.values[base.count++] = element; return true;
}}class StringValueSet : ValueSet{
private string[] values;public StringValueSet(int capacity){
base.capacity = capacity;values = new string[capacity];
}public override void PrintValues(){
for (int i = 0; i < count; i++)Console.Write("{0} ", values[i]);
Console.WriteLine();}public override bool AddValues(params string[] values){
foreach (string element in values)for (int j = 0; j < base.count; j++)
if (this.values[j] == element) return false;foreach (string element in values)
this.values[count++] = element;return true;
}}class Program1{
static void Main(string[] args){
ValueSet[] vs = new ValueSet[5];
Programmierung II - 115 -
vs[0] = new IntValueSet(50);vs[0].AddValues(4, 9, 7, 5, -3);vs[0].PrintValues();vs[1] = new StringValueSet(20);vs[1].AddValues("Anna", "Hannes", "Willi", "Paul");vs[1].PrintValues();
}}
Programmierung II - 116 -
9.3 Abstrakte Klassen und Methoden
Abstrakte Klasse
• Klasse, von der keine Instanzen erzeugt werden können
• Abstrakte Klassen werden durch den Modifizierer abstract gekennzeichnet
• eine abstrakte Klasse kann abstrakte Methoden (auch Eigenschaften, Indexer und Ereignisse) enthalten
Abstrakte Methode
• Methodendeklaration, die keinen Anweisungsblock besitzt und durch den Modifizierer abstract gekennzeichnet ist
• eine von einer abstrakten Basisklasse abgeleitete Klasse muß
− alle abstrakten Methoden der Basisklasse mit override überschreiben oder
− selbst als abstrakte Klasse mit abstract gekennzeichnet werden
• eine Methode, die mittels override eine abstrakte Methode einer Basisklasse überschreibt, zeigt polymorphes Verhalten
Beispiel
namespace Beispiel{
abstract class ValueSet{
protected int count;protected int capacity;public abstract void PrintValues();public abstract bool AddValues(params int[] values); public abstract bool AddValues(params string[] values);
}class IntValueSet : ValueSet{
private int[] values;public IntValueSet(int capacity) { . . . }public override void PrintValues() { . . . }public override bool AddValues(params int[] values) { . . . }
}class StringValueSet : ValueSet{
private string[] values;public StringValueSet(int capacity)
Programmierung II - 117 -
public override void PrintValues() { . . . }public override bool AddValues(params string[] values) { . . . }
}class Program1{
static void Main(string[] args){
ValueSet[] vs = new ValueSet[5];vs[0] = new IntValueSet(50);vs[0].AddValues(4, 9, 7, 5, -3);vs[0].PrintValues();vs[1] = new StringValueSet(20);vs[1].AddValues("Anna", "Hannes", "Willi", "Paul");vs[1].PrintValues();
}}
}
Beispiel 9.3.a
namespace Beispiel{
abstract class ValueSet{
protected int count;protected int capacity;public abstract void PrintValues();public abstract bool AddValues(params object[] values);
}class IntValueSet : ValueSet{
private int[] values;public IntValueSet(int capacity) { . . . }
public override void PrintValues() { . . . }public override bool AddValues(params object[] values){
foreach (object element in values) if(!(element is int)) // Exception{
Console.WriteLine("Zuweisung eines Nicht-Int-Elements");return false;
}foreach (object element in values)
for (int j = 0; j < base.count; j++)if (this.values[j] == (int) element) return false;
foreach (int element in values)this.values[count++] = (int) element;
return true;}
}class StringValueSet : ValueSet{
private string[] values;public StringValueSet(int capacity) { . . . }public override void PrintValues() { . . . }
Programmierung II - 118 -
public override bool AddValues(params object[] values) {
foreach (object element in values)if (!(element is string)) // Exception{
Console.WriteLine("Zuweisung eines Nicht-String-Elements");return false;
}foreach (object element in values)
for (int j = 0; j < count; j++)if (this.values[j] == (string) element) return false;
foreach (string element in values)this.values[count++] = (string) element;
return true;}
}class Program1{
static void Main(string[] args){
ValueSet[] vs = new ValueSet[5];vs[0] = new IntValueSet(50);vs[0].AddValues(4, 9, 7, 5, -3);vs[0].PrintValues();vs[1] = new StringValueSet(20);vs[1].AddValues("Anna", "Hannes", "Willi", "Paul");vs[1].PrintValues();
}}
}
• besitzt eine Klasse eine abstrakte Methode, dann muß sie selbst als abstract definiert werden
Programmierung II - 119 -
UML: Abstrakte Klassen
• Abstrakte Klasse/Methoden werden im Klassendiagramm kursiv dargestellt
Beispiel
# count : int# capacity : int
+ PrintValues( ) : void+ AddValues( values : object[] ) : bool
ValueSet
- values : int[]
+ IntValueSet( capacity : int ) + PrintValues( ) : void+ AddValues( values : object[] ) : bool
IntValueSet
- values : string[]
+ StringValueSet( capacity : int ) + PrintValues( ) : void+ AddValues( values : object[] ) : bool
StringValueSet
Programmierung II - 120 -
9.4 Schnittstellen
• Schnittstellen dienen der Deklaration von Methodenschemata, die Klassen, die diese Schnittstellen implementieren, einhalten müssen
9.4.1 Schnittstellendeklaration
Syntax
interface-declaration
interface-modifier interface name
{
interface-member
}
interface-modifier
interface-base:
partial
interface-base
interface-type
,
access-modifier
• interfac-basee: eine Schnittstelle kann mehrere Basisschnittstellen besitzen: die Schnittstelle erbt dann alle Memberdeklarationen der Basisschnittstellen
• Schnittstellennamen beginnen konventionsgemäß mit einem großen 'I'
Programmierung II - 121 -
Syntax
interface-member
interface-method-declaration
interface-property-declaration
interface-event-declaration
interface-indexer-declaration
• Member einer Schnittstelle sind die von den Basisschnittstellen vererbten Member und die in der Schnittstelle selbst deklarierten Member.
• Schnittstellenmember enthalten keine Anweisungsblöcke
Syntax
interface-method-declaration
void
name parameter(type )
new
;
Syntax
interface-property-declaration
name {type }
new
get ;
get ;
set ;
set ;
• Alle Schnittstellenmember sind implizit public
Programmierung II - 122 -
9.4.2 Schnittstellenimplementierung
• Schnittstellen können von Klassen (und auch Strukturen) implementiert werden.
Syntax (Vervollständigung: Interface-Implementation)
class-declaration
class-modifier class name
{
class-member
}
class-base:
class-base
class-type
interface-type , interface-type
• die von einer Schnittstelle übernommenen Klassenmember
− müssen in Name, Parametern und Rückgabetyp mit dem Schnittstellenmember übereinstimmen
− dürfen nur public implementiert werden
− können abstract oder virtual sein
Beispiel
namespace Beispiel{
public interface IFliegen{
void Start();}public interface IKraftstoff{
void Tanken(int menge);}class Motorflieger : IFliegen, IKraftstoff{
public void Start(){
Console.WriteLine("Motorflieger startet");}public void Tanken(int menge)
Programmierung II - 123 -
{Console.WriteLine("Motorflieger tankt {0} Liter", menge);
}}class Segelflieger{
public void Start(){
Console.WriteLine("Segelflieger startet");}
}class Program{
static void Main(string[] args){
Motorflieger m = new Motorflieger();Segelflieger s = new Segelflieger();m.Tanken(200); // Ausgabe: Motorflieger tankt 200 Literm.Start(); // Ausgabe: Motorflieger startets.Start(); // Ausgabe: Segelflieger startet
}}
}
• innerhalb einer Vererbungshierarchie werden Schnittstellen vererbt
Beispiel
namespace Beispiel{
interface IGegenstand{
string Besitzer { get; set;}}interface IFliegen{
void Start();}class Luftfahrzeug : IGegenstand, IFliegen{
protected string besitzer; //protected o.k., da mit Eigenschaft Besitzer zugegriffenpublic string Besitzer{
get { return besitzer; }set { this.besitzer = value; }
}public void Start(){
Console.WriteLine("Flieger von {0} startet", this.besitzer);}
}class Motorflieger : Luftfahrzeug{}
Programmierung II - 124 -
class Segelflieger : Luftfahrzeug{}class Program{
static void Main(string[] args){
Luftfahrzeug[] obj = new Luftfahrzeug[10];obj[0] = new Motorflieger();obj[0].Besitzer = "Meier";obj[1] = new Segelflieger();obj[1].Besitzer = "Müller";obj[0].Start(); // Ausgabe: Flieger von Meier startetobj[1].Start(); // Ausgabe: Flieger von Meier startet
}}
}
• Werden Schnittsellen-Member einer Basisklasse virtuell definiert und in den abgeleiteten Klassen mit override überschrieben, zeigen sie dort polymorphes Verhalten
Beispiel
namespace Beispiel{
interface IGegenstand{
string Besitzer { get; set;}}interface IFliegen{
void Start();}class Luftfahrzeug : IGegenstand, IFliegen{
protected string besitzer;public string Besitzer{
get { return besitzer; }set { this.besitzer = value; }
}public virtual void Start(){ }
}class Motorflieger : Luftfahrzeug{
public override void Start(){
Console.WriteLine("Motorflieger von {0} startet", this.besitzer);}
}class Segelflieger : Luftfahrzeug{
public override void Start()
Programmierung II - 125 -
{Console.WriteLine("Segelflieger von {0} startet", this.besitzer);
}}class Program{
static void Main(string[] args){
Luftfahrzeug[] obj = new Luftfahrzeug[10];obj[0] = new Motorflieger();obj[0].Besitzer = "Meier";obj[1] = new Segelflieger();obj[1].Besitzer = "Müller";obj[0].Start(); // Ausgabe: Motorflieger von Meier startetobj[1].Start(); // Ausgabe: Motorflieger von Müller startet
}}
}
• Abstrakte Kassen, die Schnittstellen implementieren, können deren Methoden abstrakt definieren
Beispiel 9.4.2.a
namespace Beispiel{
public interface IPrintable{
void PrintValues();}public interface ISetBasics{
bool AddValues(params object[] values);}abstract class ValueSet : IPrintable, ISetBasics{
protected int count;protected int capacity;public abstract void PrintValues();public abstract bool AddValues(params object[] values);
}class IntValueSet : ValueSet{
private int[] values;public IntValueSet(int capacity) { . . . }public override void PrintValues() { . . . }public override bool AddValues(params object[] values) { . . . }
}class StringValueSet : ValueSet{
private string[] values;public StringValueSet(int capacity) { . . . }public override void PrintValues() { . . . }public override bool AddValues(params object[] values) { . . . }
}
Programmierung II - 126 -
class Program1{
static void Main(string[] args){
ValueSet[] vs = new ValueSet[5];vs[0] = new IntValueSet(50);vs[0].AddValues(4, 9, 7, 5, -3);vs[0].PrintValues();vs[1] = new StringValueSet(20);vs[1].AddValues("Anna", "Hannes", "Willi", "Paul");vs[1].PrintValues();
}}
}
Programmierung II - 127 -
UML: Schnittstellen
• Schnittstellen werden durch den Stereotyp <<interface>> über dem Schnittstellennamen gekennzeichnet
• die Implementation einer Schnittstelle wird durch einen nicht ausgefüllten Pfeil mit gestrichelter Linie dargestellt
Beispiel
# count : int# capacity : int
+ PrintValues( ) : void+ AddValues( values : object[] ) : bool
ValueSet
- values : int[]
+ IntValueSet( capacity : int ) + PrintValues( ) : void+ AddValues( values : object[] ) : bool
IntValueSet
- values : string[]
AddValues( values : object[] ) : bool
StringValueSet
PrintValues( ) : void
<<interface>> ISetBasics
<<interface>> IPrintable
+ StringValueSet( capacity : int ) + PrintValues( ) : void+ AddValues( values : object[] ) : bool
Programmierung II - 128 -
9.4.3 Explizite Implementierung
Beispiel
namespace Beispiel{
interface IMotor{
void KraftstoffzufuhrErhöhen();void TurboladerZuschalten();
}class Fahrzeug : IMotor{
public void KraftstoffzufuhrErhöhen(){
Console.WriteLine("Kraftstoffzufuht erhöht");}public void TurboladerZuschalten(){
Console.WriteLine("Turbolader zugeschaltet");}
}class Program{
static void Main(string[] args){
Fahrzeug meinPkwj = new Fahrzeug();meinPkw.KraftstoffzufuhrErhöhen();meinPkw.TurboladerZuschalten();
}}
}
• Explizite Implementierung
Definition eines Members unter der Bezeichnung
iinterface-name . member-name
• bei expliziter Implementierung dürfen in der Memberdefinition keine Modifizierer angegeben werden
Beispiel
namespace Beispiel{
interface IMotor{
void KraftstoffzufuhrErhöhen();void TurboladerZuschalten();
}class Fahrzeug : IMotor
Programmierung II - 129 -
{public void KraftstoffzufuhrErhöhen(){
Console.WriteLine("Kraftstoffzufuht erhöht");}void IMotor.TurboladerZuschalten(){
Console.WriteLine("Turbolader zugeschaltet");}
}class Program{
static void Main(string[] args){
Fahrzeug meinPkw = new Fahrzeug();meinPkw.KraftstoffzufuhrErhöhen();meinPkw.TurboladerZuschalten(); // Fehler
}}
}
• Zugriff auf ein explizit implementiertes Member erfolgt immer über eine Schnittstelleninstanz, der eine Referenz auf ein Objekt der Klasse, die die Schnittstelle implementiert, zugewiesen wurde:
interface-name interface-variable //Schnittstelleninstanz definiereninterface-variable = object-referenz //Schnittstelleninstanz referenziereninterface-variable . member //Schnittstellemember-Aufruf
Beispiel
namespace Beispiel{
interface IMotor{
void KraftstoffzufuhrErhöhen();void TurboladerZuschalten();
}class Fahrzeug : IMotor{
public void KraftstoffzufuhrErhöhen(){
Console.WriteLine("Kraftstoffzufuht erhöht");}void IMotor.TurboladerZuschalten(){
Console.WriteLine("Turbolader zugeschaltet");}
}class Program{
static void Main(string[] args){
Fahrzeug meinPkw = new Fahrzeug();IMotor meinMotor = meinPkw;
meinPkw.KraftstoffzufuhrErhöhen();
Programmierung II - 130 -
meinMotor.TurboladerZuschalten(); // o.k.}
}}
Beispiel
namespace Beispiel{
interface ILandfahrzeug{
void Geschwindigkeit();}interface IWasserfahrzeug{
void Geschwindigkeit();}class Fahrzeug : ILandfahrzeug, IWasserfahrzeug{
void ILandfahrzeug.Geschwindigkeit(){
Console.WriteLine("Geschwindigkeit in km/h");}void IWasserfahrzeug.Geschwindigkeit(){
Console.WriteLine("Geschwindigkeit in Knoten");}
}class Program{
static void Main(string[] args){
Fahrzeug meinBoot = new Fahrzeug();IWasserfahrzeug wfGeschw = meinBoot;wfGeschw.Geschwindigkeit();
}}
}
Programmierung II - 131 -
10. Weitere spezielle Klassen in C#
10.1 Streams und Dateien
10.1.1 Die Klasse Stream
• Stream
Datenfluß von einer Quelle zu einem Ziel, der eine Folge von Bytes repräsentiert
− Streams auf Byte-Ebene abstrahieren von den spezifischen Interpretationen einer Bytefolge (z.B. als String, als verschlüsselte Information etc.) in einer Anwendung
− Gleicher Programmcode zum Lesen/Schreiben von Bytefolgen unabhängig davon, von wem und wohin ein Datenstrom gesendet wird
− Anwendung kann somit unabhängig von den spezifischen Details eines Streams entwickelt werden
• Quelle/Ziel eines Streams kann sein:
− Dateien
− Netzwerkverbindungen
− periphere Geräte (Drucker etc.)
− Speicherblöcke
• Klasse Stream
− Abstrakte Basisklasse aller Streamklassen.
− stellt grundlegende Eigenschaften und Methoden für diese Streamklassen bereit
− Namespace: System.IO
• abgeleitete Klassen:
Stream
FileStream
NetworkStream
MemoryStream
CryptoStream
GZipStream
Programmierung II - 132 -
• 2 Typen von Stream
− Base-Streams: lesen/schreiben direkt aus/in den Datenstrom
− Pass-Through-Streams: ergänzen Base-Streams um spezielle Funktionalitäten
• 3 grundlegende Merkmale von Streams:
− Lesen: Daten werden von einem Stream in eine Datenstruktur übertragen.
− Schreiben: Daten werden aus einer Datenstruktur in einen Stream übertragen.
− Positionieren: aktuelle Position in einem Stream wird abgefragt und bearbeitet. (wahlfreier Zugriff)
10.1.2 Die Klasse FileStream
• Klasse FileStream
− stellt Eigenschaften und Methoden zum byteweisen Schreib- und Lesezugriff auf Dateien in einem Dateisystem bereit
− Methoden zum Öffnen und Schließen dieser Dateien
− Positionieren, Lesen Schreiben
− puffert Daten (Standard 8 KByte), somit:
− Erhöhung der E/A-Geschwindigkeit
− Namespace: System.IO
Eigenschaften der Klasse FileStream
Length-Eigenschaft
ruft Länge des Streams in Bytes ab
Position-Eigenschaft
ruft Position des Streams ab oder legt diese fest
Methoden der Klasse FileStream
FileStream(Path, FileMode)
FileStream(Path, FileMode, FileAccess)FileStream(Path, FileMode, FileAccess, FileShare)FileStream(Path, FileMode, FileAccess, FileShare, int)Konstruktoren, erstellen eine neue Instanz der FileStream-Klasse
Programmierung II - 133 -
• Path-Parameter
String mit Dateiname und ggf. Pfadangabe
• FileMode-Parameter (Enumeration)
bestimmt, wie das Betriebssystem das File (String) öffnen soll
Appendöffnet oder erzeugt eine Datei, Dateizeiger auf Dateiende (Anfügen)
Createerzeugt oder überschreibt eine Datei
CreateNewerzeugt eine Datei, existiert die Datei bereits: IOException
Openöffnet eine Datei, existiert die Datei nicht: FileNotFoundException
OpenOrCreateöffnet oder erzeugt eine Datei
Truncateöffnet eine Datei und löscht deren Inhalt
• FileAccess-Parameter (Enumeration)
bestimmt, wie das File zugegriffen werden darf
Readöffnet Datei ausschließlich für LesezugriffWriteöffnet Datei ausschließlich für SchreibzugriffReadWriteöffnet Datei für Lese- und Schreibzugriff
• FileShare-Parameter (Enumeration)
bestimmt, ob und wie mehrere Anwendungen/Threads auf das File zugreifen können
Nonekein Zugriff durch weitere Anwendungen/Threads auf die DateiReaddarf durch weitere ausschließlich zum Lesen geöffnet werdenWritedarf durch weitere ausschließlich zum Schreiben geöffnet werdenReadWritedarf durch weitere zum Lesen und Schreiben geöffnet werden
• int-Parameter
positiver Int32-Wert, bestimmt die Größe des Puffers
Programmierung II - 134 -
Beispiel
FileStream fs;fs = new FileStream("D:\\temp\\test.txt", FileMode.Append, FileAccess.Write, FileShare.Read);
int Read (byte[] array, int offset, int count)
Liest einen Byteblock aus dem Stream und schreibt die Daten in das Arrayoffset: Byteoffset im Array, ab dem aus dem Stream eingelesen werden soll. count: maximale Anzahl der zu lesenden Bytes.
Rückgabewert: Anzahl gelesenen Bytes.
void Write (byte[] array, int offset, int count)
Schreibt einen Block von Bytes aus dem Array in den Streamoffset: Byteoffset im Array, ab dem in den Stream kopiert werden. count: maximale Anzahl der zu schreibenden Bytes
int ReadByte ()
Liest ein Byte aus dem Stream
void WriteByte (byte value )
Schreibt ein Byte value in den Stream
long Seek (long offset, SeekOrigin origin)
Legt die aktuelle Position des Streams auf den angegebenen Wert festoffset: Position relativ zu originorigin: Bezugspunkt für offset
Rückgabewert: neue Position im Stream.
void Flush ()
Leert den Puffer für den Stream gibt die Daten auf das zugrunde liegende Gerät aus
void Close ()
Schließt den Stream
• SeekOrigin-Parameter (Enumeration)
BeginAnfang des StreamsCurrentAktuelle Position im StreamEndEnde des Streams
Programmierung II - 135 -
Beispiel
static void Main(string[] args){
byte[] arr = { 10, 20, 30, 40, 50, 60, 70, 80};FileStream fs = new FileStream("D:\\test.txt",FileMode.Create);fs.Write(arr, 0, arr.Length);fs.Close();
}
Beispiel
static void Main(string[] args){
byte[] arr = { 10, 20, 30, 40, 50, 60, 70, 80 };FileStream fs = new FileStream("D:\\test.txt",FileMode.Create);fs.Write(arr, 0, arr.Length);Console.ReadLine(); //Programmunterbrechung
fs.Flush();Console.ReadLine(); //Programmunterbrechung
fs.Close();}
Beispiel
static void Main(string[] args){
byte[] arr = new byte[10];FileStream fs = new FileStream("D:\\test.txt",FileMode.Open);fs.Seek(4, SeekOrigin.Begin);fs.Read(arr, 4, 3);fs.Seek(1, SeekOrigin.Begin);fs.Read(arr, 0, 2);fs.Close();for (int i = 0; i < 10; i++)Console.Write("{0} ", arr[i]);
}
Programmierung II - 136 -
• Inhalt der Datei test.txt mit dem Editor betrachtet
• Bytefolgen eines Streams müssen sinnvoll interpretiert werden, damit sie Informationen liefern
Beispiel
20 117 220 4 88 157 58 14
Text (z.B. "AC&86<%u")
Binär (z.B. als double-Wert 22.345)
Bytefolge
interpretiert als
• Klassen zur Interpretation von Streams als
− Text --> StreamReader, StreamWriter
− Binär -->BinaryReader, BinaryWriter
• alles Pass-Through-Klassen: ergänzen ein Stream-Objekt um weitere Funktionalitäten
Programmierung II - 137 -
10.1.3 Die Klassen StreamReader und StreamWriter
• von Stream abgeleitete Klassen können nur für die Ein-/Ausgabe von Bytefolgen verwendet werden.
• StreamReader/StreamWriter stellen Methoden zur Ein-/Ausgabe von Zeichen in einer bestimmten Codierung zur Verfügung
Textformate
• ASCII-Zeichensatz
− Verschlüsselung der Zeichen durch 7 Bit (0 .. 127)
− keine Umlaute möglich
− US-Standard
• ANSI-Zeichensatz (ISO 8859-1)
− Verschlüsselung der Zeichen durch 8 Bit (0 .. 255)
− Umlaute und andere sprachspezifischer Schriftzeichen möglich
− Europa-Standard
• Unicode
− Zeichen werden abstrakt definiert und jedem Zeichen eine Nummer zugeordnet
− derzeit fast 96.382 unterschiedliche Zeichen
− abstrakte Zeichen müssen in Bitmuster transformiert werden: Unicode Transformation Format (UTF)
− verschiedene Transformationen: UTF-7, UTF-8, UTF-16, UTF-32
• UTF-8
− Standard in .NET
− Unicode-Zeichen 0..127 entsprechen dem ASCII-Zeichensatzes und werden in einem Byte verschlüsselt
− Unicode-Zeichen ab 128 werden in 2 bis 4 Byte verschlüsselt
− in .NET ist die Länge eines Strings (Anzahl der Zeichen) <= Anzahl der Bytes
Programmierung II - 138 -
Eigenschaften der Klasse StreamWriter
EndOfStream-Eigenschaft
ruft das Ende des Streams ab. Wenn aktuelle Stream-Position am Ende: true, sonst false
Methoden der Klasse StreamWriter
StreamWriter (Stream)
Konstruktor. Erzeugt Instanz der StreamWriter-Klasse zum Schreiben in den angegebenen Stream mit Standard-Codierung
StreamWriter (Stream, Encoding)
wie oben, Encoding: Codierung (UTF-8, ASCII ...)
StreamWriter (Path)
wie oben, Erzeugt direkt Instanz der StreamWriter-Klasse ohne vorherigen FileStream Path: String aus Dateiname und ggf. Pfad
Beispiel
FileStream fs = new FileStream("D:\\temp\\test.txt", FileMode.Create);StreamWriter sw = new StreamWriter(fs, Encoding.ASCII);
Beispiel
StreamWriter sw = new StreamWriter("D:\\temp\\test.txt");
void Write ( <parameter> )
Schreibt <parameter> in den TextStream
Syntax der <parameter> wie bei Console.Write
void WriteLine ( <parameter> )
wie Write, nur mit Zeilenabschluß '\n'
void Flush ()
Leert den Puffer für den Writer gibt die Daten auf den zugrunde liegenden Stream aus
void Close ()
Schließt das StreamWriter-Objekt und den zugrunde liegenden Stream.
Programmierung II - 139 -
Methoden der Klasse StreamReader
StreamReader (Stream)
Konstruktor. Erzeugt Instanz der StreamReader-Klasse zum Lesen aus dem angegebenen Stream mit Standard-Codierung
StreamReader (Stream, Encoding)
Konstruktor. Encoding: Codierung (UTF-8, ASCII ...)
StreamReader (Path)
Konstruktor. Erzeugt direkt Instanz der StreamReader-Klasse ohne vorherigen FileStreamPath: String aus Dateiname und ggf. Pfad
int Read ()
Liest das nächste Zeichen aus dem Stream, gibt es als Int32 zurück und verschiebt die Position um ein Zeichen nach vorn. Ist kein weiteres Zeichen im Stream wird -1 zurückgegeben. Wie Console.Read.
string ReadLine ()
Liest eine Zeile von Zeichen aus dem Stream und gibt die Daten als Zeichenfolge zurück. Bei Stream-Ende wird null zurückgegeben. Wie Console.ReadLine.
string ReadToEnd ()
Liest den Stream von der aktuellen Position bis zum Ende des Streams und gibt ihn als String zurück
void Close ()
Schließt das StreamWriter-Objekt und den zugrunde liegenden Stream.
Beispiel
static void Main(string[] args){
string s;FileStream fs = new FileStream("D:\\test.txt", FileMode.Create);StreamWriter sw = new StreamWriter(fs);while ((s = Console.ReadLine()) != null)sw.WriteLine(s);
sw.Flush();StreamReader sr = new StreamReader(fs);fs.Seek(0, SeekOrigin.Begin);while(!sr.EndOfStream)Console.WriteLine(sr.ReadLine());sw.Close();sr.Close();
}
Programmierung II - 140 -
10.1.4 Die Klassen BinaryReader und BinaryWriter
• BinaryReader/BinaryWriter stellen Methoden zur Ein-/Ausgabe von primitive Datentypen als Binärwerte zur Verfügung
• BinaryReader stellt für jeden Grunddatentyp eine Read-Methode zur Verfügung
byte ReadByte() - liest ein Bytelong ReadInt64() - liest ein 8-Byte-Integerdouble ReadDouble()- liest einen 8-Byte Gleitkommawert. . .
• BinaryWriter stellt für jeden Grunddatentyp eine Überladung der Write-Methode zur Verfügung
void Write ( Byte ) - schreibt ein Bytevoid Write ( Long ) - schreibt ein 8-Byte-Integervoid Write ( Double ) - schreibt einen 8-Byte Gleitkommawert. . .
• um Daten, die im Binärformat vorliegen, richtig interpretieren zu können, muß man deren Datentyp kennen
Beispiel
static void Main(string[] args){
int d = 123456; // entspricvht INT32FileStream fs;fs = new FileStream("D:\\test.txt", FileMode.Create);BinaryWriter bw = new BinaryWriter(fs);bw.Write(d); // Wriote für INT32bw.Flush();BinaryReader br = new BinaryReader(fs);fs.Seek(0, SeekOrigin.Begin);Console.WriteLine(br.ReadInt32());//for(int i=0; i<fs.Length; i++)// Console.WriteLine(br.ReadByte());
}
Beispiel
static void Main(string[] args){
fs = new FileStream("D:\\test.txt", FileMode.Open);BinaryReader br = new BinaryReader(fs);for(int i=0; i<fs.Length; i++)Console.WriteLine(br.ReadByte());
}
Programmierung II - 141 -
10.1.5 Spezielle Klassen zur Dateiarbeit
Die Klasse File, Methoden
• Stellt statische Methoden zum Kopieren, Verschieben, Umbenennen, Erstellen, Öffnen, Löschen und Anfügen von Dateien zur Verfügung
FileStream Create ( Path )
Erstellt eine Datei im angegebenen Pfad-String FileStream fs = File.Create("D:\\temp\\test.txt");
FileStream Open ( Path, FileMode, FileAccess, FileShare )
Öffnet einen FileStream auf dem angegebenen Pfad-StringFileStream fs = File.Open("D:\\test.txt", FileMode.Open);
String ReadAllText ( Path )
Öffnet eine Textdatei, liest alle Zeilen der Datei in eine Zeichenfolge und schließt die Datei
void WriteAllText ( Path, String )
Erstellt/Überschreibt eine Datei mit dem String und schließt die Datei dannstring s;s = File.ReadAllText("D:\\test1.txt");File.WriteAllText("D:\\test2.txt",s);
void Delete ( Path )
Löscht die angegebene DateiFile.Delete("D:\\test2.txt",s);
void Move ( SourcePath, DestPath )
Verschiebt eine Datei von einem Verzeichnis SourcePath in ein Verzeichnis DestPathFile.Move("D:\\test.txt", "D:\\temp\\test_neu.txt");
void SetAttributes ( Path, FileAttributes )
Setzt File-Attribute für eine Datei
FileAttributes GetAttributes ( Path )
Liest File-Attribute einer Dateienum FileAttributes: Hidden, ReadOnly ...File.SetAttributes("D:\\test.txt", FileAttributes.Hidden);
Programmierung II - 142 -
Die Klasse Directory, Methoden
• Stellt statische Methoden zum Erstellen, Verschieben und Auflisten in Verzeichnissen und Unterverzeichnissen zur Verfügung
DirectoryInfo CreateDirectory ( Path )
Erstellt ein Verzeichnis/Unterverzeichnis PathDirectory.CreateDirectory("D:\\TEST");
void DeleteDirectory ( Path )
Löscht ein leeres Verzeichnis/Unterverzeichnis PathDirectory.DeleteDirectory("D:\\TEST");
string[] GetFiles ( Path )
Gibt alle Dateien eines Verzeichnisses in ein String-Array ausstring[] s_array = Directory.GetFiles("D:\\temp");
foreach (string s in s_array) Console.WriteLine(s);
Die Klasse DriveInfo, Methoden und Eigenschaften
• Stellt Methoden und Eigenschaften zum Abfragen von Laufwerkinformationen bereit.
DriveInfo[] GetDrives ()
Stellt für jedes logische Laufwerk ein DriveInfo-Objekt in einem Array bereit.
IsReady-Eigenschaft
Ruft einen Bool-Wert ab, der angibt, ob ein Laufwerk bereit ist
Name-Eigenschaft
Ruft den Namen eines Laufwerks als String ab.
DriveType-Eigenschaft
Ruft den Typ (Festplatte, CD-LW, ...) eines Laufwerks als String ab
TotalSize-Eigenschaft
Ruft die Gesamtgröße des Speicherplatzes auf einem Laufwerk in Bytes ab
Beispiel
static void Main(string[] args){
DriveInfo[] d_array = DriveInfo.GetDrives();foreach (DriveInfo d in d_array){Console.WriteLine("Name: {0}", d.Name);Console.WriteLine("Typ : {0}", d.DriveType);if (d.IsReady == true)Console.WriteLine("Size: {0}", d.TotalSize);Console.WriteLine();
Programmierung II - 143 -
}}
Programmierung II - 144 -
10.2. Ausnahmen (exceptions)
• klassische Fehlerbehandlung: return-Code
Beispiel
int Modulo(int x, int y){
if (x>=0)&&(y != 0)return x % y;
elsereturn -1;
}main(){
...if (erg=Modulo(a,b) != -1)
printf("%f",erg)else
printf("Fehler: Division durch 0 oder x ist negativ");...
}
Beispiel
int Modulo(int x, int y){
if (y != 0)return x % y;
elsereturn ???;
}
• Nachteile der klassischen Fehlerbehandlung:
− uneinheitliche Behandlung von Fehlern
− Programmierer ist nicht gezwungen, auf Fehler zu reagieren
Exception-Mechanismus
• Exception (dt. Ausnahme): Laufzeitfehler, der ein definiertes Fehlerobjekt (der Klasse Exception) erzeugt, das die speziellen Fehlerinformationen kapselt
• Ablauf nach Auslösen ("Werfen der Exception") einer Exception (z.B. Division durch 0):
− der aktuelle Block, der die Exception ausgelöst hat wird verlassen und in den übergeordneten Block zurückgekehrt bzw.
− die Methode, die Exception ausgelöst hat, wird ohne Rückgabewert beendet und zum Aufrufer der Methode zurückgekehrt
Programmierung II - 145 -
− im übergeordneten Block bzw. Aufrufer wird nach einer Fehlerbehandlungsroutine für die spezielle Exception gesucht.
− wird eine Fehlerbehandlungsroutine gefunden ("Fangen der Exception"), so wird diese abgearbeitet.
− wird keine gefunden, so wiederholt sich das für den nächst-übergeordneten Block bzw. den vorhergehenden Aufrufer
− wird auch auf der obersten Hierarchieebene keine passende Fehlerbehandlungsroutine gefunden, so wird die Exception vom System gefangen und das Programm mit Fehlernachricht beendet
• Programmierer kann auf Expedites durch Fehlerbehandlungsroutine reagieren (behandelte Ausnahmen) oder nicht (unbehandelte Ausnahmen)
10.2.1 Unbehandelte Ausnahmen
Beispiel
static int Modulo(int x, int y){
return x % y;}static void Main(string[] args){
int a = 22;int b = 0;Console.WriteLine(Modulo(a, b));
}
• System wirft bei der Ausführung 22%0 eine spezielle Exception und beendet das Programm mit Ausgabe einer Nachricht:
Programmierung II - 146 -
• Exception-Typ-Hierarchie (Ausschnitt)
Exception
SystemException
NullReferenceException
ArgumentException
ArgumentNullException
ArgumentOutOfRangeException
IOException
DirectoryNotFoundException
FileNotFoundException
ApplicationException
ArithmeticException
DivideByZeroException
• im .NET-Handbuch sind für jede Methode die Excepions aufgeführt, die sie auslösen kann
2 von der Basisklasse Exception geerbte Eigenschaften:
Message-Eigenschaft
String mit Fehlermeldung, die die Ursache der Ausnahme erklärt
StackTrace-Eigenschaft
String, der den Inhalt der Aufrufliste beschreibt. Letzter Methodenaufruf erscheint als zuerst.
Programmierung II - 147 -
10.2.2 Behandelte Ausnahmen
• 2 Schritte bei Ausnahmebehandlung
1. Markierung des Codeabschnitts der eine Exception werfen kann (try-Block)2. Fangen und Behandlung der Exception (catch-Block)
Syntax (vereinfacht)
try-statement
try block
( )nameexception-typecatch block
finally block
• Abarbeitung der try-catch-Anweisung:
1. Werfenwirft eine Anweisung im try-Block eine Exception eines bestimmten Typs, so wird die Abarbeitung des try-Blockes sofort beendet, nach einem catch-Block für diesen Exception-Typ gesucht 2. Fangen
existiert ein solcher Block, so wird dieser abgearbeitet 3. Aufräumen
falls finally-Block existiert, wird dieser danach abgearbeitet wurde die Ausnahme gefangen wird nach der try-catch-Anweisung weitergemachtwurde sie nicht gefangen so wird wie bei unbehandelten Ausnahmen verfahren
Beispiel 10.2.2.a
static void FileWrite(string path, string text){
FileStream fs = File.Open(path,FileMode.CreateNew);StreamWriter sw = new StreamWriter(fs);sw.WriteLine(text);sw.Close();
}static void Main(string[] args){// Simulation für z.B. path = Console.ReadLine(); text = Console.ReadLine();
string path = "D:\\temp\\test.txt"; string text = "HALLO";
Programmierung II - 148 -
FileWrite(path,text);Console.WriteLine("Hier geht es weiter...");
}
• 2 mögliche Exceptions:
• string path = "D:\\temp\\test.txt"; // test.txt existiert bereits (CreateNew)
string text = "HALLO";
• string path = "D:\\temp1\\test.txt"; // temp1 existiert nicht
string text = "HALLO";
• weitere Ausnahmen möglich
Beispiel 10.2.2.b
static void FileWrite(string path, string text){
FileStream fs = File.Open(path,FileMode.CreateNew);StreamWriter sw = new StreamWriter(fs);sw.WriteLine(text);sw.Close();
}static void Main(string[] args){
string path = "D:\\temp\\test.txt"; string text = "HALLO";
try{FileWrite(path, text);}catch (DirectoryNotFoundException) // Fehlerhafter Pfad{Console.WriteLine("ACHTUNG FEHLER: Verzeichnis oder Pfad existieren nicht!");}catch (IOException e) // Datei existiert bereits{Console.WriteLine("ACHTUNG FEHLER: {0}", e.Message);}catch (Exception){Console.Write("UNBEKANNTER FEHLER");}
Console.WriteLine("\n...es geht weiter...");
}
Programmierung II - 149 -
• Ausgabe bei
• string path = "D:\\temp1\\test.txt"; // temp1 existiert nicht
string text = "HALLO";
• string path = "D:\\temp\\test.txt"; // test.txt existiert bereits (CreateNew)
string text = "HALLO";
• string path = "D:\\temp\test.txt"; // fehlendes '\'
string text = "HALLO";
• Reihenfolge des Auffangens der Exceptions wichtig: erst abgeleitete Ausnahme, dann erst allgemeineren Fall abfangen. (sonst Compilerfehler)
• Werden Ausnahmen gefangen, so sollten dort die aufgetretenen Fehler beseitigt werden
Programmierung II - 150 -
10.2.3 Explizites Werfen von Ausnahmen
• soll ein Zustand, der vom System nicht als Ausnahme interpretiert wird, als Ausnahme behandelt werden, so muß die Exception explizit mit throw-Anweisung geworfen werden
Syntax
throw-statement
throw expression
• expression: mit new erzeugtes Objekt einer existierenden Exception-Klasse
• es existieren mehrere überladene Konstruktoren der Exception-Klassen, u.a. für die Basisklasse und entsprechend für die abgeleiteten Klassen:
Exception()
Initialisiert eine neue Instanz der Exception-Klasse.
Exception(string message)
Initialisiert eine neue Instanz der Exception-Klasse mit einer angegebenen Message-Fehlermeldung
Beispiel 10.2.3.a
static void FileWrite(string path, string text){
if(text==null)throw new ArgumentNullException(); // StandardkonstruktorFileStream fs = File.Open(path,FileMode.CreateNew);StreamWriter sw = new StreamWriter(fs);sw.WriteLine(text);sw.Close();
}static void Main(string[] args){
string path = "D:\\temp\\test.txt"; string text = null;try{FileWrite(path, text);}
catch (ArgumentNullException e) {Console.WriteLine("ACHTUNG FEHLER: {0}", e.Message);}// ... weitere catch-Blöcke
Programmierung II - 151 -
Console.WriteLine("\n...es geht weiter..."); }
• Exception-Nachricht
Eigene Ausnahmen
• Für anwendungsspezifische Ausnahmesituationen sollten eigene Exception-Klassen von der Klasse ApplicationException abgeleitet werden
Beispiel 10.2.3.b
public class MyNullTextException : ApplicationException{
public MyNullTextException(string message): base(message){ }
}static void FileWrite(string path, string text){
if(text==null)throw new MyNullTextException ("Kein Text! Datei wurde nicht erzeugt.");FileStream fs = File.Open(path,FileMode.CreateNew);StreamWriter sw = new StreamWriter(fs);sw.WriteLine(text);sw.Close();
}static void Main(string[] args){
string path = "D:\\temp\\test.txt"; string text = null;
try{FileWrite(path, text);}
catch (MyNullTextException e) {Console.WriteLine("ACHTUNG FEHLER: {0}", e.Message);}// ... weitere catch-Blöcke
Console.WriteLine("\n...es geht weiter..."); }
Programmierung II - 152 -
• Exception-Nachricht
• Hauptprobleme beim Einsatz von Exceptions
− Herausfinden der Stellen im Programm, an denen Exceptions auftreten können
− Feststellen des speziellen Exception-Typs
• Exceptions sinnvoll verwenden da Performanceverlust
Programmierung II - 153 -
11. Klassenbeziehungen
11.1 Assoziationen
Beispiel 11.1.a
public class Person{
private string name;private string wohnort;private string strasse;private short nr;public Person(string name, string wohnort, string strasse, short nr){this.name = name;this.wohnort = wohnort;this.strasse = strasse;this.nr = nr;}
}class Program{
static void Main(string[] args){Person p1 = new Person("Cindy Meier", "HRO", "Bergstr.", 3);Person p2 = new Person("Bert Meier", "HRO", "Bergstr.", 3);}
}
Beispiel 11.1.b
public class Adresse{
private string ort;private string strasse;private short nr;public Adresse(string ort, string strasse, short nr){
this.ort = ort;this.strasse = strasse;this.nr = nr;}
}public class Person{
private string name;private Adresse wohnsitz;public Person(string name, Adresse wohnsitz){this.name = name;
Programmierung II - 154 -
this.wohnsitz = wohnsitz;}
}class Program{
static void Main(string[] args){Adresse a1 = new Adresse("HRO", "Bergstr.", 3);Adresse a2 = new Adresse("M", "Parkstr.", 1);
Person p1 = new Person("Cindy Meier", a1);Person p2 = new Person("Bert Meier", a1);Person p3 = new Person("Josef Motzen", a2);}
}
UML: Assoziation
• Assoziation
− Beziehung zwischen Klassen
− wird im UML-Klassendiagramm durch Linie zwischen den assoziierten Klassen angegeben
• Assoziationsrichtung
− Richtung, aus der ein Zugriff von Objekten der einen Klasse (source) auf Objekte der anderen Klasse (target) erfolgen kann
− eine Assoziation kann unidirektional oder bidirektional sein
− Assoziationsrichtungen werden durch Pfeile am Ende der verbindenden Linie in Richtung von der source-Klasse zur target-Klasse angegeben
− keine Pfeile oder Pfeile in beide Richtungen: bidirektionale Assoziation
• Kardinalität (Multiplizität)
gibt als Zahl n/* oder Bereich n .. m an, wieviel Objekte der einen Klasse mit wieviel Objekten der andren assoziiert sein können/müssenz.B.
0 .. 1 - kann mit keinem oder einem Objekten assoziiert sein 1 - muß mit genau einem Objekt assoziiert sein3 .. 4 - muß mit mindestens 3 und kann mit bis zu 4 Objekten assoziiert sein* - kann mit keinem oder beliebig vielen Objekten assoziiert sein (synonym: 0 .. *)1 .. * - muß mit einem und kann mit bis zu beliebig vielen Objekten assoziiert sein
• Rolle
Bezeichnung, unter der ein Objekt der einen Kasse mit einem Objekt der anderen Klasse assoziiert wird
• Diagramm
Programmierung II - 155 -
class 1 class 2role 1
card 1
role 2
card 2
− ein Objekt der Klasse 2 tritt in der Rolle 2 in der Klasse 1 auf und umgekehrt
− ein Objekt der Klasse 1 steht mit card2 Objekten der Klasse 2 in Beziehung und umgekehrt
Beispiel
Person
- name : string
+ Person( name : string, wohnsitz : Adresse)
Adresse
- ort : string- strasse : string- nr : short
+ Adresse( ort : string, strasse : string, nr : short)
- wohnsitz 1
*
UML: Aggregation und Komposition
• Aggregation und Komposition bilden Spezialfälle der Assoziation
• Aggregation
− Assoziation, bei der Objekte der target-Klasse Teile eines Objektes der source-Klassen bilden
− wird im Klassendiagramm durch nicht ausgefüllte Raute an der source-Klasse dargestellt
SourceClass TargetClassrole 2
card 2
• Komposition
− Assoziation, bei der Objekte der target-Klasse existenzabhängig eines Objektes der source-Klassen sind
− wird im Klassendiagramm durch ausgefüllte Raute an der source-Klasse dargestellt
Programmierung II - 156 -
SourceClass TargetClassrole 2
card 2
11.2. Indizierer
Beispiel 11.2.a
public class Spieler{
public string name;public int punkte = 0; public Spieler(string name){this.name = name;}public void Gewinn(int punkte){this.punkte+=punkte;}
}class Program{
static void Main(string[] args){Spieler[] Runde1 = new Spieler[3];Runde1[0] = new Spieler("Meier");Runde1[1] = new Spieler("Motzen");Runde1[2] = new Spieler("Schmidt");
Runde1[1].Gewinn(24);for(int i=0; i<3; i++)Console.WriteLine("{0} {1}",Runde1[i].name,Runde1[i].punkte);}
}
Beispiel 11.2.b
public class Spieler // Code wie oben{
public string name;public int punkte = 0; public Spieler(string name){this.name = name;}public void Gewinn(int punkte){this.punkte+=punkte;}
Programmierung II - 157 -
}public class Skatrunde{
private Spieler[] Runde = new Spieler[3];public Spieler Get(int index){return Runde[index];}public void Set(int index, string name){Runde[index] = new Spieler(name);}public void Gewinn(int index, int punkte) // Überladene Methode{Runde[index].Gewinn(punkte);}
}class Program{
static void Main(string[] args){Skatrunde Runde1 = new Skatrunde();Runde1.Set(0, "Meier");Runde1.Set(1, "Motzen");Runde1.Set(2, "Schmidt");
Runde1.Gewinn(1, 24);for (int i = 0; i < 3; i++)
Console.WriteLine("{0} {1}", Runde1.Get(i).name, Runde1.Get(i).punkte);}
}
• UML-Klassendiagramm
Skatrunde
+ Get( index: int ): Spieler+ Set( index: int, name: string ): void+ Gewinn( index: int, punkte: int): void
Spieler
+ name: string+ punkte: int = 0
+ Spieler( name: string )+ Gewinn( punkte: int ): void
- Runde
0..3
.
• ein Indizierer (Indexer) ermöglicht den Zugriff auf Klassenobjekte mittels []-Zugriffsoperator
Programmierung II - 158 -
Syntax
indexer-declaration
indexer-modifier
indexer-modifier
type[type ]
method-modifier ohne static
indexthis
get-accessor{ }set-accessor
get-accessor set-accessor
,
• Indexer müssen nicht mit ganzzahligen Werten indiziert werden.
• getter-rumpf enthält Anweisungen zum lesenden Zugriff, der setter-rumpf Anweisungen zum schreibenden Zugriff auf die Elemente eines (privates) Arrays
• get-acessor muß eine return-anweisung enthalten
• Mithilfe des Schlüsselworts value wird im set-Accessor der Wert zugewiesen wird (wie bei Properties)
Beispiel 11.2.c
public class Spieler // Code wie oben{
public string name;public int punkte = 0; public Spieler(string name){this.name = name;}public void Gewinn(int punkte){this.punkte+=punkte;}
}
Programmierung II - 159 -
public class Skatrunde{
private Spieler[] Runde = new Spieler[3];public Spieler this[int index]{get { return Runde[index]; }
set { Runde[index] = value; }}}class Program{
static void Main(string[] args){Skatrunde Runde1 = new Skatrunde();Runde1[0] = new Spieler("Meier");Runde1[1] = new Spieler("Motzen");Runde1[2] = new Spieler("Schmidt");
Runde1[1].Gewinn(28);for (int i = 0; i < 3; i++)Console.WriteLine("{0} {1}", Runde1[i].name, Runde1[i].punkte);}
}
Programmierung II - 160 -
11.3 Enumeratoren
• Um die foreach-Anweisung für Array-Objekte einer Klasse zu verwenden, muß ein Enumerator definiert werden (die Klasse muß eine Methode GetEnumerator besitzen)
• der Compiler überführt die Anweisung
foreach( <datentyp> element in array )Console.WriteLine( element );
in eine AnweisungsfolgeIEnumerator enumerator = array.GetEnumerator();enumerator.Reset();while( enumerator.MoveNext() ){
<datentyp> element = ( <datentyp> ) enumerator.Current;Console.WriteLine( element );
}
• Ein Enumerator ist ein mit
GetEnumerator() aus dem Interface IEnumerable
erzeugtes Objekt, das mit Hilfe von
Reset(), MoveNext und Current aus dem Interface IEnumerator
auf die Elemene einer Aufzählung zugegriffen werden kann.
• Um die foreach-Anweisung für Array-Objekte einer Klasse zu verwenden, muß die Klasse die Schnittstelle IEnumerable implementieren
IEnumerable
• Schnittstelle, die einen Enumerator zur Verfügung stellt
• Namespace: System.Collections
IEnumerator GetEnumerator()
Methode, die einen Enumerator für eine Klasse erzeugt
IEnumerator
• Schnittstelle, die die Methoden/Eigenschaften für einen Enumerator zur Verfügung stellt, die von der diesen Enumerator verwendenden Klasse klassenspezifisch implementiert werden müssen
• Namespace: System.Collections
Object Current
Eigenschaft, die das aktuelle Element in der Auflistung abruft
Programmierung II - 161 -
bool MoveNext()
− Setzt den Enumerator auf das nächste Element der Auflistung
− Nach Erstellen eines Enumerators wird dieser vor das erste Element positioniert
− der erste Aufruf von MoveNext positioniert den Enumerator auf das erste Element
− wird der Enumerator hinter das letzten Element positioniert, gibt MoveNext gibt false zurück
void Reset()
Setzt den Enumerator vor das erste Element der Auflistung.
Beispiel 11.3.a
public class Spieler // Code wie oben{
public string name;public int punkte = 0; public Spieler(string name){this.name = name;}public void Gewinn(int punkte){this.punkte+=punkte;}
}public class Skatrunde: IEnumerable{
private Spieler[] Runde = new Spieler[3];public Spieler this[int index]{get { return Runde[index]; }
set { Runde[index] = value; }}
// bisher Code wie obendas kann auch außerhalb geschehen, besser so, da so direkter Zugriff auf private Member
class Runde_Enumerator: IEnumerator{private Skatrunde E_Runde;private int e_index = -1;public Runde_Enumerator(Skatrunde E_Runde){this.E_Runde = E_Runde;}public object Current{get { return E_Runde[e_index]; }}public bool MoveNext(){if(e_index>=2)return false;
Programmierung II - 162 -
e_index++;return true;}public void Reset(){e_index = -1;}}public IEnumerator GetEnumerator(){return new Runde_Enumerator(this);}
} class Program{
static void Main(string[] args){Skatrunde Runde1 = new Skatrunde();Runde1[0] = new Spieler("Meier");Runde1[1] = new Spieler("Motzen");Runde1[2] = new Spieler("Schmdt");
Runde1[1].Gewinn(28);foreach (Spieler sp in Runde1)Console.WriteLine("{0} {1}", sp.name, sp.punkte);}
}
Programmierung II - 163 -
11.4 Collections
• Collection
Klasse, die eine Vielzahl von Objekten enthalten und verwalten kann
• 4 Collection-Klassen
− ArrayList
− Stack
− Queue
− Hashtable
• alle Collection-Klassen im namespace System.Collections definiert
• eine Collection kann Objekte unterschiedlichen Typs enthalten
11.4.1 ArrayList
• ArrayList
− Liste von Elementen, die intern auf ein Array abgebildet wird
− dynamisch in der Größe änderbar
Eigenschaften der Klasse ArrayList
Capacity-Eigenschaft
Liest die Anzahl der Elemente, die eine ArrayList enthalten kann oder legt diese fest.
Count-Eigenschaft
Liest die aktuelle Anzahl der Elemente einer ArrayList
Item-Eigenschaft
Liest/Schreibt ein Element einer ArrayList mittels Index der Form <arraylist-variable> [ <index> ]
Methoden der Klasse ArrayList
int Add (object Object)
Fügt ein Objekt an des Listenende an und gibt den Index zurück
void Insert (int Index, object Object)
Programmierung II - 164 -
Fügt am angegebenen Index ein Element ein
void RemoveAt (int Index)
Entfernt ein Element am angegebenen Index
void Clear( )
Löscht alle Listenelemente
Beispiel 11.4.1.a
class Program{
static void Main(string[] args){ArrayList a = new ArrayList();a.Add(11);a.Add(22);a.Add(33);foreach (object o in a)Console.Write("{0} ", o);Console.WriteLine();a[1] = 44;foreach (object o in a)Console.Write("{0} ", o);Console.WriteLine();a.RemoveAt(1); // Entfernen beim Index 1foreach (object o in a)Console.Write("{0} ", o);Console.WriteLine();a.Insert(1, 22);foreach (object o in a)Console.Write("{0} ", o);}
}
Beispiel 11.4.1.b
class Program{
static void Main(string[] args){ArrayList a = new ArrayList();a.Add(12);a.Add("Hallo");a.Add(22.31);foreach (object o in a){if(o is int)Console.WriteLine("{0} (Integer-Wert)", o);if(o is string)Console.WriteLine("{0} (String-Wert)", o);if(o is double)Console.WriteLine("{0} (Double-Wert)", o);}
Programmierung II - 165 -
}
}
Beispiel 11.4.1.c
class Apfel{ }class Birne{ }class Obstkorb{
ArrayList Korb = new ArrayList();public void Hinzu(object frucht){Korb.Add(frucht);}public void Inhalt(){int äpfel = 0;int birnen = 0;foreach (object o in Korb){if (o is Apfel)äpfel++;elsebirnen++;}Console.WriteLine("{0} Äpfel und {1} Birnen gesammelt", äpfel, birnen);}
}class Program{
static void Main(string[] args){Apfel apfel = new Apfel();Birne birne = new Birne();Obstkorb meinKorb = new Obstkorb(); meinKorb.Hinzu(apfel);meinKorb.Hinzu(birne);meinKorb.Hinzu(apfel);meinKorb.Hinzu(birne);meinKorb.Hinzu(birne);meinKorb.Inhalt(); // 2 Äpfel und 3 Birnen gesammelt}
}
Obstkorb
+ Hinzu( frucht: object ): void+ Inhalt( ): void
Apfel
.
Birne
- Korb
0..*
Programmierung II - 166 -
11.4.2 Hashtable
• Hashtable
− Liste, deren Elemente aus Tupeln (object Schlüssel, object Wert) bestehen
− Zugriff auf ein Werte (Objekte) der Liste erfolgt über den Schlüssel
• Vorteil von Hashtable gegenüber ArrayList: schnelles Suchen nach Objekten
Nachteil: langsames Einfügen
• Elemente einer Hashtable sind vom Strukturtyp DictionaryEntry
Eigenschaften von DictionaryEntry
Key-Eigenschaft
Liest den Schlüssel eines Hashtable-Elements oder legt ihn fest
Value-Eigenschaft
Liest den Wert eines Hashtable-Elements oder legt ihn fest
Eigenschaften von Hashtable
Keys-Eigenschaft
Liest die Liste (ICollection) der aktuellen Schlüssel der Elemente einer Hashtable.
Values-Eigenschaft
Liest die Liste (ICollection) der aktuellen Werte (Objekte) der Elemente einer Hashtable.
Item-Eigenschaft
Liest/Schreibt ein Element einer Hashtable mittels Schlüsselindex der Form <hashtable-variable> [ <schlüssel> ]
Methoden von Hashtable
int Add (object Key, object Object)
Fügt ein mit dem angegebenen Schlüssel und Wert der Liste hinzu
void Remove(object Key)
Entfernt ein Element mit dem angegebenen Schlüssel
void Clear( )
Programmierung II - 167 -
Löscht alle Listenelemente
Beispiel 11.4.2.a
class Program{
static void Main(string[] args){Hashtable h = new Hashtable();h.Add(101, "Meier");h.Add(202, "Motzen");h.Add(303, "Meier");foreach (object o in h.Values)Console.Write("{0} ", o);
Console.WriteLine();
foreach (object o in h.Keys)Console.Write("{0} ", o);Console.WriteLine();
foreach (DictionaryEntry d in h)Console.Write("({0}, {1}) ", d.Key, d.Value);Console.WriteLine();
Console.Write("{0}", h[202]);Console.WriteLine();
h.Remove(101);foreach (DictionaryEntry d in h)Console.Write("({0}, {1}) ", d.Key, d.Value);Console.WriteLine();}
}
11.4.3 Queue
• Queue
Collection nach dem FIFO-Prinzip (First In First Out)
Eigenschaften der Klasse Queue
Count-Eigenschaft
Liest die Anzahl der Elemente, die in der Queue enthalten sind
Methoden der Klasse Queue
void Enqueue (object Object)
Programmierung II - 168 -
Fügt an das Ende der Queue ein Objekt hinzu
object Dequeue ( )
Entfernt das Objekt am Anfang von der Queue und gibt es zurück.
void Clear( )
Löscht alle Objekte der Queue
Beispiel 11.4.3.a
class Program{
static void Main(string[] args){Queue q = new Queue();q.Enqueue(11);q.Enqueue(22);q.Enqueue(33);foreach (object o in q)Console.Write("{0} ", o);Console.WriteLine();
Console.WriteLine("{0} entfernt", q.Dequeue());
foreach (object o in q) Console.Write("{0} ", o); Console.WriteLine();
q.Enqueue(11); foreach (object o in q) Console.Write("{0} ", o); Console.WriteLine(); } }}
11.4.4 Stack
• Stack
Collection nach dem LIFO-Prinzip (Last In First Out)
Eigenschaften der Klasse Stack
Count-Eigenschaft
Liest die Anzahl der Elemente, die im Stack enthalten sind
Programmierung II - 169 -
Methoden der Klasse Stack
void Push (object Object)
Fügt an den Anfang des Stack ein Objekt hinzu
object Pop ( )
Entfernt das oberste Objekt des Stack und gibt es zurück.
object Peek ( )
Gibt das oberste Objekt des Stack zurück ohne es zu entfernen.
void Clear( )
Löscht alle Objekte des Stack
Beispiel 11.4.4.a
class Program{
static void Main(string[] args){Stack s = new Stack();s.Push(11);s.Push(22);s.Push(33);foreach (object o in s)Console.Write("{0} ", o);Console.WriteLine();
Console.WriteLine("{0} entfernt", s.Pop());
foreach (object o in s) Console.Write("{0} ", o); Console.WriteLine();
s.Push(44); foreach (object o in s) Console.Write("{0} ", o); Console.WriteLine();
}}
Programmierung II - 170 -
12 Delegate und Ereignisse
12.1 Delegate
• ein Delegat ist ein Objekt, das einen Verweis auf eine Methode beschreibt
Definition eines Delegaten
Syntax
delegate-declaration
delegate-modifier
void
name
delegate-modifier
parameter(type ) ;delegate
public
protected
internal
private
new
Schnittstellen Delegate
werden bereits beim Übersetzen erstellt werden erst zur Laufzeit erstellt
können die Signaturen unterschiedlicher Methoden festlegen
legt nur eine Signatur fest
Methoden werden innerhalb der Schnittstellenklasse definiert
Delegate können innerhalb oder außerhalb von Klassen definiert werden
Programmierung II - 171 -
Erzeugen und Aufruf eines Delegat-Objektes
Definition einer Delegat-Variablen
<delegat-name> <delegat-variable> ;
Erzeugen eines Delegat-Objektes
<delegat-variable> = new <delegat-name> ( <method-name> ) ;
• Voraussetzungen
− <delegat-name> ist ein bereits definierter Delegat
− <method-name> ist eine bereits definierte Methode
− die Signaturen (Rückgabewert und Parameter) des Delegaten und der Methode sind identisch
Aufruf eines Delegaten
<delegat-variable> ( [ <actual-parameter-list> ] ) ;
Beispie 12.1.a
class Program{
public delegate void fooDelegat();
static void Main(string[] args){
fooDelegat d; d = new fooDelegat(foo1); d(); d = new fooDelegat(foo2);
d();}
public static void foo1() { }
public static void foo2() { }}
Programmierung II - 172 -
Beispiel12.1.b
abstract class Math{
public delegate int Delegat(int x, int y);public static int plus(int x, int y){
return x + y;}public static int mal(int x, int y){
return x * y;}
}class Program{
static void Main(string[] args){
Math.Delegat fd;fd = new Math.Delegat(Math.plus);Console.WriteLine("3+4 = {0}", fd(3, 4)); // Ausgabe: 3+4 = 7fd = new Math.Delegat(Math.mal);Console.WriteLine("3*4 = {0}", fd(3, 4)); // Ausgabe: 3*4 = 12
}}
• in C# 2.0 vereinfachte Erzeugen eines Delegat-Objektes:
für
<delegat-variable> = new <delegat-name> ( <method-name> ) ;auch
<delegat-variable> = <methoden-name> ;
Programmierung II - 173 -
12.2 Ereignisse
• Ein Event (Ereignis) ist eine Nachricht, die durch eine Methode ausgelöst wird und an den ursprünglichen Aufrufer der Methode zurückgesendet wird
• Event-Source: Klasse, die ein Ereignis auslöst
Event-Sink: Klasse, die ein Ereignis verarbeitet (Client)
• Methoden die nach Auslösung eines Events durch eine Event-Sink (Client) ausgeführt werden, heißen Event-Handler
Erzeugen und Auslösen eines Ereignisses
Event-Sink Event-Source
Aufruf Methode
Auslösen Event (1)
Auslösen Event (2)
(1)
(1)
(2)
(2)
Methode
Event-Handler
• Event-Variablen werden im Event-Source definiert
Programmierung II - 174 -
Syntax
event-declaration
event-modifier namedelegate-nameevent
event-modifier
method-modifier
;
Erzeugen einer Delegat-Instanz für einen Event-Handler und Binden an eine Event-Variable
<event-variable> += new <delegate-name> ( <handler-name> ) ;
• durch aufeinanderfolgende Anwendung des '+='-Operators können mehrere Handler an ein Ereignis gebunden werden
event += new delegate (handler1 ) ;event += new delegate (handler2 ) ;. . .
Entfernen eines Handlers aus einer Ereignisliste
<event-variable> -= new <delegate-name> ( <handler-name> ) ;
Auslösen eines Ereignisses im Event-Source
<event-variable> ( [ <actual-parameter-list> ] ) ;
Beispiel 12.2.a
class EventSource{
public delegate void OnEventHandler();public event OnEventHandler OnEvent; public void SourceMethode(){
OnEvent();}
} class EventSink{
static void Main(string[] args)
Programmierung II - 175 -
{EventSource es = new EventSource();es.OnEvent += new EventSource.OnEventHandler(foo1);es.OnEvent += new EventSource.OnEventHandler(foo2);es.SourceMethode();es.OnEvent -= new EventSource.OnEventHandler(foo1);es.SourceMethode();
}
public static void foo1(){ } public static void foo2(){ }
}
Publisher-Subscriber-Modell
Events deklarieren
Delegat deklarieren
delegate void TimeEventHandler(int timeEvent)
event TimeEventHandler OnMinuteevent TimeEventHandler OnHour
Events auslösen
OnHour(hour)OnMinute(minute)
Publisher (Timer, Zeitgeber)
Eventhandler definieren
Events registrieren
OnMinute += new TimeEventHandler(MinuteChange)OnHour += new TimeEventHandler(HourChange)
void MinuteChange(int m) { }void HourChange(int h) { }
Subscriber (Clock, normale Uhr)
Eventhandler definieren
Events registrieren
OnMinute += new TimeEventHandler(MinuteChange)
void MinuteChange(int m) { }
Subscriber (EggClock, Eieruhr)
Events gesendet
Programmierung II - 176 -
Beispiel 12.2.b
class Timer //Zeitgeber{
public delegate void TimeEventHandler(int timeEvent);public event TimeEventHandler OnMinute;public event TimeEventHandler OnHour;private int minute;private int hour;public Timer(int h, int m){
hour = h;minute = m;
}public void Run(){
OnHour(hour);OnMinute(minute);while (true){
Thread.Sleep(1000); //1 sek entspricht 1 minminute++;if (minute == 60){
minute = 0;hour++;OnHour(hour);
}OnMinute(minute);
}}
}class Clock //normale Uhr{
private int minute;private int hour;public void MinuteChange(int m){
minute = m;Console.WriteLine("Clock: {0} : {1}", hour, minute);
}public void HourChange(int h){
hour = h;}
}class EggClock //Eieruhr{
private int delay;public EggClock(int d){
delay = d;}public void MinuteChange(int m){
delay--;
Programmierung II - 177 -
if (delay == 0)Console.WriteLine("EggClock: Alarm!!!");
}}class Program{
static void Main(string[] args){
Timer timer = new Timer(15, 57); //Aktuelle Start-Zeit: 15:57 UhrClock clock = new Clock();EggClock eggClock = new EggClock(6); //Alarm nach 6 Minuten für ein gutes Ei!timer.OnMinute += new Timer.TimeEventHandler(clock.MinuteChange);timer.OnHour += new Timer.TimeEventHandler(clock.HourChange);timer.OnMinute += new Timer.TimeEventHandler(eggClock.MinuteChange);timer.Run();
}}