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

Inhaltsverzeichnis
Vorwort zur 5. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Weitere .NET-Datentypen
7 Weitere Möglichkeiten von C#
8 Auflistungsklassen (Collections)
9 Fehlerbehandlung und Debugging
10 LINQ to Objects
11 Multithreading und die Task Parallel Library (TPL)
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 Einige wichtige .NET-Klassen
15 Projektmanagement und Visual Studio 2010
16 XML
17 WPF – Die Grundlagen
18 WPF-Containerelemente
19 WPF-Steuerelemente
20 Konzepte der WPF
21 Datenbindung
22 2D-Grafik
23 ADO.NET – verbindungsorientierte Objekte
24 ADO.NET – Das Command-Objekt
25 ADO.NET – Der SqlDataAdapter
26 ADO.NET – Daten im lokalen Speicher
27 ADO.NET – Aktualisieren der Datenbank
28 Stark typisierte DataSets
29 LINQ to SQL
30 Weitergabe von Anwendungen
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual C# 2010 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2010

Visual C# 2010
geb., mit DVD
1295 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1552-7
Pfeil 4 Vererbung, Polymorphie und Interfaces
Pfeil 4.1 Die Vererbung
Pfeil 4.1.1 Basisklassen und abgeleitete Klassen
Pfeil 4.1.2 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 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 Implizite Typumwandlung von Objektreferenzen
Pfeil 4.3.2 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 »Klassische« Methodenimplementierung
Pfeil 4.4.2 Abstrakte Methoden
Pfeil 4.4.3 Virtuelle Methoden
Pfeil 4.4.4 Versiegelte Methoden
Pfeil 4.4.5 Überladen einer Basisklassenmethode
Pfeil 4.4.6 Statische Member und Vererbung
Pfeil 4.5 Das Projekt »GeometricObjectsSolution« ergänzen
Pfeil 4.6 Hat-ein(e)-Beziehungen
Pfeil 4.6.1 Innere Klassen (Nested Classes)
Pfeil 4.7 Interfaces (Schnittstellen)
Pfeil 4.7.1 Schnittstellendefinition
Pfeil 4.7.2 Schnittstellenimplementierung
Pfeil 4.7.3 Interpretation der Schnittstellen
Pfeil 4.8 Das Zerstören von Objekten – der Garbage Collector
Pfeil 4.8.1 Arbeitsweise des Garbage Collectors
Pfeil 4.8.2 Expliziter Aufruf des Garbage Collectors
Pfeil 4.8.3 Der Destruktor
Pfeil 4.8.4 Die »IDisposable«-Schnittstelle
Pfeil 4.8.5 Ergänzungen in den Klassen »Circle« und »Rectangle«


Galileo Computing - Zum Seitenanfang

4.8 Das Zerstören von Objekten – der Garbage Collector Zur nächsten ÜberschriftZur vorigen Überschrift


Galileo Computing - Zum Seitenanfang

4.8.1 Arbeitsweise des Garbage Collectors Zur 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 freizugeben. Stattdessen sorgt eine spezielle Komponente der Common Language Runtime (CLR) für die notwendige Speicherbereinigung: der Garbage Collector.

Der Garbage Collector (GC) 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.


Hinweis

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. Handelt es sich um Instanzfelder, werden die Daten beim Zerstören des Objekts aufgegeben.


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 Heaps kopiert und die entsprechenden Verweise auf diese Objekte aktualisiert.

Damit nicht unnötig viel Zeit vom Garbage Collector beansprucht wird, ist der Speicherbereinigungsprozess ein ziemlich ausgeklügeltes System. Unter anderem werden die Objekte in 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 werden 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 kümmert und nur dann die Objekte der Generation 1 und eventuell auch die der Generation 2 erfasst, wenn die freigegebenen Ressourcen anschließend immer noch nicht ausreichend sind.


Galileo Computing - Zum Seitenanfang

4.8.2 Expliziter Aufruf des Garbage Collectors Zur nächsten ÜberschriftZur vorigen Überschrift

Sie können zwar 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 auf, wobei Sie das Objekt übergeben, das abgefragt werden soll:


int generation = GC.GetGeneration(kreis);


Galileo Computing - Zum Seitenanfang

4.8.3 Der Destruktor Zur 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 Sie 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 folgen der Klassenbezeichner mit dem obligatorischen runden Klammerpaar und zum Schluss der Anweisungsblock. Ein Destruktor hat weder einen Zugriffsmodifizierer noch eine Parameterliste, und es wird auch kein Rückgabetyp angegeben.

Der C#-Compiler wandelt den Destruktor in eine Überschreibung der von Object geerbten Methode Finalize um und markiert das Objekt gleichzeitig als finalisierungsbedürftig.

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 finalisierungsbedürftiges 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 aufwendig und muss mit einer Leistungseinbuße bezahlt werden. Sie sollten daher nur dann einen Destruktor bereitstellen, wenn er tatsächlich benötigt wird.


Galileo Computing - Zum Seitenanfang

4.8.4 Die »IDisposable«-Schnittstelle Zur 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, auch 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.


Hinweis

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.


Der 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 und im Destruktor freigegeben werden.
  • Ruft der Code Dispose auf einem Objekt auf, werden die Fremdressourcen in Dispose freigegeben. In diesem Fall ist der Aufruf des Destruktors während des Finalisierungsprozesses unnötig und sollte vermieden werden, um einerseits unnötige Performance-Verluste zu vermeiden und andererseits 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 aufgerufen 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 durch den Aufruf der Dispose-Methode dieser Objekte). Der Grund ist darin zu finden, 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 auch 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);
  }
}

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 eventuell vorhandener verwalteter Objekte codiert.

Wie bereits oben beschrieben wurde, dürfen verwaltete Ressourcen während der Destruktorausführung nicht mehr freigegeben werden. Daher wird die überladene Dispose-Methode vom Destruktor durch Übergabe von false und die parameterlose Dispose-Methode durch Ü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, um 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 obj = new Demo()) {
  obj.AnyOperation();
}

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.


Galileo Computing - Zum Seitenanfang

4.8.5 Ergänzungen in den Klassen »Circle« und »Rectangle« topZur vorigen Überschrift

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

Wir implementieren daher in beiden Klassen die IDisposable-Schnittstelle und den jeweiligen Destruktor. Nachfolgend wird das exemplarisch für die Klasse Circle gezeigt.


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

Obwohl nun der Destruktor und Dispose dafür sorgen, den Objektzähler bei der Freigabe eines Objekts zu reduzieren, müssen Sie sich der Tatsache 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.


Hinweis

Das komplette Beispiel des Projekts GeometricObjects finden Sie auf der Buch-DVD unter Beispiele\Kapitel 4\GeometricObjectsSolution_3.




Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual C# 2010

Visual C# 2010
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: C++ Handbuch






 C++ Handbuch


Zum Katalog: C/C++






 C/C++


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2010
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