Zum Fußzeileninhalt springen
Iron Academy Logo
C#-Anwendung
C#-Anwendung

Andere Kategorien

Wie IDisposable und Using-Anweisungen in C# zusammenarbeiten

Tim Corey
10m 00s

Das Ressourcenmanagement ist eine der wichtigsten Aufgaben eines C#-Entwicklers. Ohne ordnungsgemäße Bereinigung von Ressourcen wie Datei-Handles, Datenbankverbindungen oder nicht verwaltetem Speicher können Anwendungen schnell in Leistungsprobleme, Speicherlecks oder sogar Systemabstürze geraten.

In seinem Video "How IDisposable and Using Statements Work Together in C#" erklärt Tim Corey anschaulich und praxisnah, wie das C# IDisposable-Pattern die richtige Ressourcenverwaltung sicherstellt und wie die using-Anweisung die Bereinigung vereinfacht. In diesem Artikel gehen wir Schritt für Schritt durch seine Demonstration, um zu verstehen, wie dieses Muster dabei hilft, nicht verwaltete Ressourcen effizient freizugeben und Ressourcenlecks zu verhindern.

Einführung in IDisposable und Ressourcenmanagement

Tim beginnt mit der Beschreibung von IDisposable als "leistungsfähiges Tool zur Gewährleistung einer angemessenen Ressourcenverwaltung und Sicherheit für Ihre Anwendung" Er erklärt, dass nicht verwaltete Ressourcen - wie Datenbankverbindungen, Dateistreams oder System-Handles - nicht automatisch vom Garbage Collector aufgeräumt werden.

Im Gegensatz dazu werden verwaltete Ressourcen (wie Strings oder reguläre C#-Objekte) automatisch vom Garbage Collection-Prozess behandelt. Das Problem entsteht, wenn eine Klasse direkt mit nicht verwaltetem Code oder nicht verwalteten Ressourcen wie Speicher auf Betriebssystemebene oder Datei-Handles interagiert - da diese außerhalb der Kontrolle der .NET-Laufzeitumgebung liegen.

Tim betont, dass nicht verwaltete Ressourcen, die nicht explizit freigegeben werden, zugewiesen bleiben, was zu Speicherlecks und schlechter Systemleistung führt. Die IDisposable-Schnittstelle wurde entwickelt, um Entwicklern einen deterministischen Bereinigungsmechanismus zu bieten - eine garantierte Möglichkeit, Ressourcen zu bereinigen, wenn die Lebensdauer eines Objekts endet.

Simulierung der Ressourcennutzung

Um die Notwendigkeit der Bereinigung zu demonstrieren, erstellt Tim eine kleine Konsolenanwendung, die eine DemoResource-Klasse enthält. Die Klasse verfügt über eine DoWork()-Methode, die das Öffnen und Schließen einer Datenbankverbindung simuliert:

public class DemoResource
{
    public void DoWork()
    {
        Console.WriteLine("Opening Connection");
        Console.WriteLine("Doing Work");
        Console.WriteLine("Closing Connection");
    }
}
public class DemoResource
{
    public void DoWork()
    {
        Console.WriteLine("Opening Connection");
        Console.WriteLine("Doing Work");
        Console.WriteLine("Closing Connection");
    }
}

Dies ist ein typischer Arbeitsablauf, der nicht verwaltete Ressourcen einbezieht - wie das Herstellen einer Verbindung zu einer Datenbank oder das Schreiben in eine Datei. Die Operationen innerhalb von DoWork() simulieren, was passieren würde, wenn wir nicht verwaltete Ressourcen direkt verwenden würden.

Wenn etwas schief geht - Ressourcenlecks

Etwa bei Minute 2 demonstriert Tim, was passiert, wenn der Prozess nicht richtig abgeschlossen wird. Er fügt eine Ausnahme hinzu, um einen Fehler während des Vorgangs zu simulieren:

throw new Exception("I broke");
throw new Exception("I broke");

Wenn diese Ausnahme auftritt, erreicht das Programm nie die Zeile "Closing Connection", d. h. die nicht verwaltete Ressource bleibt geöffnet.

Tim erinnert sich an seine frühen Erfahrungen, bei denen Server jede Nacht neu gebootet werden mussten, weil Anwendungen Datenbankverbindungen nicht schließen konnten. Diese nicht abgeschlossenen Verbindungen würden sich anhäufen und den gesamten verfügbaren Speicher und die Sockets verbrauchen. Dies ist ein klassisches Beispiel für Ressourcenlecks aufgrund fehlender oder unsachgemäßer Bereinigungslogik.

Die Rolle von IDisposable

Um dieses Problem zu lösen, führt Tim die Schnittstelle IDisposable ein, die die Methode Dispose definiert. Die Implementierung von IDisposable teilt .NET mit, dass diese Klasse über freizugebende Ressourcen verfügt und definiert, wie diese freigegeben werden sollen.

Tim fügt : IDisposable zu seiner Klasse hinzu und implementiert die Methode:

public class DemoResource : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Closing Connection via Dispose");
    }
}
public class DemoResource : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Closing Connection via Dispose");
    }
}

Die Dispose-Methode dient als spezieller Ort für die Bereinigung von Ressourcen, wie z. B. das Freigeben von nicht verwaltetem Speicher, das Schließen von Datei-Handles oder die Freigabe von Datenbankverbindungen.

Tim erklärt, dass diese Dispose-Methode automatisch über eine using-Anweisung aufgerufen werden kann, wodurch sichergestellt wird, dass die Bereinigung zuverlässig erfolgt - selbst wenn Ausnahmen auftreten.

Verwendung von Anweisungen und deterministische Bereinigung

Tim stellt klar, dass using in C# zwei verschiedene Dinge bedeuten kann:

  • Using-Direktive - am Anfang der Datei (z. B. using System;)

  • Anweisung zur Verwendung - zur Bereinigung von Ressourcen

Letzteres demonstriert er:

using DemoResource demo = new DemoResource();
demo.DoWork();
using DemoResource demo = new DemoResource();
demo.DoWork();

Am Ende des Geltungsbereichs dieser Anweisung ruft der Compiler automatisch die Methode Dispose auf. Dies gewährleistet eine deterministische Bereinigung, d. h. die Ressource wird sofort nach der Verwendung freigegeben, anstatt darauf zu warten, dass der Garbage Collector das Objekt später abschließt.

Dieser Ansatz verbessert die Stabilität der Anwendung und die Effizienz der Speichernutzung, indem sichergestellt wird, dass alle Wegwerfobjekte zum richtigen Zeitpunkt ordnungsgemäß entsorgt werden.

Was passiert, wenn eine Exception auftritt

Tim führt die Ausnahme erneut ein und führt die Demo erneut durch. Obwohl die Ausnahme den normalen Ablauf unterbricht, zeigt die Ausgabe, dass Dispose() weiterhin aufgerufen wird:

Opening Connection
Doing Work
I broke
Closing Connection via Dispose
Opening Connection
Doing Work
I broke
Closing Connection via Dispose

Dies zeigt, dass der using block auch bei Fehlern eine Bereinigung garantiert. Dies entspricht der Platzierung von Bereinigungslogik innerhalb eines finally-Blocks, ist aber viel sauberer und lesbarer.

Dies ist die Stärke des C# IDisposable-Musters - es stellt sicher, dass alle verwalteten oder nicht verwalteten Ressourcen ordnungsgemäß freigegeben werden, ohne dass eine manuelle Bereinigung in jedem Teil des Codes erforderlich ist.

Der Umfang der Verwendung und wann Dispose aufgerufen wird

Anschließend untersucht Tim, wie sich der Umfang auf das Timing der Entsorgung auswirkt. Wenn die using-Deklaration endet, fügt der Compiler automatisch einen Aufruf von Dispose() ein.

Er zeigt, dass, wenn Sie eine weitere Zeile wie:

Console.WriteLine("I'm done running Program.cs");
Console.WriteLine("I'm done running Program.cs");

nach der using-Anweisung wird diese Zeile vor dem Aufruf von Dispose() ausgeführt, da die Entsorgung erfolgt, wenn der aktuelle Bereich verlassen wird (z. B. am Ende der Methode).

Damit die Entsorgung früher erfolgt, verpackt Tim den Code in einen using-Block:

using (DemoResource demo = new DemoResource())
{
    demo.DoWork();
}
Console.WriteLine("I'm done running Program.cs");
using (DemoResource demo = new DemoResource())
{
    demo.DoWork();
}
Console.WriteLine("I'm done running Program.cs");

Jetzt wird die Dispose-Methode vor der abschließenden Druckanweisung ausgeführt, da das Objekt am Ende des Blocks aus dem Anwendungsbereich herausgeht.

Dies demonstriert die deterministische Ressourcenbereinigung, die sicherstellt, dass Ressourcen sofort freigegeben werden, wenn der Codeblock die Ausführung beendet.

Das vollständige Dispose-Muster (erweitertes Konzept)

Tims Demo konzentriert sich zwar auf die Grundlagen, führt aber ganz natürlich zum vollständigen C# Dispose-Muster, das im Produktionscode verwendet wird. Dieses Muster ermöglicht eine sichere Bereinigung sowohl von verwalteten als auch von nicht verwalteten Ressourcen, unterstützt Vererbung und vermeidet doppelte Bereinigungen. Das Muster sieht typischerweise wie folgt aus:

public class BaseResource : IDisposable
{
    private bool disposed = false; // To detect redundant calls

    // Public dispose method
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected virtual dispose method
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources here
            }

            // Free unmanaged resources here
            disposed = true;
        }
    }
}
public class BaseResource : IDisposable
{
    private bool disposed = false; // To detect redundant calls

    // Public dispose method
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected virtual dispose method
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources here
            }

            // Free unmanaged resources here
            disposed = true;
        }
    }
}

So sieht es aus:

  • Dispose(bool disposing) unterscheidet zwischen dem Entsorgen von verwalteten Objekten (wenn disposing true ist) und dem Freigeben von nicht verwalteten Ressourcen (immer erforderlich).

  • Der Disposing-Parameter hilft zu verhindern, dass verwaltete Objekte während der Finalisierung entsorgt werden, wenn der Garbage Collector sie möglicherweise bereits zurückgefordert hat.

  • GC.SuppressFinalize(this) verhindert, dass der Garbage Collector den Finalizer aufruft, sobald die manuelle Entsorgung abgeschlossen ist.

  • protected virtual void Dispose(bool disposing) ermöglicht es abgeleiteten Klassen, das Entsorgungsverhalten mit protected override void Dispose(bool disposing) für Kaskaden-Dispose-Aufrufe außer Kraft zu setzen.

Dies gewährleistet eine effiziente Ressourcenverwaltung, verhindert Ressourcenlecks und bietet eine sichere Bereinigungslogik für verwaltete und nicht verwaltete Ressourcen.

Warum eine korrekte Bereinigung wichtig ist

Tims Beispiel zeigt, wie wichtig es ist, das Dispose-Muster korrekt zu implementieren - nicht nur, um Datenbankverbindungen zu schließen, sondern auch, um nicht verwalteten Speicher, Datei-Handles und Systemressourcen ordnungsgemäß zu behandeln. Durch die Implementierung von IDisposable und die Einbettung von Objekten in using-Anweisungen stellen Sie sicher, dass:

  • Die Ressourcen werden zeitnah veröffentlicht

  • Die Garbage Collection muss nicht mit unverwalteten Ressourcen umgehen

  • Die Speichernutzung bleibt optimal

  • Anwendungen bleiben stabil und effizient

Abschluss

Wie Tim in seinem Video zusammenfasst, arbeiten die IDisposable-Schnittstelle und die using-Anweisung Hand in Hand, um zu gewährleisten, dass die Bereinigung automatisch erfolgt, auch wenn Ausnahmen auftreten.

Durch die Implementierung des Dispose-Musters erhalten Sie die volle Kontrolle darüber, wie Ihre Objekte ihre verwalteten und nicht verwalteten Ressourcen freigeben, während der using-Block sicherstellt, dass dieser Prozess im richtigen Moment ausgelöst wird - egal was passiert.

Diese Kombination bildet das Rückgrat einer effektiven Ressourcenverwaltung in C# und sorgt für stabile, effiziente und leckagefreie Anwendungen.

Wenn Sie IDisposable mit einer using-Anweisung verwenden, wird die Dispose-Methode immer am Ende des Bereichs aufgerufen - ob mit oder ohne Ausnahme."

  • Tim Corey

Kurz gesagt, das Verständnis und die Implementierung des C# IDisposable-Musters ist ein wesentlicher Schritt zur Beherrschung der Ressourcenbereinigung, zur Vermeidung von Lecks und zur Verbesserung der Anwendungsstabilität.

Hero Worlddot related to Wie IDisposable und Using-Anweisungen in C# zusammenarbeiten
Hero Affiliate related to Wie IDisposable und Using-Anweisungen in C# zusammenarbeiten

Verdienen Sie mehr, indem Sie teilen, was Sie lieben

Erstellen Sie Inhalte für Entwickler, die mit .NET, C#, Java, Python oder Node.js arbeiten? Verwandeln Sie Ihr Fachwissen in ein zusätzliches Einkommen!

Iron Support Team

Wir sind 24 Stunden am Tag, 5 Tage die Woche online.
Chat
E-Mail
Rufen Sie mich an