Реализация метода Dispose

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

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

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

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

Совет

Что касается внедрения зависимостей, при регистрации служб в ней IServiceCollectionвремя существования службы управляется неявно от вашего имени. Очистка и соответствующая IServiceProviderIHost очистка ресурсов оркестрации. В частности, реализации 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) класс должен иметь метод перегрузки.

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

  • publicnon-virtual (в Visual Basic) (NotOverridableIDisposable.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(Boolean)

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

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

    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.

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

     ' A block that frees unmanaged resources.
     
     If disposing Then
         ' Deterministic call…
         ' A conditional block that frees managed resources.    	
     End If
     
     disposed = True
End Sub

Внимание

Параметр disposing при вызове из метода завершения должен иметь значение false, а при вызове из метода IDisposable.Dispose — значение true. Иными словами, при детерминированном вызове он будет иметь значение true, а при недетерминированном вызове — false.

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

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

  • Блок, который освобождает неуправляемые ресурсы. Этот блок выполняется вне зависимости от значения параметра disposing.

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

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

    • Управляемые объекты, которые используют большие объемы памяти или дефицитные ресурсы. Назначайте ссылки на большие управляемые объекты в null, чтобы они чаще оказывались недоступными. Это освобождает их быстрее, чем если бы они были восстановлены недетерминированно.

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

Каскадные вызовы Dispose

Если класс владеет полем или свойством и его типом реализуется 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

См. также