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.9 Das Zerstören von Objekten – der »Garbage Collector«Zur nächsten Überschrift


Rheinwerk Computing - Zum Seitenanfang

4.9.1 Die Arbeitsweise des Garbage CollectorsZur nächsten ÜberschriftZur vorigen Überschrift

Ein Konstruktor wird aufgerufen, wenn das Objekt einer Klasse erzeugt wird. Damit beginnt der Lebenszyklus des Objekts. Objekte benötigen Speicherressourcen für ihre Daten. Solange ein Objekt noch referenziert wird, müssen die Daten im Speicher bleiben. Verliert ein Objekt seine letzte Referenz oder wird der Objektreferenz null zugewiesen, beispielsweise mit

Circle kreis = new Circle();
[...]
kreis = null;

können die vom Objekt beanspruchten Speicherressourcen freigegeben werden. Das geschieht jedoch nicht automatisch. Vielmehr beanspruchen die Objekte weiterhin Speicher, obwohl sie vom laufenden Programm nicht mehr genutzt werden können. Unter .NET ist es, im Gegensatz zu anderen Programmierumgebungen, nicht möglich, mittels Programmcode den Speicher eines Objekts freizugeben. Stattdessen sorgt eine spezielle Komponente der Common Language Runtime (CLR) für die notwendige Speicherbereinigung: der Garbage Collector (GC).

Der Garbage Collector arbeitet nichtdeterministisch, das heißt, es kann nicht vorhergesagt werden, wann der Garbage Collector aktiv wird. Damit stellt sich sofort die Frage, nach welchen Kriterien der GC seine Arbeit aufnimmt und eine Speicherbereinigung durchführt.

Als selbstständige Ausführungseinheit (Thread) genießt der GC keine hohe Priorität und kann erst dann den Prozessor in Anspruch nehmen, wenn die Anwendung beschäftigungslos ist. Theoretisch könnte das bedeuten, dass eine viel beschäftigte Anwendung dem GC keine Chance lässt, jemals aktiv zu werden. Dem ist tatsächlich so, es gibt aber eine wichtige Einschränkung: Noch bevor den Speicherressourcen der Anwendung die »Luft ausgeht«, ist die zweite Bedingung erfüllt, um die Speicherbereinigung mit dem GC anzustoßen. Der Garbage Collector wird also spätestens dann nach allen aufgegebenen Objekten suchen und deren Speicherplatz freigeben, wenn die Speicherressourcen knapp werden. Die dritte Situation ist gegeben, wenn die Anwendung geschlossen wird. Auch in diesem Moment wird der Garbage Collector aktiv. Das hängt damit zusammen, dass dies die letzte Chance darstellt, von einem Objekt beanspruchte Fremdressourcen freizugeben.

Der Garbage Collector ist nur im Zusammenhang mit Referenztypen von Bedeutung. Daten, die auf Wertetypen basieren und ihr Dasein auf dem Stack fristen, hören automatisch auf zu existieren, wenn die Methode verlassen wird, in der sie als lokale Variablen definiert sind.

Die Arbeit des Garbage Collectors ist sehr zeitintensiv, da sich im Hintergrund sehr viele einzelne Aktivitäten abspielen. Dabei werden beispielsweise Objekte in andere Bereiche des Heap kopiert und die entsprechenden Verweise auf diese Objekte aktualisiert.

Damit nicht unnötig viel Zeit vom Garbage Collector beansprucht wird, ist der Speicherbereinigungsprozess ein ausgesprochen ausgeklügeltes System. Unter anderem werden die Objekte auf drei separate Speicherbereiche aufgeteilt, die als Generationen bezeichnet werden. Das Konzept der Speicherbereinigung unter Berücksichtigung der Generationen ist dabei wie folgt:

  • Generation 0 bleibt den neuen Objekten vorbehalten. Ist dieser Speicherbereich voll, wird der Garbage Collector aktiv und gibt die Speicherressourcen der nicht mehr benötigten Objekte frei. Objekte der Generation 0, die weiter referenziert werden, werden in den Bereich der Generation 1 kopiert.
  • Sollte der erste Vorgang nicht genügend Speicherressourcen freigesetzt haben, erfasst der Garbage Collector auch den Bereich der Objekte, die der Generation 1 zugeordnet sind. Objekte, die dort nicht mehr referenziert werden, werden gelöscht, alle anderen in den Bereich der Generation 2 verschoben.
  • Reicht auch danach der Speicher immer noch nicht aus, wird der Garbage Collector auch alle nicht mehr benötigten Objekte der Generation 2 löschen.

Die Idee, die hinter dem Prinzip der Generationen steckt, beruht darauf, dass die meisten Objekte nur für eine relativ kurze Zeitspanne benötigt werden. Je »älter« aber ein Objekt ist, umso größer ist die Wahrscheinlichkeit, dass es auch weiterhin benötigt wird. Das ist der Grund, warum der Garbage Collector sich zuerst um die Objekte der Generation 0 (also verhältnismäßig »junge« Objekte) kümmert und nur dann die der Generation 1 und eventuell auch die der Generation 2 erfasst, wenn die freigegebenen Ressourcen anschließend immer noch nicht ausreichend sind.


Rheinwerk Computing - Zum Seitenanfang

4.9.2 Expliziter Aufruf des Garbage CollectorsZur nächsten ÜberschriftZur vorigen Überschrift

Sie können mittels Code nicht die Speicherressourcen eines einzelnen Objekts freigeben, aber immerhin können Sie veranlassen, dass der Garbage Collector aktiv wird. Dazu wird die statische Methode Collect der Klasse GC aufgerufen:

GC.Collect();

Dieser Aufruf veranlasst den Garbage Collector, alle drei Generationen zu bereinigen. Sie können den Aufruf optimieren, indem Sie der Methode mitteilen, welche die letzte noch zu bereinigende Generation sein soll. Mit

GC.Collect(1);

erreichen Sie, dass die verwaisten Objekte der Generationen 0 und 1 zerstört werden.

Die Klasse GC eignet sich auch, um in Erfahrung zu bringen, welcher Generation ein bestimmtes Objekt zugeordnet ist. Rufen Sie dazu die statische Methode GetGeneration unter Übergabe des abzufragenden Objekts auf:

int generation = GC.GetGeneration(kreis);

Rheinwerk Computing - Zum Seitenanfang

4.9.3 Der DestruktorZur nächsten ÜberschriftZur vorigen Überschrift

Der Garbage Collector sorgt dafür, dass der Speicherplatz nicht mehr referenzierter Objekte freigegeben wird. Es gibt aber auch Objekte, die ihrerseits Referenzen auf externe Fremdressourcen halten. Dabei kann es sich zum Beispiel um Datenbankverbindungen oder geöffnete Dateien handeln. Solche Fremdressourcen werden vom Garbage Collector nicht verwaltet und konsequenterweise auch nicht freigegeben. In solchen Fällen sind die Objekte selbst dafür verantwortlich.

Ein zweistufiges Modell unterstützt bei der Freigabe der Fremdressourcen:

  • der Destruktor
  • die Schnittstelle IDisposable

Widmen wir uns zuerst dem Destruktor, dessen Syntax wie folgt lautet:

~Klassenbezeichner() { [...] }

Eingeleitet wird der Destruktor mit dem Tildezeichen, danach folgt der Klassenbezeichner mit dem obligatorischen runden Klammerpaar und zum Schluss der Anweisungsblock. Ein Destruktor hat weder einen Zugriffsmodifizierer noch eine Parameterliste oder die Angabe eines Rückgabetyps. Der C#-Compiler wandelt den Destruktor in eine Überschreibung der von Object geerbten Methode Finalize um und markiert das Objekt gleichzeitig als »finalisierungsbedürftig«.

Bei der Instanziierung eines Objekts werden die Konstruktoren beginnend bei Object über den Konstruktor der davon direkt abgeleiteten Klasse bis hin zu der Klasse, von der das Objekt erstellt werden soll, durchlaufen (siehe Abbildung 4.3). Bei den Destruktoren dreht sich dieser Sachverhalt genau um: Es wird zuerst der Destruktor der am meisten abgeleiteten Klasse aufgerufen und abgearbeitet, danach der der direkten Basisklasse und so weiter – bis hin zum Destruktor von Object (siehe Abbildung 4.8).

Abbildung

Abbildung 4.8 Verkettung der Destruktoraufrufe

Aus dem Programmcode heraus kann der Destruktor nicht aufgerufen werden. Das kann nur der Garbage Collector bei seinen Aufräumarbeiten. Trifft der Garbage Collector auf ein verwaistes und zur Finalisierung anstehendes Objekt, erzeugt er einen neuen Objektverweis und stellt danach das Objekt in eine Finalisierungswarteschlange. Ein separater Thread arbeitet diese Warteschlange ab, ruft die Methode Finalize auf und markiert das Objekt. Erst beim nächsten Speicherbereinigungsprozess wird das Objekt komplett entfernt und dessen Speicherplatz freigegeben.

Der gesamte Vorgang ist sehr aufwändig und muss mit einer Leistungseinbuße bezahlt werden. Sie sollten daher nur dann einen Destruktor bereitstellen, wenn er tatsächlich benötigt wird.


Rheinwerk Computing - Zum Seitenanfang

4.9.4 Die »IDisposable«-SchnittstelleZur nächsten ÜberschriftZur vorigen Überschrift

Mit einem Destruktor sind zwei gravierende Nachteile verbunden:

  • Wenn ein Destruktor implementiert ist, kann nicht exakt vorherbestimmt werden, wann er vom Speicherbereinigungsprozess ausgeführt wird.
  • Ein Destruktor kann nicht explizit aus dem Code heraus aufgerufen werden.

Wie Sie bereits wissen, werden die Aufräumarbeiten angestoßen, wenn durch die Beschäftigungslosigkeit einer laufenden Anwendung der niedrig priorisierte Thread des Garbage Collectors seine Arbeit aufnimmt oder sich die Speicherressourcen verknappen. Tatsächlich sind sogar Situationen denkbar, die niemals zum Destruktoraufruf führen – denken Sie nur an den Absturz des Rechners. Folglich kann auch nicht garantiert werden, dass der GC überhaupt jemals seine ihm zugedachte Aufgabe verrichtet. Wenn ein Objekt aber kostspielige oder begrenzte Ressourcen beansprucht, muss sichergestellt sein, dass diese so schnell wie möglich wieder freigegeben werden.

Um dem Problem zu begegnen, können Sie zusätzlich zum Destruktor eine öffentliche Methode implementieren, die der Benutzer der Klasse explizit aufrufen kann. Grundsätzlich kann dazu jede beliebige Methode geschrieben werden, jedoch empfiehlt es sich, die Schnittstelle IDisposable zu implementieren, die die Methode Dispose vorschreibt.

Es ist unüblich, anstelle der Methode Dispose eine Methode Close zu definieren. Trotzdem weisen viele Klassen in der .NET-Klassenbibliothek die Methode Close auf, in der aber in der Regel Dispose aufgerufen wird.

Destruktor und Dispose müssen miteinander harmonieren, denn sie sind voneinander abhängig. Deshalb sollten Sie bei der Codierung von Destruktor und Dispose auf die folgenden Punkte achten:

  • Alle Fremdressourcen sollten von Dispose oder spätestens im Destruktor freigegeben werden.
  • Wird Dispose auf ein Objekt aufgerufen, ist der Aufruf des Destruktors während des Finalisierungsprozesses unnötig und sollte vermieden werden, um unnötige Performanceverluste zu vermeiden und auch mögliche Fehlerquellen im Keim zu ersticken. Dazu wird in Dispose die statische Methode SuppressFinalize der Klasse GC unter Angabe des betroffenen Objekts aufgerufen. Die Folge ist, dass das Objekt nicht mehr in die Finalisierungswarteschlange gestellt und der Destruktor nicht ausgeführt wird.
  • Für den Fall, dass die Methode Dispose nicht explizit aufgerufen wird, sollte der Aufruf aus dem Destruktor heraus erfolgen.
  • Im Destruktor dürfen nur externe, nicht verwaltete Ressourcen freigegeben werden. Das bedeutet andererseits, dass Felder, die auf Referenztypen basieren, nur in Dispose freigegeben werden dürfen (z. B. durch Setzen auf null oder den Aufruf der Dispose-Methode dieser Objekte). Der Grund dafür ist, dass beim Aufruf von Dispose noch alle Objekte über einen Verweis erreichbar sind, bei der Ausführung des Destruktors jedoch nicht mehr.
  • Möglicherweise sollte die Methode Dispose der Basisklasse aufgerufen werden.
  • Sie sollten sicherstellen, dass Dispose bei einem mehrfachen Aufruf ohne Fehler reagiert.

Ein Codemuster, das den Anforderungen der genannten Punkte genügt, wird üblicherweise wie folgt implementiert:

public class Demo : IDisposable {
bool disposed = false;
public void Dispose() {
// wird nur beim ersten Aufruf ausgeführt
if (!disposed) {
Dispose(true);
GC.SuppressFinalize(this);
disposed = true;
}
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
// Freigabe verwalteter Objekte
}
// Freigabe von Fremdressourcen
}
// Destruktor
~Demo() {
Dispose(false);
}
}

Listing 4.46 Pattern zur Implementierung von »IDisposable« und Destruktor

Neben der parameterlosen Methode Dispose, die aus der Schnittstelle IDisposable stammt, und dem Konstruktor enthält die Klasse eine zweite, überladene Dispose-Methode. In dieser ist die Freigabe der Fremdressourcen und die Freigabe etwaig vorhandener verwalteter Objekte codiert.

Wie bereits oben beschrieben, dürfen verwaltete Ressourcen während der Destruktorausführung nicht mehr freigegeben werden. Daher wird die überladene Dispose-Methode vom Destruktor unter Übergabe von false, aus der parameterlosen Dispose-Methode unter Übergabe von true aufgerufen. Der Wert des booleschen Parameters wird ausgewertet und dazu benutzt, festzustellen, um welchen Aufrufer es sich handelt. Nach der Sondierung werden Fremdressourcen und verwaltete Ressourcen entsprechend bereinigt.

Die »using«-Anweisung zur Zerstörung von Objekten

C# stellt eine alternative Möglichkeit bereit, ein Objekt schnellstmöglich zu zerstören. Es handelt sich hierbei um das Schlüsselwort using, das in diesem Fall nicht als Direktive, sondern als Anweisung eingesetzt wird.

using (Demo @object = new Demo()) {
@object.DoSomething();
}

Im Ausdruck wird ein Objekt instanziiert, auf dem nach dem Verlassen des Anweisungsblocks automatisch die Dispose-Methode aufgerufen wird. Die Klasse, die im Ausdruck instanziiert wird, muss nur eine Bedingung erfüllen: Sie muss die Schnittstelle IDisposable implementieren.


Rheinwerk Computing - Zum Seitenanfang

4.9.5 Die Ergänzungen in den Klassen »Circle« und »Rectangle«Zur vorigen Überschrift

Zum Abschluss der Betrachtungen zur Objektzerstörung sollen noch die Klassen Circle und Rectangle überarbeitet werden. Bisher ist es nämlich noch immer so, dass die Objektzähler zwar erhöht, aber bei Zerstörung eines Objekts nicht reduziert werden.

Wir implementieren daher in den genannten Klassen die IDisposable-Schnittstelle und den jeweiligen Destruktor. Nachfolgend wird das exemplarisch anhand der Klasse Circle gezeigt.

public class Circle : GeometricObject, IDisposable {
private bool disposed;
public void Dispose() {
if (!disposed) {
Circle._CountCircles--;
GeometricObject._CountGeometricObjects--;
GC.SuppressFinalize(this);
disposed = true;
}
}
~Circle() {
Dispose();
}
}

Listing 4.47 Destruktor und »Dispose« in der Klasse »Circle«

Bei der Bereitstellung der Dispose-Methode in Circle und Rectangle müssen wir beachten, auch den allgemeinen Zähler in GeometricObject zu reduzieren. Dazu ist in Dispose die Anweisung

GeometricObject._CountGeometricObjects--;

notwendig.

Obwohl nun Destruktor und Dispose dafür sorgen, den Objektzähler bei Freigabe eines Objekts zu reduzieren, müssen Sie sich darüber bewusst sein, dass Sie zu keinem Zeitpunkt garantieren können, dass der Zähler den richtigen Stand hat. Sie bleiben weiter darauf angewiesen, dass die Dispose-Methode explizit aufgerufen wird oder der Garbage Collector aktiv wird.

Das komplette Beispiel des Projekts GeometricObjects mit allen Änderungen, die sich im Laufe dieses Kapitels ergeben haben, finden Sie auf der Buch-DVD unter ...\Beispiele\Kapitel 4\GeometricObjectsSolution_5.



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