Реализация метода 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)
класс должен иметь метод перегрузки.
Подписи методов:
public
non-virtual (в Visual Basic) (NotOverridable
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
Совет
- Если класс имеет 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