Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort zur 6. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Das Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Strukturen und Enumerationen
7 Fehlerbehandlung und Debugging
8 Auflistungsklassen (Collections)
9 Generics – Generische Datentypen
10 Weitere C#-Sprachfeatures
11 LINQ
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 XML
15 Multithreading und die Task Parallel Library (TPL)
16 Einige wichtige .NET-Klassen
17 Projektmanagement und Visual Studio 2012
18 Einführung in die WPF und XAML
19 WPF-Layout-Container
20 Fenster in der WPF
21 WPF-Steuerelemente
22 Elementbindungen
23 Konzepte von WPF
24 Datenbindung
25 Weitere Möglichkeiten der Datenbindung
26 Dependency Properties
27 Ereignisse in der WPF
28 WPF-Commands
29 Benutzerdefinierte Controls
30 2D-Grafik
31 ADO.NET – Verbindungsorientierte Objekte
32 ADO.NET – Das Command-Objekt
33 ADO.NET – Der SqlDataAdapter
34 ADO.NET – Daten im lokalen Speicher
35 ADO.NET – Aktualisieren der Datenbank
36 Stark typisierte DataSets
37 Einführung in das ADO.NET Entity Framework
38 Datenabfragen des Entity Data Models (EDM)
39 Entitätsaktualisierung und Zustandsverwaltung
40 Konflikte behandeln
41 Plain Old CLR Objects (POCOs)
Stichwort

Download:
- Beispiele, ca. 62,4 MB

Jetzt Buch bestellen
Ihre Meinung?

Spacer
Visual C# 2012 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2012

Visual C# 2012
Rheinwerk Computing
1402 S., 6., aktualisierte und erweiterte Auflage 2013, geb., mit DVD
49,90 Euro, ISBN 978-3-8362-1997-6
Pfeil 4 Vererbung, Polymorphie und Interfaces
Pfeil 4.1 Die Vererbung
Pfeil 4.1.1 Basisklassen und abgeleitete Klassen
Pfeil 4.1.2 Die Ableitung einer Klasse
Pfeil 4.1.3 Klassen, die nicht abgeleitet werden können
Pfeil 4.1.4 Konstruktoren in abgeleiteten Klassen
Pfeil 4.1.5 Der Zugriffsmodifizierer »protected«
Pfeil 4.1.6 Die Konstruktorverkettung in der Vererbung
Pfeil 4.2 Der Problemfall geerbter Methoden
Pfeil 4.2.1 Geerbte Methoden mit »new« verdecken
Pfeil 4.2.2 Abstrakte Methoden
Pfeil 4.2.3 Virtuelle Methoden
Pfeil 4.3 Typumwandlung und Typuntersuchung von Objektvariablen
Pfeil 4.3.1 Die implizite Typumwandlung von Objektreferenzen
Pfeil 4.3.2 Die explizite Typumwandlung von Objektreferenzen
Pfeil 4.3.3 Typuntersuchung mit dem »is«-Operator
Pfeil 4.3.4 Typumwandlung mit dem »as«-Operator
Pfeil 4.4 Polymorphie
Pfeil 4.4.1 Die »klassische« Methodenimplementierung
Pfeil 4.4.2 Abstrakte Methoden
Pfeil 4.4.3 Virtuelle Methoden
Pfeil 4.5 Weitere Gesichtspunkte der Vererbung
Pfeil 4.5.1 Versiegelte Methoden
Pfeil 4.5.2 Überladen einer Basisklassenmethode
Pfeil 4.5.3 Statische Member und Vererbung
Pfeil 4.5.4 Geerbte Methoden ausblenden?
Pfeil 4.6 Das Projekt »GeometricObjectsSolution« ergänzen
Pfeil 4.6.1 Die Klasse »GeometricObject«
Pfeil 4.7 Eingebettete Klassen (Nested Classes)
Pfeil 4.8 Interfaces (Schnittstellen)
Pfeil 4.8.1 Einführung in die Schnittstellen
Pfeil 4.8.2 Die Schnittstellendefinition
Pfeil 4.8.3 Die Schnittstellenimplementierung
Pfeil 4.8.4 Die Interpretation der Schnittstellen
Pfeil 4.8.5 Änderungen am Projekt »GeometricObjects«
Pfeil 4.9 Das Zerstören von Objekten – der »Garbage Collector«
Pfeil 4.9.1 Die Arbeitsweise des Garbage Collectors
Pfeil 4.9.2 Expliziter Aufruf des Garbage Collectors
Pfeil 4.9.3 Der Destruktor
Pfeil 4.9.4 Die »IDisposable«-Schnittstelle
Pfeil 4.9.5 Die Ergänzungen in den Klassen »Circle« und »Rectangle«

Rheinwerk Computing - Zum Seitenanfang

4.8 Interfaces (Schnittstellen)Zur nächsten Überschrift


Rheinwerk Computing - Zum Seitenanfang

4.8.1 Einführung in die SchnittstellenZur nächsten ÜberschriftZur vorigen Überschrift

Das Konzept der Schnittstellen ist am einfachsten zu verstehen, wenn man sich deutlich macht, worin genau der Unterschied zwischen einer Klasse und einem Objekt besteht. Klassen sind Schablonen, in denen Methoden und Eigenschaften definiert sind. Die Methoden manipulieren die Eigenschaften und stellen damit das Verhalten eines Objekts sicher. Ein Objekt wird jedoch nicht durch sein Verhalten, sondern durch seine Daten beschrieben, die über Eigenschaften manipuliert werden.

Treiben wir die Abstraktion noch weiter. Wenn Objekte durch Daten beschrieben werden und in einer Klasse Eigenschaften und Methoden definiert sind, dann muss es auch ein Extrem geben, das nur Verhaltensweisen festlegt. Genau diese Position nehmen die Schnittstellen ein.

Die Aufgaben der Schnittstellen gehen über die einfache Fähigkeit, Verhaltensweisen bereitzustellen, hinaus. Bekanntlich wird in .NET die Mehrfachvererbung nicht unterstützt. Damit sind die .NET-Architekten möglichen Schwierigkeiten aus dem Weg gegangen, die mit der Mehrfachvererbung (z. B. in C++) verbunden sind. Mehrfachvererbung ist nur schwer umzusetzen und wird deshalb in der Praxis auch nur selten eingesetzt. Andererseits hielten die .NET-Architekten es aber für erstrebenswert, neben der Basisklasse weitere »Oberbegriffe« zuzulassen, um gemeinsame Merkmale mehrerer ansonsten unabhängiger Klassen beschreiben zu können. Mit der Schnittstelle wurde ein Konstrukt geschaffen, das genau diese Möglichkeiten bietet.

Sie müssen sich Schnittstellen wie eine Vertragsvereinbarung vorstellen. Sobald eine Klasse eine Schnittstelle implementiert, hat der auf ein Objekt dieser Klasse zugreifende Code die Garantie, dass die Klasse die Member der Schnittstelle aufweist. Mit anderen Worten: Eine Schnittstelle legt einen Vertragsrahmen fest, den die implementierende Klasse erfüllen muss.


Rheinwerk Computing - Zum Seitenanfang

4.8.2 Die SchnittstellendefinitionZur nächsten ÜberschriftZur vorigen Überschrift

Schnittstellen können

  • Methoden
  • Eigenschaften
  • Ereignisse
  • Indexer

vorschreiben. (Hinweis: Indexer und Ereignisse waren bisher noch kein Thema und werden erst in Kapitel 5 bzw. Kapitel 10 behandelt.) Schnittstellen enthalten selbst keine Codeimplementierung, sondern nur abstrakte Definitionen. Schauen wir uns dazu eine einfache, fiktive Schnittstelle an:

public interface ICopy 
{
string Caption {get; set;};
void Copy();
}

Listing 4.29 Definition eines Interfaces

Die Definition einer Schnittstelle ähnelt der Definition einer Klasse, bei der das Schlüsselwort class gegen das Schlüsselwort interface ausgetauscht worden ist. Fehlt die Angabe eines Zugriffsmodifizierers, gilt eine Schnittstelle standardmäßig als internal, ansonsten kann eine Schnittstelle noch public sein. Hinter der Definition werden in geschweiften Klammern alle Mitglieder der Schnittstelle aufgeführt. Beachten Sie, dass das von den abstrakten Klassen her bekannte Schlüsselwort abstract in einer Schnittstellendefinition nicht auftaucht.

Konventionsgemäß wird dem Bezeichner einer Schnittstelle ein »I« vorangestellt. Man kann von Konventionen halten, was man will, aber diese sollten Sie einhalten.

Die Schnittstelle ICopy beschreibt die Eigenschaft Caption sowie die Methode Copy. Weil eine Schnittstelle grundsätzlich nur abstrakte Definitionen bereitstellt, hat kein Mitglied einen Anweisungsblock. Es ist auch kein Zugriffsmodifizierer angegeben. Der C#-Compiler reagiert sogar mit einer Fehlermeldung, wenn Sie einem Schnittstellenmitglied einen Zugriffsmodifizierer voranstellen. Alle von einer Schnittstelle vorgeschriebenen Member gelten grundsätzlich als public.


Rheinwerk Computing - Zum Seitenanfang

4.8.3 Die SchnittstellenimplementierungZur nächsten ÜberschriftZur vorigen Überschrift

Bei der Vererbung wird von Ableitung gesprochen, analog hat sich bei den Schnittstellen der Begriff Implementierung geprägt. Eine Schnittstelle ist wie ein Vertrag, den eine Klasse unterschreibt, sobald sie eine bestimmte Schnittstelle implementiert. Das hat Konsequenzen: Eine Klasse, die eine Schnittstelle implementiert, muss ausnahmslos jedes Mitglied der Schnittstelle übernehmen. Das erinnert uns an das Ableiten einer abstrakten Klasse: Die ableitende Klasse muss die abstrakten Member implementieren – zumindest, solange sie nicht ihrerseits selbst abstrakt sein soll.

Eine zu implementierende Schnittstelle wird, getrennt durch einen Doppelpunkt, hinter dem Klassenbezeichner angegeben. In der Klasse werden alle Member, die aus der Schnittstelle stammen (in unserem Beispiel die Methode Copy sowie die Eigenschaft Caption), mit den entsprechenden Anweisungen codiert.

class Document : ICopy {
public void Copy() {
Console.WriteLine("Das Dokument wird kopiert.");
}
public string Caption {
get{ [...] }
set{ [...] }
}
[...]
}

Listing 4.30 Implementierung einer Schnittstelle

Grundsätzlich können Sie jeden beliebigen Code in die Schnittstellenmethoden schreiben. Das ist aber nicht Sinn und Zweck. Stattdessen sollten Sie sich streng daran halten, was die Dokumentation beschreibt. Das bedeutet im Umkehrschluss aber auch, dass eine Schnittstelle ohne Dokumentation wertlos ist. Nur die Dokumentation gibt Auskunft darüber, was eine Methode leisten soll und wie ihre Rückgabewerte zu interpretieren sind.

Eine Klasse ist nicht nur auf die Implementierung einer Schnittstelle beschränkt, es dürfen – im Gegensatz zur Vererbung – auch mehrere sein, die durch ein Komma voneinander getrennt werden.

class Document : ICopy, IDisposable {
[...]
}

Eine Klasse, die eine oder mehrere Schnittstellen implementiert, darf durchaus auch eine konkrete Basisklasse haben. Dabei wird die Basisklasse vor der Liste der Schnittstellen aufgeführt. Im folgenden Codefragment bildet Frame die Basis von Document.

class Document : Frame, ICopy, IDisposable {
[...]
}

Schnittstellen dürfen nach der Veröffentlichung, d. h. nach der Verteilung, unter keinen Umständen verändert werden, da sowohl das Interface als auch die implementierende Klasse in einem Vertragsverhältnis zueinander stehen. Die Bedingungen des Vertrags müssen von beiden Vertragspartnern eingehalten werden.

Sollten Sie nach dem Veröffentlichen einer Schnittstelle Änderungen oder Ergänzungen vornehmen wollen, müssen Sie eine neue Schnittstelle bereitstellen.

Mit der Veröffentlichung einer Schnittstelle erklärt sich eine Klasse bereit, die Schnittstelle exakt so zu implementieren, wie sie entworfen wurde. Die von der Klasse übernommenen Mitglieder der Schnittstelle müssen daher in jeder Hinsicht identisch zu ihrer Definition sein:

  • Der Name muss dem Namen in der Schnittstelle entsprechen.
  • Der Rückgabewert und die Parameterliste dürfen nicht von denen in der Schnittstellendefinition abweichen.

Ein aus einer Schnittstelle stammender Member darf nur public sein. Zulässig sind außerdem die Modifizierer abstract und virtual, während static und const nicht erlaubt sind.

Aus Schnittstellen stammende Methoden zeigen in der implementierenden Klasse immer polymorphes Verhalten. Das setzt sich jedoch nicht automatisch bei den Klassen durch, die eine schnittstellenimplementierende Klasse ableiten. Eine ableitende Klasse kann daher im Weiteren die Schnittstellenmethode mit new verdecken. Soll die Schnittstellenmethode den ableitenden Klassen jedoch polymorph angeboten werden, muss sie mit virtual signiert werden.

Die Unterstützung von Visual Studio 2012

Insbesondere wenn eine Klasse eine Schnittstelle mit vielen Membern implementieren soll, lohnt es sich, diese mit Hilfe von Visual Studio automatisch hinzuzufügen. Gehen Sie dazu mit dem Mauszeiger auf den Schnittstellenbezeichner, und öffnen Sie das Kontextmenü, wie in Abbildung 4.6 gezeigt. In diesem wird Ihnen Schnittstelle implementieren und Schnittstelle explizit implementieren angeboten. In der Regel werden Sie sich für den erstgenannten Punkt entscheiden. Die explizite Schnittstellenimplementierung werden wir weiter unten noch behandeln.

Abbildung

Abbildung 4.6 Die Unterstützung von Visual Studio bei der Schnittstellenimplementierung

Wählen Sie Schnittstelle implementieren, erzeugt Visual Studio den nachfolgend gezeigten Code automatisch.

class Document : ICopy 
{
public string Caption {
get {
throw new NotImplementedException();
}
set {
throw new NotImplementedException();
}
}
public void Copy() {
throw new NotImplementedException();
}
}

Listing 4.31 Klasse, die das Interface »ICopy« implementiert

Zugriff auf die Schnittstellenmethoden

Der Aufruf einer aus einer Schnittstelle stammenden Methode unterscheidet sich nicht vom Aufruf einer Methode, die in der Klasse implementiert ist:

Document doc = new Document();
doc.Copy();

Listing 4.32 Zugriff auf die Methode »Copy« über eine Instanzvariable

Sie instanziieren zuerst die Klasse und rufen auf das Objekt die Methode auf. Es gibt aber auch noch eine andere Variante, die ich Ihnen nicht vorenthalten möchte:

Document doc = new Document();
ICopy copy = doc;
copy.Copy();

Listing 4.33 Zugriff auf die Methode »Copy« über eine Interface-Variable

Auch hierbei ist zunächst ein Objekt vom Typ Document notwendig. Dessen Referenz weisen wir aber anschließend einer Variablen vom Typ der Schnittstelle ICopy zu. Auf letztere wird dann die Methode Copy aufgerufen.

Mehrdeutigkeiten mit expliziter Implementierung vermeiden

Implementiert eine Klasse mehrere Schnittstellen, kann es passieren, dass in zwei oder mehr Schnittstellen ein gleichnamiges Mitglied definiert ist. Diese Mehrdeutigkeit wird durch die explizite Implementierung eines Schnittstellenmembers aus der Welt geschafft. Eine explizite Implementierung ist der vollständig kennzeichnende Name eines Schnittstellenmitglieds. Er besteht aus dem Namen der Schnittstelle und dem Bezeichner des implementierten Mitglieds, getrennt durch einen Punkt.

Nehmen wir an, in den beiden Schnittstellen ICopy und IAddress wäre jeweils eine Eigenschaft Caption definiert:

public interface ICopy {
string Caption { get; set; }
void Copy();
}
public interface IAddress {
string Caption { get; set; }
}

Listing 4.34 Mehrdeutigkeit bei Schnittstellen

In einer Klasse Document, die beide Schnittstellen aus Listing 4.34 implementiert, könnten die Methoden, wie im folgenden Codefragment gezeigt, explizit implementiert werden, um sie eindeutig den Schnittstellen zuzuordnen:

class Document : ICopy, IAddress {
void ICopy.Caption
() {
Console.WriteLine("Caption-Methode in ICopy");
}
void IAddress.Caption() {
Console.WriteLine("Caption-Methode in IAdresse");
}
[...]
}

Listing 4.35 Explizite Schnittstellenimplementierung

Es müssen nicht zwangsläufig beide Caption-Methoden explizit implementiert werden. Um eine eindeutige Schnittstellenzuordnung zu gewährleisten, würde eine explizite Implementierung vollkommen ausreichen.

Explizit implementierte Schnittstellenmember haben keinen Zugriffsmodifizierer, denn im Zusammenhang mit der expliziten Schnittstellenimplementierung ist eine wichtige Regel zu beachten:

Bei der expliziten Implementierung eines Schnittstellenmembers darf weder ein Zugriffsmodifizierer noch einer der Modifikatoren abstract, virtual, override oder static angegeben werden.

Auf die explizite Implementierung eines Schnittstellenmembers kann nur über eine Schnittstellenreferenz zugegriffen werden, wie im folgenden Codefragment gezeigt wird:

Document doc = new Document();
ICopy copy = doc;
copy.Caption = "Dokumentkopie";
IAddress adresse = doc;
adresse.Caption = "Bremen";

Listing 4.36 Aufruf eines explizit implementierten Schnittstellenmembers

Schnittstellen, die selbst Schnittstellen implementieren

Mehrere Schnittstellen lassen sich zu einer neuen Schnittstelle zusammenfassen. Das folgende Codefragment zeigt, wie die Schnittstelle ICopy die Schnittstelle ICompare implementiert:

public interface ICompare {
bool Compare(Object obj);
}
public interface ICopy : ICompare {
void Copy();
}

Listing 4.37 Schnittstelle, die selbst eine Schnittstelle implementiert

Eine Klasse, die sich die Dienste der Schnittstelle ICopy sichern möchte, muss beide Methoden bereitstellen: die der Schnittstelle ICompare und die spezifische der Schnittstelle ICopy:

class Document : ICopy {
public void Copy() {
[...]
}
public bool Compare(object obj) {
[...]
}
}

Listing 4.38 Implementierung der Schnittstelle »ICopy« aus Listing 4.37


Rheinwerk Computing - Zum Seitenanfang

4.8.4 Die Interpretation der SchnittstellenZur nächsten ÜberschriftZur vorigen Überschrift

Schnittstellen zu codieren ist sehr einfach. Da werden Sie mir zustimmen. Aber wahrscheinlich werden Sie sich nun fragen, welchen Sinn bzw. welche Aufgabe eine Schnittstelle hat. Schließlich ließen sich die Schnittstellenmember doch auch direkt in einer Klasse codieren, ohne vorher den Umweg der Implementierung eines interface-Typs gehen zu müssen.

Natürlich steckt hinter einem interface nicht die Absicht, den Programmcode unnötig komplex zu gestalten. Tatsächlich lässt sich die Existenz durch zwei Punkte rechtfertigen:

  • Mit einer Schnittstelle wird die fehlende Mehrfachvererbung ersetzt, ohne gleichzeitig deren gravierende Nachteile in Kauf nehmen zu müssen.
  • Mit einer Schnittstelle kann ein Typ vorgegeben werden, dessen exakte Typangabe nicht bekannt ist.

Der letzte Punkt ist dabei nur eine logische Konsequenz des zuerst aufgeführten. Beide Aussagen möchte ich Ihnen im Folgenden beweisen.

Schnittstellen als Ersatz der Mehrfachvererbung

Weiter oben im Listing 4.33 haben wir die folgenden beiden Anweisungen im Programmcode geschrieben:

Document doc = new Document();
ICopy copy = doc;

Kommt Ihnen das nicht bekannt vor? Sehr ähnlich sahen zwei Anweisungen aus, die wir in Abschnitt 4.3.1 geschrieben hatten:

Flugzeug flg = new Flugzeug();
Luftfahrzeug lfzg = flg;

Die beiden Anweisungen bildeten die Grundlage für die Aussage, dass Sie eine Subklassenreferenz einer Basisklassenreferenz zuweisen können. Wie das vorletzte Codefragment zeigt, können Sie einer Interface-Variablen die Referenz eines Objekts übergeben, das die entsprechende Schnittstelle implementiert. Das führt zu der folgenden Aussage:

Im Programmcode kann eine Schnittstelle genauso behandelt werden, als würde es sich um eine Basisklasse handeln.

Die daraus resultierende Konsequenz und Interpretation möchte ich am Beispiel des Projekts GeometricObjectsSolution weiter verdeutlichen. Erinnern Sie sich bitte an die Aussage, dass alle abgeleiteten Klassen gleichzeitig auch vom Typ der Basisklasse sind. Das bedeutet mit anderen Worten, bei Objekten vom Typ Circle, Rectangle, GraphicCircle und GraphicRectangle handelt es sich um geometrische Objekte. GeometricObject beschreibt demnach eine Familie geometrischer Objekte, weil die ableitenden Klassen alle Member der Basisklasse GeometricObject aufweisen.

Betrachten wir nun die beiden Klassen GraphicCircle und GraphicRectangle. Beide weisen mit der Methode Draw ein gemeinsames Merkmal auf. Wir können die Methode Draw auch über eine Schnittstelle bereitstellen, die von GraphicCircle und GraphicRectangle implementiert wird.

public interface IDraw {
void Draw();
}
public class GraphicCircle : Circle, IDraw {
[...]
public virtual void Draw() {
Console.WriteLine("Der Kreis wird gezeichnet");
}
}
public class GraphicRectangle : Rectangle, IDraw {
[...]
public virtual void Draw() {
Console.WriteLine("Das Rechteck wird gezeichnet");
}
}

Listing 4.39 Ergänzung des Beispielprogramms »GeometricObjectsSolution« um »IDraw«

Ein erster Blick auf den überarbeiteten Programmcode scheint uns eher Nachteile als Vorteile zu bescheren, denn er ist komplexer geworden. Nun betrachten Sie bitte Abbildung 4.7. Entgegen der ansonsten üblichen Darstellungsweise wird hier die Schnittstelle IDraw wie eine Basisklasse dargestellt. GeometricObject beschreibt alle geometrischen Objekte und bildet damit eine Familie. In gleicher Weise beschreibt IDraw alle Objekte, die gezeichnet werden können. Dazu gehören GraphicCircle und GraphicRectangle. Die beiden letztgenannten sind damit sogar Mitglieder von zwei ganz unterschiedlichen Familien.

Abbildung

Abbildung 4.7 Die Interpretation einer Schnittstelle als Basisklasse und deren Folgen

Nutzen können wir daraus erst ziehen, wenn wir eine weitere Klasse codieren – nennen wir sie Auto –, die ebenfalls die Schnittstelle IDraw implementiert.

public class Auto : IDraw {
[...]
public virtual void Draw() {
Console.WriteLine("Das Auto wird gezeichnet");
}
}

Listing 4.40 Die fiktiv angenommene Klasse »Auto«

Was hat nun die Klasse Auto mit beispielsweise GraphicCircle zu tun? Eigentlich nichts! Dennoch haben beide ein gemeinsames Merkmal: Objekte dieser beiden Klassen lassen sich zeichnen, weil beide dieselbe »Basis« haben und die Methode Draw implementieren.

Sie könnten nun Objekte vom Typ Auto, GraphicCircle und GraphicRectangle in ein Array vom Typ IDraw stecken und in einer Schleife die allen gemeinsame Methode Draw aufrufen, z. B.:

IDraw[] arr = new IDraw[5];
arr[0] = new GraphicCircle();
arr[1] = new GraphicRectangle();
arr[2] = new Auto();
arr[3] = new GraphicCircle();
arr[4] = new Auto();
foreach (IDraw item in arr)
item.Draw();

Listing 4.41 Gemeinsame Behandlung mehrerer Typen mit derselben Schnittstellenimplementierung

Die Laufvariable in der Schleife ist vom Typ IDraw. Auf die Referenz der Laufvariablen wird im Anweisungsblock der Schleife die Methode Draw aufgerufen. Da GraphicCircle, GraphicRectangle und Auto die Schnittstelle IDraw implementieren und das damit verbundene Vertragsverhältnis erfüllen, wird der Code fehlerfrei ausgeführt. Natürlich erfolgt der Methodenaufruf polymorph.

Nichts anderes haben wir bereits gemacht, als wir Flugzeug- und Hubschrauber-Objekte einem Array vom Typ der Basisklasse Luftfahrzeug hinzugefügt haben, um anschließend die allen gemeinsame Methode Starten aufzurufen. Hier noch einmal zum Vergleich der angesprochene Code:

Luftfahrzeug[] arr = new Luftfahrzeug[5];
arr[0] = new Flugzeug();
arr[1] = new Zeppelin();
arr[2] = new Hubschrauber();
[...]
foreach (Luftfahrzeug item in arr) {
item.Starten();
}

Anhand dieser beiden Beispiele bestätigt sich die Aussage, dass Schnittstellen eine Alternative zu der von .NET nicht unterstützten Mehrfachvererbung darstellen.

Schnittstellen als Ersatz exakter Typangaben

Nun wollen wir mehrere verschiedene geometrische Objekte miteinander vergleichen und dabei eine Liste erstellen, in der die Objekte der Größe nach sortiert sind. Als Kriterium der Größe soll uns die Fläche der Objekte dienen, so dass wir auch Rechtecke mit Kreisen vergleichen können. Wir müssen nicht unbedingt eine eigene Methode mit einem Sortieralgorithmus schreiben, wir können dabei auf Methoden zurückgreifen, die in der .NET-Klassenbibliothek zu finden sind.

Jetzt stellt sich sofort die Frage: Wie soll das denn geschehen, denn die Architekten der .NET-Klassenbibliothek wussten doch nicht, dass wir mehrere Objekte vom Typ GeometricObject einem Vergleich unterziehen wollen? Wir hätten unsere Klassen Circle, GraphicCircle, Rectangle und GraphicRectangle auch ganz anders benennen können.

Auch bei der Lösung dieses Problems spielen Schnittstellen die alles entscheidende Rolle. Ich möchte Ihnen das an einem Beispiel zeigen, in dem die zu sortierenden Objekte in einem Array zusammengefasst werden:

GeometricObject[] arr = new GeometricObject[5];
arr[0] = new Circle(34);
arr[1] = new Rectangle(10, 230);
[...]

Listing 4.42 Zusammenfassen mehrerer Objekte in einem Array

Mit Hilfe der Klasse Array, die uns die statische Methode Sort zur Verfügung stellt, können wir unsere geometrischen Objekte sortieren. Die Methode ist vielfach überladen. Für uns ist die folgende Überladung von Interesse:

public static void Sort(Array array, IComparer comparer)

Dem ersten Parameter übergeben wir das zu sortierende Array, in unserem Fall also arr. Der zweite Parameter ist vom Typ der Schnittstelle IComparer. Natürlich können Sie dem Methodenaufruf keine Instanz vom Typ IComparer übergeben, da Schnittstellen nicht instanziierbar sind. So ist die Typangabe des zweiten Parameters auch nicht zu verstehen. Stattdessen verlangt der zweite Parameter lediglich, dass das ihm übergebene Argument ein Objekt ist, das die Schnittstelle IComparer implementiert – egal, ob das Objekt vom Typ DemoClass, Circle, Auto oder HalliGalli ist.

Denken Sie noch einmal an die Aussagen in diesem Kapitel: Das Objekt einer abgeleiteten Klasse ist gleichzeitig auch ein Objekt der Basisklasse. Außerdem kann eine Schnittstelle wie eine Basisklasse betrachtet werden. Dadurch, dass ein Parameter vom Typ einer Schnittstelle definiert ist, wird uns lediglich vorgeschrieben, dass die Member der Schnittstelle von der Klasse implementiert sind. Im Fall von IComparer handelt es sich um die Methode Compare, die zwei Objekte des angegebenen Arrays miteinander vergleicht. Welche weiteren Member sich noch in der Klasse tummeln, die ICompare implementiert, interessiert in diesem Zusammenhang nicht.

Sehen wir uns nun die Definition der Schnittstellenmethode an:

int Compare(Object x, Object y)

Die Methode Sort der Klasse Array kann natürlich nicht wissen, nach welchen Kriterien zwei zu vergleichende Objekte als größer oder kleiner eingestuft werden sollen. Dies ist die Aufgabe des Codes in der Schnittstellenmethode, den Sie schreiben müssen. Anhand des Rückgabewerts (siehe auch Tabelle 4.1) werden die Objekte im Array nach einem internen Algorithmus in Sort umgeschichtet, und zwar so lange, bis alle Array-Elemente in der richtigen Reihenfolge stehen.

Tabelle 4.1 Die Rückgabewerte der Methode »Compare« des Interfaces »IComparer«

Wert Bedingung

< 0

x ist kleiner als y

0

x und y sind gleich groß

> 0

x ist größer als y

Das folgende Beispielprogramm zeigt das komplette Coding. Es enthält mit ComparerClass eine separate Klasse, die nur der Implementierung der Schnittstelle IComparer dient. Man könnte diese Klasse auch als »Vergleichsklasse« bezeichnen.

// Beispiel: ..\Kapitel 4\Sorting 
class Program {
static void Main(string[] args) {
GeometricObject[] arr = new GeometricObject[5];
arr[0] = new Circle(34);
arr[1] = new Rectangle(10, 230);
arr[2] = new GraphicCircle(37);
arr[3] = new Circle(20);
arr[4] = new GraphicRectangle(12,70);
Array.Sort(arr, new ComparerClass());
foreach (GeometricObject item in arr)
Console.WriteLine(item.ToString());
Console.ReadLine();
}
}
class ComparerClass : IComparer {
public int Compare(object x, object y) {
return ((GeometricObject)x).Bigger((GeometricObject)y);
}
}

Listing 4.43 Programmcode des Beispielprogramms »Sorting«

In der Methode Compare kommt uns die Methode Bigger zugute, die in der Klasse GeometricObject enthalten ist und zwei geometrische Objekte miteinander vergleicht. Bigger liefert genau den Rückgabewert, den wir der Methode Sort zur Weiterverarbeitung übergeben können.

Kommen wir noch einmal zurück zu der Behauptung, dass mit einer Schnittstelle ein Typ vorgegeben werden kann, dessen exakte Typangabe nicht bekannt ist. Genau das macht die Methode Sort. Sie kennt zwar nicht den genauen Typ, der ihr übergeben wird, aber sie kann sich darauf verlassen, dass das Objekt garantiert die Methode Compare implementiert, weil ein Objekt vom Typ der Schnittstelle IComparer im zweiten Parameter vorgeschrieben ist. Da IComparer zum .NET Framework gehört, ist diese Schnittstelle beiden Parteien, der Anwendung und der Sort-Methode bekannt – beide können darüber kommunizieren, sich gegenseitig austauschen.


Rheinwerk Computing - Zum Seitenanfang

4.8.5 Änderungen am Projekt »GeometricObjects«Zur vorigen Überschrift

Die Schnittstelle IDraw, von der die beiden Klassen GraphicCircle und GraphicRectangle durch Implementierung profitieren, haben wir bereits erörtert. Aber es gibt noch eine weitere Möglichkeit, sinnvoll eine Schnittstelle einzusetzen. Den Hinweis dazu liefert die einfach parameterlose Methode Sort der Klasse Array. Diese Methode erwartet nur das zu sortierende Array, schreibt aber eine Schnittstelle vor, die von den zu sortierenden Objekten unterstützt werden muss. Es handelt sich um das Interface IComparable, die die Methode CompareTo vorschreibt, die wie folgt beschrieben wird:

int CompareTo(Object obj);

Diese Methode erinnert sehr stark an die Instanzmethode Bigger in GeometricObject. Es macht nicht allzu viel Mühe, die derzeitige Implementierung

public virtual int Bigger(GeometricObject @object) {
if (@object == null || GetArea() > @object.GetArea()) return 1;
if (GetArea() < @object.GetArea()) return -1;
return 0;
}

Listing 4.44 Aktuelle Implementierung der Methode »Bigger«

umzuschreiben. Im Wesentlichen müssen wir nur den Typ des Parameters an die Vorschrift der Schnittstelle anpassen und natürlich den Methodenbezeichner ändern. Innerhalb der Methode ist eine Konvertierung des Übergabeparameters vom Typ Object in GeometricObject notwendig. Dazu bietet sich unter anderem der as-Operator an, der null zurückliefert, falls die Konvertierung nicht möglich ist.

Das einzige Problem, das gemeistert werden muss, ist der Fall des Scheiterns der Konvertierung. Theoretisch könnte man einen festgeschriebenen Fehlerwert an den Aufrufer zurückliefern, aber dieser würde mit hoher Wahrscheinlichkeit zu einer Fehlinterpretation seitens des Aufrufers führen. Um einen zweckmäßigen, lauffähigen Code zu haben, ist es optimal, in diesem Fall eine Ausnahme auszulösen. Nehmen Sie das Auslösen der Exception zunächst einmal so hin, wie es im Code unten gemacht wird. In Kapitel 7 werden wir uns noch näher damit beschäftigen.

public virtual int CompareTo(Object @object) {
GeometricObject geoObject = @object as GeometricObject;
if (geoObject != null) {
if (GetArea() < geoObject.GetArea()) return -1;
if (GetArea() == geoObject.GetArea()) return 0;
return 1;
}
// Auslösen einer Ausnahme
throw new ArgumentException("Es wird der Typ 'GeometricObject' erwartet.");
}

Listing 4.45 Ändern der ursprünglichen Methode »Bigger« in der Klasse »GeometricObject«

Natürlich dürfen wir nicht vergessen, die Klasse GeometricObject um die Implementierung der Schnittstelle IComparable zu ergänzen, also:

public abstract class GeometricObject : IComparable

Sie finden die vollständige Zusammenfassung des Codes auf der Buch-DVD unter ...Beispiele\Kapitel 4\GeometricObjectsSolution_4.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Visual C# 2012

Visual C# 2012
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Rheinwerk-Shop: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: C/C++






 C/C++


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern