Поделиться через


Реализуйте метод Dispose

Метод Dispose в основном реализуется для выпуска неуправляемых ресурсов. При работе с элементами экземпляра, которые являются реализацией IDisposable, обычно происходит каскадирование вызовов Dispose. Существуют и другие причины реализации Dispose, например для освобождения выделенной памяти, удаления элемента, добавленного в коллекцию, или сигнала о выпуске блокировки, полученной.

сборщик мусора .NET не выделяет или не освобождает неуправляемую память. Шаблон удаления объекта, известный как шаблон удаления, накладывает порядок на время существования объекта. Шаблон удаления используется для объектов, реализующих интерфейс IDisposable. Этот шаблон распространен при взаимодействии с дескрипторами файлов и каналов, дескрипторами реестра, дескрипторами ожидания или указателями на блоки неуправляемой памяти, так как сборщик мусора не может освободить неуправляемые объекты.

Чтобы убедиться, что ресурсы всегда очищаются соответствующим образом, метод Dispose должен быть идемпотентным, таким образом, что он может вызываться несколько раз, не вызывая исключения. Кроме того, последующие вызовы Dispose не должны ничего делать.

В примере кода, предоставленном для метода GC.KeepAlive, показано, как сборка мусора может привести к запуску финализатора, в то время как неуправляемая ссылка на объект или его члены все еще используется. Может потребоваться использовать GC.KeepAlive, чтобы сделать объект недопустимым для сборки мусора с начала текущей подпрограммы до точки вызова этого метода.

Совет

Что касается внедрения зависимостей, при регистрации служб в IServiceCollectionвремя существования службы управляется неявно в ваших интересах. IServiceProvider и соответствующий IHost координируют процесс очистки ресурсов. В частности, реализации IDisposable и IAsyncDisposable подлежат корректному удалению в конце указанного срока службы.

Дополнительные сведения см. в статье о внедрении зависимостей в .NET.

Безопасные хендлы

Написание кода для средства завершения объекта — это сложная задача, которая может вызвать проблемы, если это не сделано правильно. Поэтому рекомендуется создавать объекты System.Runtime.InteropServices.SafeHandle вместо реализации финализатора.

System.Runtime.InteropServices.SafeHandle — это абстрактный управляемый тип, который обёртывает System.IntPtr, определяющий неуправляемый ресурс. В Windows он может идентифицировать хэндл, а в Unix — дескриптор файла. SafeHandle предоставляет всю необходимую логику для того, чтобы этот ресурс был освобожден один раз и только один раз, либо когда SafeHandle удаляется, либо когда все ссылки на SafeHandle удалены и экземпляр SafeHandle завершен.

System.Runtime.InteropServices.SafeHandle является абстрактным базовым классом. Производные классы предоставляют конкретные экземпляры для различных типов дескрипторов. Эти производные классы проверяют, какие значения для System.IntPtr считаются недопустимыми и как освободить дескриптор. Например, SafeFileHandle наследуется от SafeHandle для обёртки IntPtrs, которые идентифицируют открытые файловые дескрипторы, и переопределяет метод SafeHandle.ReleaseHandle() для их закрытия (с помощью функции close на Unix или функции CloseHandle на Windows). Большинство API в библиотеках .NET, которые создают неуправляемый ресурс, оборачивают его в SafeHandle и возвращают этот SafeHandle вам по мере необходимости, вместо того чтобы напрямую возвращать необработанный указатель. В ситуациях, когда вы взаимодействуете с неуправляемым компонентом и получаете IntPtr для неуправляемого ресурса, можно создать собственный тип SafeHandle для его упаковки. В результате немногие типы, отличные отSafeHandle, нуждаются в реализации финализаторов. Большинство одноразовых реализаций шаблонов в конечном итоге упаковывают другие управляемые ресурсы, некоторые из которых могут быть SafeHandle объектами.

Следующие производные классы в пространстве имен Microsoft.Win32.SafeHandles предоставляют безопасные дескрипторы.

Класс Ресурсы, которые он содержит
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
Файлы, сопоставленные с памятью файлы и каналы
SafeMemoryMappedViewHandle Представления памяти
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
Конструкции криптографии
SafeRegistryHandle Ключи реестра
SafeWaitHandle Дескриптор ожидания

Dispose() и Dispose(bool)

Интерфейс IDisposable требует реализации одного метода без параметров Dispose. Кроме того, любой незапечатанный класс должен иметь перегружаемый метод Dispose(bool).

Подписи методов:

  • public не виртуальные (NotOverridable в Visual Basic) (реализацияIDisposable.Dispose).
  • protected virtual (Overridable в Visual Basic Dispose(bool)).

Метод Dispose()

Так как метод public, невиртуальный (NotOverridable в Visual Basic), метод без параметров Dispose вызывается, когда он больше не требуется (потребителем типа), его цель — освободить неуправляемые ресурсы, выполнить общую очистку и указать, что финализатор, если он существует, может не выполняться. Освобождение фактической памяти, связанной с управляемым объектом, всегда является обязанностью сборщика мусора. Из-за этого она имеет стандартную реализацию:

public void Dispose()
{
    // Dispose of unmanaged resources.
    Dispose(true);
    // Suppress finalization.
    GC.SuppressFinalize(this);
}
Public Sub Dispose() _
    Implements IDisposable.Dispose
    ' Dispose of unmanaged resources.
    Dispose(True)
    ' Suppress finalization.
    GC.SuppressFinalize(Me)
End Sub

Метод Dispose выполняет очистку всех объектов, поэтому сборщик мусора больше не требует вызова переопределения Object.Finalize объектов. Поэтому вызов метода SuppressFinalize запрещает сборщику мусора запускать метод завершения. Если тип не имеет финализатора, вызов GC.SuppressFinalize не оказывает эффекта. Фактическая очистка выполняется перегрузкой метода Dispose(bool).

Перегрузка метода Dispose(bool)

В перегруженной версии параметр disposing представляет собой Boolean, указывающий, происходит ли вызов метода из метода Dispose (его значение true) или из финализатора (его значение false).

protected virtual void Dispose(bool disposing)
{
    if (_disposed)
    {
        return;
    }

    if (disposing)
    {
        // Dispose managed state (managed objects).
        // ...
    }

    // Free unmanaged resources.
    // ...

    _disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
     If disposed Then Exit Sub

     If disposing Then

         ' Free managed resources.
         ' ...

     End If

     ' Free unmanaged resources.
     ' ...

     disposed = True
End Sub

Важное

Параметр disposing должен быть false при вызове из финализатора, а true при вызове из метода IDisposable.Dispose. Другими словами, это true при детерминированном вызове и false при недетерминированном вызове.

Текст метода состоит из трех блоков кода:

  • Блок условного возврата, если объект уже удален.

  • Условный блок, который освобождает управляемые ресурсы. Этот блок выполняется, если значение disposing равно true. Управляемые ресурсы, которые он освобождает, могут включать:

    • Управляемые объекты, реализующие IDisposable. Условный блок можно использовать для вызова реализации Dispose (каскадного удаления). Если вы использовали производный класс System.Runtime.InteropServices.SafeHandle для упаковки неуправляемого ресурса, необходимо вызвать реализацию SafeHandle.Dispose() здесь.
    • Управляемые объекты, использующие большие объемы памяти или использующие ограниченные ресурсы. Назначьте ссылки на крупные управляемые объекты null, чтобы сделать их более недоступными. Это освобождает их быстрее, чем если бы они были восстановлены недетерминированно.
  • Блок, который освобождает неуправляемые ресурсы. Этот блок выполняется независимо от значения параметра disposing.

Если вызов метода поступает из средства завершения, должен выполняться только код, который освобождает неуправляемые ресурсы. Реализатор несет ответственность за то, чтобы ложный путь не взаимодействовал с управляемыми объектами, которые могли быть очищены. Это важно, так как порядок удаления управляемых объектов сборщиком мусора во время завершения недетерминирован.

Каскадные вызовы удаления

Если класс владеет полем или свойством, и тип этого поля или свойства реализует IDisposable, сам содержащий класс также должен реализовать IDisposable. Класс, который создает экземпляр реализации IDisposable и сохраняет его в качестве члена экземпляра, также отвечает за очистку. Это помогает обеспечить, чтобы упомянутые удаляемые типы имели возможность детерминированно выполнить очистку с помощью метода Dispose. В следующем примере класс sealed (или NotInheritable в Visual Basic).

using System;

public sealed class Foo : IDisposable
{
    private readonly IDisposable _bar;

    public Foo()
    {
        _bar = new Bar();
    }

    public void Dispose() => _bar.Dispose();
}
Public NotInheritable Class Foo
    Implements IDisposable

    Private ReadOnly _bar As IDisposable

    Public Sub New()
        _bar = New Bar()
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        _bar.Dispose()
    End Sub
End Class

Совет

  • Если класс имеет поле или свойство IDisposable, но не его, то есть класс не создает объект, то класс не должен реализовывать IDisposable.
  • Существуют случаи, когда может потребоваться выполнить null-проверку в финализаторе (который включает метод Dispose(false), вызываемый финализатором). Одна из основных причин заключается в том, если вы не уверены, был ли экземпляр полностью инициализирован (например, исключение может быть создано в конструкторе).

Реализация шаблона удаления

Все неконечные классы (или классы Visual Basic, не изменённые как NotInheritable) должны считаться потенциальными базовыми классами, так как они могут быть унаследованы. Если вы реализуете шаблон удаления для любого потенциального базового класса, необходимо указать следующее:

  • Реализация Dispose, которая вызывает метод Dispose(bool).
  • Метод Dispose(bool), выполняющий фактическую очистку.
  • Класс, производный от SafeHandle, который упаковывает ваш неуправляемый ресурс (рекомендуется), или переопределение метода Object.Finalize. Класс SafeHandle предоставляет финализатор, поэтому вам не придется писать его самостоятельно.

Важный

Базовый класс может ссылаться только на управляемые объекты и реализовывать шаблон удаления. В таких случаях финализатор не требуется. Средство завершения требуется только в том случае, если вы напрямую ссылаетесь на неуправляемые ресурсы.

Ниже приведен общий пример реализации шаблона удаления для базового класса, использующего безопасный дескриптор.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class BaseClassWithSafeHandle : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
                _safeHandle = null;
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Public Class BaseClassWithSafeHandle
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If
    End Sub
End Class

Заметка

В предыдущем примере для иллюстрации шаблона используется объект SafeFileHandle; вместо этого можно использовать любой объект, производный от SafeHandle. Обратите внимание, что в примере не выполняется корректное создание экземпляра объекта SafeFileHandle.

Ниже приведен общий шаблон реализации шаблона удаления для базового класса, который переопределяет Object.Finalize.

using System;

public class BaseClassWithFinalizer : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    ~BaseClassWithFinalizer() => Dispose(false);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects)
            }

            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
            // TODO: set large fields to null
            _disposedValue = true;
        }
    }
}
Public Class BaseClassWithFinalizer
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects)
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override finalizer
            ' TODO: set large fields to null
            _disposedValue = True
        End If
    End Sub
End Class

Совет

В C# вы реализуете финализацию, используя финализатор, а не переопределяя Object.Finalize. В Visual Basic создаётся финализатор с помощью Protected Overrides Sub Finalize().

Реализация шаблона удаления для производного класса

Класс, производный от класса, реализующего интерфейс IDisposable, не должен реализовывать IDisposable, так как реализация базового класса IDisposable.Dispose наследуется производными классами. Вместо этого, чтобы очистить производный класс, укажите следующее:

  • Метод protected override void Dispose(bool), который переопределяет метод базового класса и выполняет фактическую очистку производного класса. Этот метод также должен вызывать метод base.Dispose(bool) (MyBase.Dispose(bool) в Visual Basic), передавая его состояние удаления (параметрbool disposing) в качестве аргумента.
  • Класс, производный от SafeHandle, который упаковывает неуправляемый ресурс (рекомендуется) или переопределение метода Object.Finalize. Класс SafeHandle предоставляет финализатор, который освобождает вас от необходимости писать собственный. Если вы предоставляете метод завершения, он должен вызвать перегрузку Dispose(bool) с аргументом false.

Ниже приведен пример общего шаблона реализации шаблона удаления для производного класса, использующего безопасный дескриптор:

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
                _safeHandle = null;
            }

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Public Class DerivedClassWithSafeHandle
    Inherits BaseClassWithSafeHandle

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If

        ' Call base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

Заметка

В предыдущем примере для иллюстрации шаблона используется объект SafeFileHandle; вместо этого можно использовать любой объект, производный от SafeHandle. Обратите внимание, что в примере не удается правильно создать экземпляр объекта SafeFileHandle.

Ниже приведен общий шаблон реализации шаблона удаления для производного класса, который переопределяет Object.Finalize:

public class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => Dispose(false);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects).
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.
            _disposedValue = true;
        }

        // Call the base class implementation.
        base.Dispose(disposing);
    }
}
Public Class DerivedClassWithFinalizer
    Inherits BaseClassWithFinalizer

    ' To detect redundant calls
    Private _disposedValue As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects).
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override a finalizer below.
            ' TODO: set large fields to null.
            _disposedValue = True
        End If

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

См. также