Использование объектов, реализующих IDisposable

Сборщик мусора среды cl language (GC) освобождает память, используемую управляемыми объектами. Как правило, типы, использующие неуправляемые ресурсы, реализуют IDisposable или IAsyncDisposable интерфейс, чтобы разрешить восстановление неуправляемых ресурсов. Завершив использование объекта, реализующего IDisposableобъект, вызовите Dispose объект или DisposeAsync реализацию, чтобы явно выполнить очистку. Это можно сделать одним из двух способов.

  • С помощью инструкции или объявления C# using (Using в Visual Basic).
  • Путем реализации try/finally блока и вызова DisposeDisposeAsync метода в объекте finally.

Внимание

GC не удаляет объекты, так как у него нет знаний IDisposable.Dispose() или IAsyncDisposable.DisposeAsync(). GC знает только, является ли объект завершенным (т. е. определяет Object.Finalize() метод), и когда необходимо вызвать метод завершения объекта. Дополнительные сведения см. в разделе о том, как работает завершение. Дополнительные сведения о реализации Dispose и DisposeAsync, см. в статье:

Объекты, реализующие System.IDisposable или System.IAsyncDisposable всегда должны быть должным образом удалены независимо от области переменных, если иное не указано явным образом. Типы, определяющие метод завершения для выпуска неуправляемых ресурсов, обычно вызываются GC.SuppressFinalize из их Dispose или DisposeAsync реализации. Вызов SuppressFinalize указывает в GC, что средство завершения уже запущено, и объект не должен быть повышен для завершения.

Оператор using

Оператор using (C#) и оператор Using (Visual Basic) упрощают написание кода для очистки объекта. Оператор using получает один или больше ресурсов, выполняет заданные операторы, после чего автоматически освобождает объект. Однако оператор using полезен только для объектов в области действия метода, в котором они созданы.

В следующем примере для создания и освобождения объекта using используется оператор 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

using Объявление — это альтернативный синтаксис, доступный при удалении фигурных скобок, и области неявно.

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 реализует интерфейс IDisposable, который указывает, что используется неуправляемый ресурс, пример явно не вызывает метод StreamReader.Dispose. Когда компилятор C# или Visual Basic встречает оператор using, он создает промежуточный язык, эквивалентный следующему коду, который явно содержит блок try/finally.

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

Оператор using в C# позволяет присоединять несколько ресурсов в одном операторе, что эквивалентно использованию вложенных инструкций using. В следующем примере создается два объекта StreamReader для чтения содержимого двух разных файлов.

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

Вместо размещения блока try/finally в операторе using можно реализовать блок try/finally напрямую. Это может отражать ваш стиль программирования или же осуществляться по одной из следующих причин:

  • Чтобы включить блок catch для обработки исключений, вызванных в блоке try. В противном случае любые исключения, вызванные в операторе using, не обрабатываются.
  • Чтобы создать экземпляр объекта, реализующего интерфейс IDisposable, область действия которого не является локальной для блока, в котором он объявлен.

Следующий пример похож на предыдущий с тем отличием, что в нем используется блок try/catch/finallyдля создания, использования и удаления экземпляра объекта StreamReader, а также для обработки исключений, создаваемых конструктором StreamReader и его методом ReadToEnd. Перед вызовом метода Dispose код в блоке finally проверяет, что объект, реализующий IDisposable, не является null. Если этого сделать не удастся, это может привести к исключению NullReferenceException во время выполнения.

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

Вы можете применить этот базовый шаблон, если есть желание или необходимость реализовать блок try/finally на языке программирования, который не поддерживает оператор using, но допускает прямые вызовы метода Dispose.

Члены экземпляра IDisposable

Если класс владеет полем экземпляра или свойством и его типом IDisposableреализуется, класс также должен реализовать IDisposable. Дополнительные сведения см. в разделе "Реализация каскадного удаления".

См. также