Реализация метода 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 ресурс будет освобожден один и только один раз.

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:

Dispose() и Dispose(bool)

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

Сигнатуры методов:

  • publicnon-virtual (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(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

Совет

В некоторых случаях может потребоваться выполнить 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;

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);

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

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

Class BaseClassWithSafeHandle : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
        End If

        disposed = True
    End Sub
End Class

Примечание

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

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

using System;

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;
        }
    }
}
Class BaseClassWithFinalizer : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    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;

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();
            }

            _disposedValue = true;
        }

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

Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
            ' Free any other managed objects here.
            '
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

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

Примечание

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

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

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

    ~DerivedClassWithFinalizer() => this.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);
    }
}
Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

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

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

См. также раздел