Używanie obiektów implementujących interfejs IDisposable
Moduł odśmiecający pamięci (GC) środowiska uruchomieniowego języka wspólnego odzyskuje pamięć używaną przez obiekty zarządzane. Zazwyczaj typy korzystające z zasobów niezarządzanych implementują IDisposable interfejs lub IAsyncDisposable , aby umożliwić odzyskanie niezarządzanych zasobów. Po zakończeniu korzystania z obiektu, który implementuje IDisposablemetodę , wywołujesz obiekty Dispose lub DisposeAsync implementację, aby jawnie wykonać oczyszczanie. Możesz to zrobić na dwa sposoby:
- Za pomocą instrukcji lub deklaracji języka C#
using
(Using
w Visual Basic). - Implementując blok i wywołując metodę
try/finally
Dispose or DisposeAsync w obiekciefinally
.
Ważne
GC nie usuwa obiektów, ponieważ nie ma wiedzy IDisposable.Dispose() lub .IAsyncDisposable.DisposeAsync() GC wie tylko, czy obiekt jest finalizowalny (czyli definiuje metodę Object.Finalize() ) i kiedy finalizator obiektu musi być wywoływany. Aby uzyskać więcej informacji, zobacz Jak działa finalizacja. Aby uzyskać dodatkowe informacje na temat implementacji Dispose
i DisposeAsync
, zobacz:
Obiekty, które implementują System.IDisposable lub System.IAsyncDisposable powinny być zawsze prawidłowo usuwane, niezależnie od zakresu zmiennych, chyba że określono inaczej. Typy definiujące finalizator do wydania niezarządzanych zasobów zwykle są wywoływane GC.SuppressFinalize z poziomu ich Dispose
lub DisposeAsync
implementacji. Wywołanie SuppressFinalize wskazuje GC, że finalizator został już uruchomiony, a obiekt nie powinien być promowany do finalizacji.
Instrukcja using
Instrukcja using
w języku C# i Using
instrukcja w Visual Basic upraszczają kod, który należy napisać w celu oczyszczenia obiektu. Instrukcja using
uzyskuje co najmniej jeden zasób, wykonuje określone instrukcje i automatycznie usuwa obiekt. Jednak instrukcja using
jest przydatna tylko w przypadku obiektów używanych w zakresie metody, w której są konstruowane.
W poniższym przykładzie użyto instrukcji using
, aby utworzyć i zwolnić System.IO.StreamReader obiekt.
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
Deklaracja using
jest alternatywną składnią dostępną, gdy nawiasy klamrowe są usuwane, a określanie zakresu jest niejawne.
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.
//
}
}
}
StreamReader Mimo że klasa implementuje IDisposable interfejs, który wskazuje, że używa niezarządzanego zasobu, przykład nie wywołuje StreamReader.Dispose jawnie metody . Gdy kompilator języka C# lub Visual Basic napotka instrukcję using
, emituje język pośredni (IL), który jest odpowiednikiem następującego kodu, który jawnie zawiera try/finally
blok.
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
Instrukcja języka C# using
umożliwia również uzyskanie wielu zasobów w jednej instrukcji, która jest wewnętrznie równoważna zagnieżdżonym using
instrukcjom. Poniższy przykład tworzy wystąpienie dwóch StreamReader obiektów w celu odczytania zawartości dwóch różnych plików.
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.
//
}
}
}
Blok try/finally
Zamiast opakowować try/finally
blok w instrukcji using
, możesz zdecydować się na bezpośrednie zaimplementowanie try/finally
bloku. Może to być twój osobisty styl kodowania lub możesz to zrobić z jednego z następujących powodów:
- Aby dołączyć
catch
blok do obsługi wyjątków zgłaszanych wtry
bloku. W przeciwnym razie wszelkie wyjątki zgłoszone w instrukcjiusing
są nieobsługiwane. - Aby utworzyć wystąpienie obiektu, który implementuje IDisposable , którego zakres nie jest lokalny dla bloku, w którym jest zadeklarowany.
Poniższy przykład jest podobny do poprzedniego przykładu, z tą różnicą try/catch/finally
, że używa bloku do tworzenia wystąpień, używania i usuwania StreamReader obiektu oraz do obsługi wszelkich wyjątków zgłaszanych przez StreamReader konstruktor i jego ReadToEnd metodę. Kod w finally
bloku sprawdza, czy obiekt, który implementuje IDisposable , nie null
jest przed wywołaniem Dispose metody . Wykonanie tej czynności może spowodować wyjątek w NullReferenceException czasie wykonywania.
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
Jeśli zdecydujesz się zaimplementować try/finally
lub wdrożyć blok, możesz postępować zgodnie z tym podstawowym wzorcem using
, ponieważ język programowania nie obsługuje instrukcji, ale zezwala na bezpośrednie wywołania Dispose metody.
Elementy członkowskie wystąpienia IDisposable
Jeśli klasa jest właścicielem pola lub właściwości wystąpienia, a jego typ implementuje IDisposable, klasa powinna również zaimplementować IDisposablewartość . Aby uzyskać więcej informacji, zobacz Implementowanie usuwania kaskadowego.