Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

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.
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.
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 Sub DoWork()
Console.WriteLine("Opening Connection")
Console.WriteLine("Doing Work")
Console.WriteLine("Closing Connection")
End Sub
End ClassDies 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.
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.
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
Implements IDisposable
Public Sub Dispose() Implements IDisposable.Dispose
Console.WriteLine("Closing Connection via Dispose")
End Sub
End ClassDie 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.
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 demo As New DemoResource()
demo.DoWork()
End UsingAm 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.
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 DisposeOpening Connection
Doing Work
I broke
Closing Connection via DisposeDies 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.
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 demo As New DemoResource()
demo.DoWork()
End Using
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.
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
Implements IDisposable
Private disposed As Boolean = False ' To detect redundant calls
' Public dispose method
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
' Protected virtual dispose method
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposed Then
If disposing Then
' Dispose managed resources here
End If
' Free unmanaged resources here
disposed = True
End If
End Sub
End ClassSo 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.
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
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.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy