Реализуйте метод 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 BasicDispose(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