C#: Using und IDisposable verstanden

Das Schlüsselwort using nimmt unter C# eine Sonderstellung ein, da es sowohl als Direktive wie auch als Befehl verwendet werden kann. codingfreaks zeigt die Unterschiede und die Anwendungen.

Der ein oder andere C#-Entwickler wird vielleicht schon darüber gestolpert sein, dass das using-Schlüsselwort im C#-Sprachumfang doppelt belegt ist.

Die using-Direktive

Die wohl bekannteste Funktion von using ist das Einbinden von Namensräumen innerhalb eines Codebereiches:

using System.Windows.Forms;

Nach der obigen Codezeile können nun Objekte aus dem Namensraum System.Windows.Forms ohne Vorranstellen des Namensraums genutzt werden. Insofern ähnelt dies dem Verhalten von include in C(++) oder z.B. auch PHP. Folgender C#-Code wäre im folgenden gültig:

// ohne using:
// System.Windows.Forms.Form frmShow = new System.Windows.Forms.Form();
// mit using:
Form frmShow = new Form();

Was auch bei dieser Nutzung nicht jedem Entwickler unbedingt bekannt ist, ist der Umstand, dass dies nicht zwingend im Kopf einer Klassendatei zu geschehen hat. So ist folgender Code durchaus gültig:

// Dieses using gilt für die gesamte Datei
using System.Windows.Forms;

public class Foo
{
	// Dieses using ist nur innerhalb von Foo gültig!
	using System.Text;
}

public class Bar
{
	// hier steht nur der Namespace System.Windows.Forms bereit!
}

Das Nutzen einer Code-Datei für mehrere Klassen ist unter C# durchaus erlaubt, wenn es auch – gerade in größeren Projekten – nicht ohne weiteres zu empfehlen ist! Insofern ist der Nutzen dieses Verhaltens der using-Direktive (man spricht in solchen Fällen von sog. scopes (Gültigkeitsbereichen) für Variablen bzw. Deklarationen) zumindest diskussionswürdig. Wissen sollte man es trotzdem.

Der using-Block

Eine weit weniger bekannte und wahrscheinlich auch deshalb seltener genutzte Variante von using ist der sog. using-Block. Um diesen zu verstehen, müssen wir kurz ein wenig tiefer in das .NET-Klassensystem eintauchen.

IDisposable

Von Beginn an liefert das .NET Framework eine Schnittstelle IDisposable mit, die im wesentlichen nur die Methode Dispose() bindend vereinbart. D.h., Klassen, die IDisposable einbinden, müssen eine Dispose()-Methode bereitstellen. Im Hintergrund passier allerdings viel mehr. Der garbage collector (GC) der common language runtime (CLR) ist bekanntlich ein ständig laufender Hintergrundprozess bei .NET-Anwendungen, der nicht mehr benötigte Objektinstanzen vom Speicher entfernt. Es ist nicht definitiv vorherzusehen geschweige denn zu steuern, wann dieser Prozess welche Objekte zerstört.

Durch Nutzung von IDisposable in eigenen Klassen kann nun innerhalb der Dispose()-Methode Code eingefügt werden, der kurz vor dem Zerstören des Objektes ausgeführt werden soll und z.B. dafür sorgt, dass auch wirklich alle ggf. noch vorhandenen Ressourcen abgetrennt werden. Ein Beispiel könnte eine Klasse sein, die ständigen Zugriff auf eine Datei hat und diesen beim Zerstören freigeben soll.

Bei der Verwendung von IDisposable.Dispose() sollten allerdings einige Besonderheiten bedacht werden:

  • Es sollte verhindert werden, dass Dispose() im Destruktor aufgerufen wird.
  • Der Disposed-Zustand sollte beim Zugriff auf kritische Ressourcen ständig geprüft werden.
  • Innerhalb der Dispose()-Methode sollte GC.SupressFinalize() aufgerufen werden, um zu verhindern, dass das jeweilige Objekt nochmals an den GC übergeben wird.
  • Managed und Unmanaged Objekte sind unterschiedlich zu behandeln.

Dazu ein Beispiel:

public class MyDisposableClass : IDisposable
{

    private bool _isDisposed;

    /// <summary>
    /// Default destructor calling <see cref="Dispose"/>.
    /// </summary>
    ~MyDisposableClass()
    {
        Dispose(false);
    }

    /// <inheritdoc cref="IDisposable"/>.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
  
    /// <summary>
    /// Is called to handle disposal internally.
    /// </summary>
    /// <param name="disposing"><c>true</c> if the call originated in the internal <see cref="Dispose"/>.</param>
    protected virtual void Dispose(bool disposing)
    {
        if (_isDisposed)
        {
            // method was called before
            return;
        }
        if (disposing)
        {
            // TODO Cleanup managed ressources here.
        }
        // TODO Cleanup unmanaged resources here
        _isDisposed = true;
    }
}

Der Beispielcode enthält keinerlei Objekte, die freigegeben werden müssen, bleibt dafür aber übersichtlicher. Den Code selbst kann man durchaus als Entwurfsmuster für eigene IDisposable-Klassen verwenden.

Ein Schlüsselelement ist die Überladung der Dispose()-Methode für diese und ggf. abgeleitete Klassen. Dieser objektorientierte Ansatz stellt sicher, dass ein kaskadierendes Dispose() möglich wird, wenn wir von dieser Klasse erben. In diesem Fall sollte jede geerbte Instanz das Dispose() der Vater-Instanz aufrufen, sodass die gesamte Objekt-Hierarchie dem GC zugeführt wird.

An dieser Stelle soll nochmals die Codezeile Nr. 20 hervorgehoben werden, die die statische Methode SuppressFinalize() der Framework-Klasse GC nutzt, um zu verhindern, dass diese Instanz versehentlich mehrfach an den GC übergeben wird, was einmal aus Performance-Sicht eher suboptimal wäre und andererseit zu schweren Problemen bei unmanaged Objekten, wie Zeigern (NullPointerException) führen kann.

Abschließend sei noch bemerkt, dass eine Suche mit dem .NET Reflector in den Standard-.NET-Klassen ein paar Hundert Klassen zu Tage befördert, die IDisposable implementieren.

IDisposable und using

Doch was hat all das mit using zu tun? Diese Frage ist relativ schnell beantwortet. Wir greifen dazu auf unser Beispiel von oben zurück. Damit das Dispose() einer Instanz von MyDisposableClass auch aufgerufen wird, bedarf es eines ähnlichen Codes wie:

MyDisposableClass disThis = new MyDisposableClass();
// wir tun etwas mit der Instanz disThis
disThis.Dispose();

Wie wir sicherlich alle aus unseren eigenen Projekten wissen, wird aber gerade Zeile 3 gern mal vergessen oder übersehen, wenn sie halt nicht Zeile 3 sondern Zeile 356 ist. Daher kann man als Äquivalent folgendes schreiben:

using (MyDisposableClass disThis = new MyDisposableClass())
{
	// tue etwas mit der Instanz disThis
}

Durch die Nutzung von using wird also ein Gültigkeitsbereich für disThis festgelegt. Sobald die schließende Klammer des using-Blocks erreicht ist, wird automatisch Dispose() aufgerufen und kann somit nicht mehr vergessen werden. Ein solche using-Block kann wohlgemerkt nur mit IDisposable-Objekten erstellt werden! Ein weitere Vorteil ist, dass bereits beim Lesen von Zeile 1 jedem klar ist, dass er nicht mehr darauf achten muss, ob Dispose() aufgerufen wird.

Die Nutzung von using trägt also vor allem dazu bei, den Code sauber zu halten und vor allem Konzepte 100%ig umzusetzen, wenn sie denn schon einmal implementiert wurden – wie in diesem Fall IDisposable.

11 Antworten auf „C#: Using und IDisposable verstanden“

  1. Sehr guter Beitrag! Zumal sich die gesamte .net-Dokumentation zu diesem Thema ausschweigt. Aber es freut mich, dass es wenigstens ein paar Leute gibt, die einem den letzten Krümel verständlich machen können. Gruß Vincenz

  2. Danke für den Artikel.

    Interessant zu wissen ist auch, sobald eine nicht behandelte Exception in einem using-Block ausgelöst wird, wird die Dispose-Methode ebenfalls aufgerufen.
    Somit muss man sich nicht in z.B. einem finally-Block um die Speicherbereinigung kümmern.

    1. Hallo jub1: Ja, auf einige Aspekte sind wir damals im Artikel nicht eingegangen und schlauer sind wir nun auch noch geworden. Ich überlege schon, einen Update-Artikel zu machen, zumal man bei einigen Techniken, wie WCF tierisch aufpassen muss, weil using und Disposable nicht funktionieren. Aber erstmal danke für die Anregung.

    1. Hallo Alex! Du hast recht, das ist ein wenig verwirrend. Werde das mal korrigieren. Wenn Du genau hinschaust, dann siehst Du, dass ich die eigene Überladung von Dispose aufrufe und ihr false übergebe. Dadurch wird letztlich sichergestellt, dass in Zeile 28 des Listings kein Dispose erfolgt.

    1. Hmmm. Es wird immer dann verwendet, wenn ein Typ IDisposable implementiert und eine Instanz dieses Typs vom Garbage Collector entsorgt wird. Man nutzt es bei Typen, die Ressourcen verwalten, die aufgeräumt werden müssen, wenn sie nicht mehr benötigt werden. Typisch sind: Dateizugriffs-Handles, DB-Verbindungen usw.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.