Udostępnij za pośrednictwem


Używanie obiektów implementujących interfejs IDisposable

Kolektor odpadów 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 IDisposable, wywołujesz implementację Dispose lub DisposeAsync, aby przeprowadzić jawne czyszczenie. Można to zrobić na jeden z dwóch sposobów:

  • Za pomocą instrukcji lub deklaracji języka C# using (Using w Visual Basic).
  • Implementując blok try/finally i wywołując metodę Dispose lub DisposeAsync w finally.

Ważne

GC nie usuwa Twoich obiektów, ponieważ nie ma informacji o 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 wywołują GC.SuppressFinalize w swojej 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ć obiekt System.IO.StreamReader.

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

Deklaracjausing jest alternatywną składnią dostępną, w której nawiasy klamrowe są usuwane, a zakres jest definiowany niejawnie.

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.
            //
        }
    }
}

Mimo że klasa StreamReader implementuje interfejs IDisposable, który wskazuje, że używa niezarządzanego zasobu, przykład nie wywołuje jawnie metody StreamReader.Dispose. 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 C# using umożliwia również uzyskanie wielu zasobów w jednej instrukcji, co wewnętrznie odpowiada zagnieżdżonym instrukcjom using. Poniższy przykład tworzy dwa wystąpienia StreamReader obiektów do 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.
            //
        }
    }
}

Spróbuj/na koniec blokuj

Zamiast obudowywać blok try/finally instrukcją using, możesz zdecydować się na implementację bloku try/finally bezpośrednio. Może to być twój osobisty styl kodowania lub możesz to zrobić z jednego z następujących powodów:

  • Aby włączyć blok catch do obsługi wyjątków, które są zgłaszane w bloku try. W przeciwnym razie wszelkie wyjątki zgłoszone w instrukcji using 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, z tą różnicą, że używa bloku try/catch/finally do zainicjowania, używania i usuwania obiektu StreamReader, oraz do obsługi wszelkich wyjątków zgłaszanych przez konstruktor StreamReader i jego metodę ReadToEnd. Kod w bloku finally sprawdza, czy obiekt, który implementuje IDisposable, nie jest null przed wywołaniem metody Dispose. Niepowodzenie w wykonaniu tej czynności może spowodować wyjątek w NullReferenceException czasie działania.

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

Możesz postępować zgodnie z tym podstawowym wzorcem, jeśli zdecydujesz się zaimplementować lub musisz zaimplementować blok try/finally, ponieważ język programowania nie obsługuje instrukcji using, ale umożliwia bezpośrednie wywołania metody Dispose.

Członkowie instancji IDisposable

Jeśli klasa jest właścicielem pola lub właściwości wystąpienia, a jej typ implementuje IDisposable, klasa powinna również zaimplementować IDisposable. Aby uzyskać więcej informacji, zobacz Implementowanie usuwania kaskadowego.

Zobacz także