Verwenden von Objekten, die IDisposable implementieren
Der Garbage Collector (GC) der Common Language Runtime beansprucht den von verwalteten Objekten verwendeten Arbeitsspeicher wieder. In der Regel implementieren Typen, die nicht verwaltete Ressourcen verwenden, die IDisposable- oder IAsyncDisposable-Schnittstelle, damit die nicht verwalteten Ressourcen wieder beansprucht werden können. Wenn Sie mit der Verwendung eines Objekts fertig sind, das IDisposable implementiert, rufen Sie die Dispose- oder DisposeAsync-Implementierung des Objekts auf, um die Bereinigung explizit durchzuführen. Dazu haben Sie zwei Möglichkeiten:
- Mithilfe der C#-Anweisung oder Deklaration
using
(Using
in Visual Basic). - Durch Implementieren eines
try/finally
-Blocks und Aufrufen der Dispose- oder DisposeAsync-Methode infinally
.
Wichtig
Die GC entsorgt Ihre Objekte nicht, weil sie keine Kenntnis von IDisposable.Dispose() oder IAsyncDisposable.DisposeAsync() hat. Die GC weiß nur, ob ein Objekt finalisierbar ist (d. h., es definiert eine Object.Finalize()-Methode) und wann der Finalizer des Objekts aufgerufen werden muss. Weitere Informationen finden Sie unter Funktionsweise der Finalisierung. Weitere Details zur Implementierung von Dispose
und DisposeAsync
finden Sie unter:
Objekte, die System.IDisposable oder System.IAsyncDisposable implementieren, sollten immer unabhängig vom Variablenbereich ordnungsgemäß gelöscht werden, sofern nicht ausdrücklich anders angegeben. Typen, die einen Finalizer definieren, um nicht verwaltete Ressourcen freizugeben, rufen in der Regel GC.SuppressFinalize entweder von deren Dispose
- oder DisposeAsync
-Implementierung auf. Das Aufrufen von SuppressFinalize zeigt der GC an, dass der Finalizer bereits ausgeführt wurde und das Objekt nicht zur Finalisierung höhergestuft werden sollte.
Die using-Anweisung
Die using
-Anweisung in C# und die Using
-Anweisung in Visual Basic vereinfachen den Code, den Sie schreiben müssen, um ein Objekt zu bereinigen. Die using
-Anweisung ruft eine oder mehrere Ressourcen ab, führt die von Ihnen angegebenen Anweisungen aus und verwirft dann das Objekt automatisch. Die using
-Anweisung ist jedoch nur hilfreich bei Objekten, die im Bereich der Methode verwendet werden, in der sie erstellt werden.
Im folgenden Beispiel wird die using
-Anweisung verwendet, um ein System.IO.StreamReader-Objekt zu erstellen und freizugeben.
using System.IO;
class UsingStatement
{
static void Main()
{
var buffer = new char[50];
using (StreamReader streamReader = new("file1.txt"))
{
int charsRead = 0;
while (streamReader.Peek() != -1)
{
charsRead = streamReader.Read(buffer, 0, buffer.Length);
//
// Process characters read.
//
}
}
}
}
Imports System.IO
Module UsingStatement
Public Sub Main()
Dim buffer(49) As Char
Using streamReader As New StreamReader("File1.txt")
Dim charsRead As Integer
Do While streamReader.Peek() <> -1
charsRead = streamReader.Read(buffer, 0, buffer.Length)
'
' Process characters read.
'
Loop
End Using
End Sub
End Module
Eine using
-Deklaration ist eine alternative Syntax, die verfügbar ist, wenn die Klammern entfernt werden und die Bereichsdefinition implizit ist.
using System.IO;
class UsingDeclaration
{
static void Main()
{
var buffer = new char[50];
using StreamReader streamReader = new("file1.txt");
int charsRead = 0;
while (streamReader.Peek() != -1)
{
charsRead = streamReader.Read(buffer, 0, buffer.Length);
//
// Process characters read.
//
}
}
}
Obwohl die StreamReader-Klasse die IDisposable-Schnittstelle implementiert, die angibt, dass sie eine nicht verwaltete Ressource verwendet, wird in dem Beispiel nicht explizit die StreamReader.Dispose-Methode aufgerufen. Wenn der C#- oder Visual Basic-Compiler die using
-Anweisung findet, gibt er Zwischensprache (Intermediate Language, IL) aus, die dem folgenden Code entspricht, der explizit einen try/finally
-Block enthält.
using System.IO;
class TryFinallyGenerated
{
static void Main()
{
var buffer = new char[50];
StreamReader? streamReader = null;
try
{
streamReader = new StreamReader("file1.txt");
int charsRead = 0;
while (streamReader.Peek() != -1)
{
charsRead = streamReader.Read(buffer, 0, buffer.Length);
//
// Process characters read.
//
}
}
finally
{
// If non-null, call the object's Dispose method.
streamReader?.Dispose();
}
}
}
Imports System.IO
Module TryFinallyGenerated
Public Sub Main()
Dim buffer(49) As Char
Dim streamReader As New StreamReader("File1.txt")
Try
Dim charsRead As Integer
Do While streamReader.Peek() <> -1
charsRead = streamReader.Read(buffer, 0, buffer.Length)
'
' Process characters read.
'
Loop
Finally
If streamReader IsNot Nothing Then DirectCast(streamReader, IDisposable).Dispose()
End Try
End Sub
End Module
Mit der using
-Anweisung in C# können Sie auch mehrere Ressourcen in einer einzigen Anweisung abrufen. Intern entspricht dies geschachtelten using
-Anweisungen. Im folgenden Beispiel werden zwei StreamReader-Objekte instanziiert, um den Inhalt von zwei verschiedenen Dateien zu lesen.
using System.IO;
class SingleStatementMultiple
{
static void Main()
{
var buffer1 = new char[50];
var buffer2 = new char[50];
using StreamReader version1 = new("file1.txt"),
version2 = new("file2.txt");
int charsRead1, charsRead2 = 0;
while (version1.Peek() != -1 && version2.Peek() != -1)
{
charsRead1 = version1.Read(buffer1, 0, buffer1.Length);
charsRead2 = version2.Read(buffer2, 0, buffer2.Length);
//
// Process characters read.
//
}
}
}
Try-/Finally-Block
Anstatt einen try/finally
-Block in einer using
-Anweisung zu umschließen, haben Sie die Möglichkeit, den try/finally
-Block direkt zu implementieren. Dies kann Ihr persönlicher Codierungsstil sein, oder Sie möchten dies eventuell aus einem der folgenden Gründe durchführen:
- Um einen
catch
-Block einzufügen, um imtry
-Block ausgelöste Ausnahmen zu behandeln. Andernfalls werden alle in derusing
-Anweisung ausgelösten Ausnahmen nicht behandelt. - Um ein Objekt zu instanziieren, das IDisposable implementiert, dessen Bereich für den Block, in dem es deklariert ist, nicht lokal ist.
Das folgende Beispiel ähnelt dem vorhergehenden, es wird jedoch ein try/catch/finally
-Block verwendet, um ein StreamReader-Objekt zu instanziieren, zu verwenden und zu verwerfen, und um alle Ausnahmen zu behandeln, die von dem StreamReader-Konstruktor und dessen ReadToEnd-Methode ausgelöst werden. Der Code im finally
-Block überprüft, ob das Objekt, das IDisposable implementiert, nicht null
ist, bevor die Dispose-Methode aufgerufen wird. Wird dies nicht ausgeführt, kann es zu einer NullReferenceException-Ausnahme zur Laufzeit kommen.
using System;
using System.Globalization;
using System.IO;
class TryExplicitCatchFinally
{
static void Main()
{
StreamReader? streamReader = null;
try
{
streamReader = new StreamReader("file1.txt");
string contents = streamReader.ReadToEnd();
var info = new StringInfo(contents);
Console.WriteLine($"The file has {info.LengthInTextElements} text elements.");
}
catch (FileNotFoundException)
{
Console.WriteLine("The file cannot be found.");
}
catch (IOException)
{
Console.WriteLine("An I/O error has occurred.");
}
catch (OutOfMemoryException)
{
Console.WriteLine("There is insufficient memory to read the file.");
}
finally
{
streamReader?.Dispose();
}
}
}
Imports System.Globalization
Imports System.IO
Module TryExplicitCatchFinally
Sub Main()
Dim streamReader As StreamReader = Nothing
Try
streamReader = New StreamReader("file1.txt")
Dim contents As String = streamReader.ReadToEnd()
Dim info As StringInfo = New StringInfo(contents)
Console.WriteLine($"The file has {info.LengthInTextElements} text elements.")
Catch e As FileNotFoundException
Console.WriteLine("The file cannot be found.")
Catch e As IOException
Console.WriteLine("An I/O error has occurred.")
Catch e As OutOfMemoryException
Console.WriteLine("There is insufficient memory to read the file.")
Finally
If streamReader IsNot Nothing Then streamReader.Dispose()
End Try
End Sub
End Module
Sie können dieses grundlegende Muster beibehalten, wenn Sie einen try/finally
-Block implementieren möchten oder müssen, da Ihre Programmiersprache eine using
-Anweisung nicht unterstützt, jedoch direkte Aufrufe der Dispose-Methode erlaubt.
IDisposable-Instanzmember
Wenn die Klasse ein Instanzfeld oder eine Instanzeigenschaft besitzt und dessen/deren Typ IDisposable implementiert, sollte die Klasse auch IDisposable implementieren. Weitere Informationen finden Sie unter Implementieren eines kaskadierenden Dispose (Löschung).