Dispose finalize что это за методы как используются в net
Перейти к содержимому

Dispose finalize что это за методы как используются в net

  • автор:

Реализация метода 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 non-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 
    • Если класс имеет 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 

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

    • Удаление служб
    • SuppressFinalize
    • IDisposable
    • IDisposable.Dispose
    • Microsoft.Win32.SafeHandles
    • System.Runtime.InteropServices.SafeHandle
    • Object.Finalize
    • Определение и использование классов и структур (C++/CLI)

    Совместная работа с нами на GitHub

    Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

    Object. Finalize Метод

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

    Позволяет объекту попытаться освободить ресурсы и выполнить другие операции очистки, перед тем как он будет уничтожен во время сборки мусора.

    !Object ()
    ~Object ();
    abstract member Finalize : unit -> unit override this.Finalize : unit -> unit
    Finalize ()

    Примеры

    В следующем примере проверяется, вызывается ли Finalize метод при уничтожении объекта, переопределенного Finalize . Обратите внимание, что в рабочем приложении метод будет переопределен для освобождения неуправляемых ресурсов, Finalize удерживаемых объектом. Также обратите внимание, что пример C# предоставляет деструктор вместо переопределения Finalize метода.

    using System; using System.Diagnostics; public class ExampleClass < Stopwatch sw; public ExampleClass() < sw = Stopwatch.StartNew(); Console.WriteLine("Instantiated object"); >public void ShowDuration() < Console.WriteLine("This instance of has been in existence for ", this, sw.Elapsed); > ~ExampleClass() < Console.WriteLine("Finalizing object"); sw.Stop(); Console.WriteLine("This instance of has been in existence for ", this, sw.Elapsed); > > public class Demo < public static void Main() < ExampleClass ex = new ExampleClass(); ex.ShowDuration(); >> // The example displays output like the following: // Instantiated object // This instance of ExampleClass has been in existence for 00:00:00.0011060 // Finalizing object // This instance of ExampleClass has been in existence for 00:00:00.0036294 
    open System.Diagnostics type ExampleClass() = let sw = Stopwatch.StartNew() do printfn "Instantiated object" member this.ShowDuration() = printfn $"This instance of has been in existence for " override this.Finalize() = printfn "Finalizing object" sw.Stop() printfn $"This instance of has been in existence for " let ex = ExampleClass() ex.ShowDuration() // The example displays output like the following: // Instantiated object // This instance of ExampleClass has been in existence for 00:00:00.0011060 // Finalizing object // This instance of ExampleClass has been in existence for 00:00:00.0036294 
    Imports System.Diagnostics Public Class ExampleClass Dim sw As StopWatch Public Sub New() sw = Stopwatch.StartNew() Console.WriteLine("Instantiated object") End Sub Public Sub ShowDuration() Console.WriteLine("This instance of has been in existence for ", Me, sw.Elapsed) End Sub Protected Overrides Sub Finalize() Console.WriteLine("Finalizing object") sw.Stop() Console.WriteLine("This instance of has been in existence for ", Me, sw.Elapsed) End Sub End Class Module Demo Public Sub Main() Dim ex As New ExampleClass() ex.ShowDuration() End Sub End Module ' The example displays output like the following: ' Instantiated object ' This instance of ExampleClass has been in existence for 00:00:00.0011060 ' Finalizing object ' This instance of ExampleClass has been in existence for 00:00:00.0036294 

    Дополнительные примеры переопределения Finalize метода см. в описании GC.SuppressFinalize метода.

    Комментарии

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

    Принцип работы завершения

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

    Если тип переопределяет Finalize метод, сборщик мусора добавляет запись для каждого экземпляра типа во внутреннюю структуру, называемую очередью завершения. Очередь завершения содержит записи для всех объектов в управляемой куче, код завершения которой должен выполняться, прежде чем сборщик мусора сможет освободить память. Затем сборщик мусора автоматически вызывает Finalize метод в следующих условиях:

    • После того как сборщик мусора обнаружил, что объект недоступен, если объект не был исключен из завершения путем вызова GC.SuppressFinalize метода.
    • Только в платформа .NET Framework во время завершения работы домена приложения, если только объект не будет исключен из завершения. Во время завершения работы завершается завершение даже объектов, которые по-прежнему доступны.

    Finalize автоматически вызывается только один раз в данном экземпляре, если объект не зарегистрирован повторно с помощью такого механизма, как GC.ReRegisterForFinalize и GC.SuppressFinalize метод впоследствии не был вызван.

    Finalize операции имеют следующие ограничения:

    • Точное время выполнения метода завершения не определено. Чтобы обеспечить детерминированный выпуск ресурсов для экземпляров класса, реализуйте Close метод или предоставьте реализацию IDisposable.Dispose .
    • Методы завершения двух объектов не гарантированно выполняются в определенном порядке, даже если один объект ссылается на другой. То есть, если объект A имеет ссылку на объект B и оба имеют методы завершения, объект Б, возможно, уже был завершен при запуске метода завершения объекта A.
    • Поток, в котором выполняется метод завершения, не указан.

    Метод Finalize может не выполняться до завершения или вообще не может выполняться в следующих исключительных случаях:

    • Если другой метод завершения блокируется бесконечно (переходит в бесконечный цикл, пытается получить блокировку, она никогда не сможет получить и т. д.). Так как среда выполнения пытается запустить методы завершения до завершения, другие методы завершения могут не вызываться, если метод завершения блокируется на неопределенный срок.
    • Если процесс завершается без предоставления среде выполнения возможности очистки. В этом случае первое уведомление среды выполнения о завершении процесса является уведомлением DLL_PROCESS_DETACH.

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

    Если Finalize или переопределение Finalize вызывает исключение, а среда выполнения не размещается приложением, которое переопределяет политику по умолчанию, среда выполнения завершает процесс, и try / finally активные блоки или методы завершения не выполняются. Такое поведение гарантирует целостность процессов, если метод завершения не может освободить или уничтожить ресурсы.

    Переопределение метода Finalize

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

    SafeHandle Если объект доступен, который упаковывает неуправляемый ресурс, рекомендуется реализовать шаблон удаления с безопасным дескриптором и не переопределитьFinalize. Дополнительные сведения см. в разделе «Альтернатива SafeHandle «.

    Метод Object.Finalize не выполняет никаких действий по умолчанию, но при необходимости следует переопределить Finalize только при необходимости и освободить неуправляемые ресурсы. Восстановление памяти, как правило, занимает гораздо больше времени, если выполняется операция завершения, так как для этого требуется по крайней мере две сборки мусора. Кроме того, следует переопределить Finalize метод только для ссылочных типов. Среда CLR завершает только ссылочные типы. Он игнорирует методы завершения для типов значений.

    Область действия Object.Finalize метода . protected При переопределении метода в классе следует поддерживать эту ограниченную область. Сохраняя Finalize защиту метода, пользователи приложения не могут напрямую вызывать метод объекта Finalize .

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

    Компилятор C# не позволяет переопределить Finalize метод. Вместо этого вы предоставляете метод завершения, реализуя деструктор для класса. Деструктор C# автоматически вызывает деструктор базового класса.

    Visual C++ также предоставляет собственный синтаксис для реализации Finalize метода. Дополнительные сведения см. в разделе «Деструкторы и методы завершения» статьи «Практическое руководство. Определение и использование классов и структур (C++/CLI)».

    Поскольку сборка мусора не детерминирована, вы не знаете точно, когда сборщик мусора выполняет завершение. Чтобы немедленно освободить ресурсы, можно также реализовать шаблон удаления и IDisposable интерфейс. Реализация IDisposable.Dispose может вызываться потребителями класса для освобождения неуправляемых ресурсов, и вы можете использовать Finalize метод для освобождения неуправляемых ресурсов в случае, Dispose если метод не вызывается.

    Finalize может выполнять почти любое действие, включая восстановление объекта (т. е. повторного доступа к объекту) после очистки во время сборки мусора. Однако объект можно восстановить только один раз; Finalize не может вызываться для восстановленных объектов во время сборки мусора.

    Альтернативный вариант с использованием SafeHandle

    Создание надежных методов завершения часто сложно, так как нельзя делать предположения о состоянии приложения, а также поскольку необработанных системных исключений, таких как OutOfMemoryException и StackOverflowException завершение метода завершения. Вместо реализации метода завершения для класса для освобождения неуправляемых ресурсов можно использовать объект, производный от System.Runtime.InteropServices.SafeHandle класса, для упаковки неуправляемых ресурсов, а затем реализовать шаблон удаления без метода завершения. Платформа .NET Framework предоставляет следующие классы в Microsoft.Win32 пространстве имен, производных отSystem.Runtime.InteropServices.SafeHandle:

    • SafeFileHandle — это класс-оболочка для дескриптора файла.
    • SafeMemoryMappedFileHandle — это класс-оболочка для дескрипторов файлов, сопоставленных с памятью.
    • SafeMemoryMappedViewHandle — это класс-оболочка для указателя на блок неуправляемой памяти.
    • SafeNCryptKeyHandle, SafeNCryptProviderHandleи SafeNCryptSecretHandle являются классами-оболочками для криптографических дескрипторов.
    • SafePipeHandle — это класс-оболочка для дескрипторов канала.
    • SafeRegistryHandle — это класс-оболочка для дескриптора раздела реестра.
    • SafeWaitHandle — это класс-оболочка для дескриптора ожидания.

    В следующем примере используется шаблон удаления с безопасными дескрипторами вместо переопределения Finalize метода. Он определяет FileAssociation класс, который упаковывает сведения о реестре о приложении, которое обрабатывает файлы с определенным расширением файла. Два дескриптора реестра, возвращаемые в качестве out параметров, Windows вызовы функций RegOpenKeyEx передаются конструкторуSafeRegistryHandle. Затем защищенный Dispose метод типа вызывает SafeRegistryHandle.Dispose метод, чтобы освободить эти два дескриптора.

    using Microsoft.Win32.SafeHandles; using System; using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; public class FileAssociationInfo : IDisposable < // Private variables. private String ext; private String openCmd; private String args; private SafeRegistryHandle hExtHandle, hAppIdHandle; // Windows API calls. [DllImport("advapi32.dll", CharSet= CharSet.Auto, SetLastError=true)] private static extern int RegOpenKeyEx(IntPtr hKey, String lpSubKey, int ulOptions, int samDesired, out IntPtr phkResult); [DllImport("advapi32.dll", CharSet= CharSet.Unicode, EntryPoint = "RegQueryValueExW", SetLastError=true)] private static extern int RegQueryValueEx(IntPtr hKey, string lpValueName, int lpReserved, out uint lpType, string lpData, ref uint lpcbData); [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegSetValueEx(IntPtr hKey, [MarshalAs(UnmanagedType.LPStr)] string lpValueName, int Reserved, uint dwType, [MarshalAs(UnmanagedType.LPStr)] string lpData, int cpData); [DllImport("advapi32.dll", SetLastError=true)] private static extern int RegCloseKey(UIntPtr hKey); // Windows API constants. private const int HKEY_CLASSES_ROOT = unchecked((int) 0x80000000); private const int ERROR_SUCCESS = 0; private const int KEY_QUERY_VALUE = 1; private const int KEY_SET_VALUE = 0x2; private const uint REG_SZ = 1; private const int MAX_PATH = 260; public FileAssociationInfo(String fileExtension) < int retVal = 0; uint lpType = 0; if (!fileExtension.StartsWith(".")) fileExtension = "." + fileExtension; ext = fileExtension; IntPtr hExtension = IntPtr.Zero; // Get the file extension value. retVal = RegOpenKeyEx(new IntPtr(HKEY_CLASSES_ROOT), fileExtension, 0, KEY_QUERY_VALUE, out hExtension); if (retVal != ERROR_SUCCESS) throw new Win32Exception(retVal); // Instantiate the first SafeRegistryHandle. hExtHandle = new SafeRegistryHandle(hExtension, true); string appId = new string(' ', MAX_PATH); uint appIdLength = (uint) appId.Length; retVal = RegQueryValueEx(hExtHandle.DangerousGetHandle(), String.Empty, 0, out lpType, appId, ref appIdLength); if (retVal != ERROR_SUCCESS) throw new Win32Exception(retVal); // We no longer need the hExtension handle. hExtHandle.Dispose(); // Determine the number of characters without the terminating null. appId = appId.Substring(0, (int) appIdLength / 2 - 1) + @"\shell\open\Command"; // Open the application identifier key. string exeName = new string(' ', MAX_PATH); uint exeNameLength = (uint) exeName.Length; IntPtr hAppId; retVal = RegOpenKeyEx(new IntPtr(HKEY_CLASSES_ROOT), appId, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, out hAppId); if (retVal != ERROR_SUCCESS) throw new Win32Exception(retVal); // Instantiate the second SafeRegistryHandle. hAppIdHandle = new SafeRegistryHandle(hAppId, true); // Get the executable name for this file type. string exePath = new string(' ', MAX_PATH); uint exePathLength = (uint) exePath.Length; retVal = RegQueryValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, out lpType, exePath, ref exePathLength); if (retVal != ERROR_SUCCESS) throw new Win32Exception(retVal); // Determine the number of characters without the terminating null. exePath = exePath.Substring(0, (int) exePathLength / 2 - 1); // Remove any environment strings. exePath = Environment.ExpandEnvironmentVariables(exePath); int position = exePath.IndexOf('%'); if (position >= 0) < args = exePath.Substring(position); // Remove command line parameters ('%0', etc.). exePath = exePath.Substring(0, position).Trim(); >openCmd = exePath; > public String Extension < get < return ext; >> public String Open < get < return openCmd; >set < if (hAppIdHandle.IsInvalid | hAppIdHandle.IsClosed) throw new InvalidOperationException("Cannot write to registry key."); if (! File.Exists(value)) < string message = String.Format("'' does not exist", value); throw new FileNotFoundException(message); > string cmd = value + " %1"; int retVal = RegSetValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, REG_SZ, value, value.Length + 1); if (retVal != ERROR_SUCCESS) throw new Win32Exception(retVal); > > public void Dispose() < Dispose(disposing: true); GC.SuppressFinalize(this); >protected void Dispose(bool disposing) < // Ordinarily, we release unmanaged resources here; // but all are wrapped by safe handles. // Release disposable objects. if (disposing) < if (hExtHandle != null) hExtHandle.Dispose(); if (hAppIdHandle != null) hAppIdHandle.Dispose(); >> > 
    open Microsoft.Win32.SafeHandles open System open System.ComponentModel open System.IO open System.Runtime.InteropServices // Windows API constants. let HKEY_CLASSES_ROOT = 0x80000000 let ERROR_SUCCESS = 0 let KEY_QUERY_VALUE = 1 let KEY_SET_VALUE = 0x2 let REG_SZ = 1u let MAX_PATH = 260 // Windows API calls. [] extern int RegOpenKeyEx(nativeint hKey, string lpSubKey, int ulOptions, int samDesired, nativeint& phkResult) [] extern int RegQueryValueEx(nativeint hKey, string lpValueName, int lpReserved, uint& lpType, string lpData, uint& lpcbData) [] extern int RegSetValueEx(nativeint hKey, [] string lpValueName, int Reserved, uint dwType, [] string lpData, int cpData) [] extern int RegCloseKey(unativeint hKey) type FileAssociationInfo(fileExtension: string) = // Private values. let ext = if fileExtension.StartsWith "." |> not then "." + fileExtension else fileExtension let mutable args = "" let mutable hAppIdHandle = Unchecked.defaultof let mutable hExtHandle = Unchecked.defaultof let openCmd = let mutable lpType = 0u let mutable hExtension = 0n // Get the file extension value. let retVal = RegOpenKeyEx(nativeint HKEY_CLASSES_ROOT, fileExtension, 0, KEY_QUERY_VALUE, &hExtension) if retVal <> ERROR_SUCCESS then raise (Win32Exception retVal) // Instantiate the first SafeRegistryHandle. hExtHandle ERROR_SUCCESS then raise (Win32Exception retVal) // We no longer need the hExtension handle. hExtHandle.Dispose() // Determine the number of characters without the terminating null. let appId = appId.Substring(0, int appIdLength / 2 - 1) + @"\shell\open\Command" // Open the application identifier key. let exeName = String(' ', MAX_PATH) let exeNameLength = uint exeName.Length let mutable hAppId = 0n let retVal = RegOpenKeyEx(nativeint HKEY_CLASSES_ROOT, appId, 0, KEY_QUERY_VALUE ||| KEY_SET_VALUE, &hAppId) if retVal <> ERROR_SUCCESS then raise (Win32Exception retVal) // Instantiate the second SafeRegistryHandle. hAppIdHandle ERROR_SUCCESS then raise (Win32Exception retVal) // Determine the number of characters without the terminating null. let exePath = exePath.Substring(0, int exePathLength / 2 - 1) // Remove any environment strings. |> Environment.ExpandEnvironmentVariables let position = exePath.IndexOf '%' if position >= 0 then args ' does not exist") let cmd = value + " %1" let retVal = RegSetValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, REG_SZ, value, value.Length + 1) if retVal <> ERROR_SUCCESS then raise (Win32Exception retVal) member this.Dispose() = this.Dispose true GC.SuppressFinalize this member _.Dispose(disposing) = // Ordinarily, we release unmanaged resources here // but all are wrapped by safe handles. // Release disposable objects. if disposing then if hExtHandle <> null then hExtHandle.Dispose() if hAppIdHandle <> null then hAppIdHandle.Dispose() interface IDisposable with member this.Dispose() = this.Dispose() 
    Imports Microsoft.Win32.SafeHandles Imports System.ComponentModel Imports System.IO Imports System.Runtime.InteropServices Imports System.Text Public Class FileAssociationInfo : Implements IDisposable ' Private variables. Private ext As String Private openCmd As String Private args As String Private hExtHandle, hAppIdHandle As SafeRegistryHandle ' Windows API calls. Private Declare Unicode Function RegOpenKeyEx Lib"advapi32.dll" _ Alias "RegOpenKeyExW" (hKey As IntPtr, lpSubKey As String, _ ulOptions As Integer, samDesired As Integer, _ ByRef phkResult As IntPtr) As Integer Private Declare Unicode Function RegQueryValueEx Lib "advapi32.dll" _ Alias "RegQueryValueExW" (hKey As IntPtr, _ lpValueName As String, lpReserved As Integer, _ ByRef lpType As UInteger, lpData As String, _ ByRef lpcbData As UInteger) As Integer Private Declare Function RegSetValueEx Lib "advapi32.dll" _ (hKey As IntPtr, _ lpValueName As String, _ reserved As Integer, dwType As UInteger, _ lpData As String, _ cpData As Integer) As Integer Private Declare Function RegCloseKey Lib "advapi32.dll" _ (hKey As IntPtr) As Integer ' Windows API constants. Private Const HKEY_CLASSES_ROOT As Integer = &h80000000 Private Const ERROR_SUCCESS As Integer = 0 Private Const KEY_QUERY_VALUE As Integer = 1 Private Const KEY_SET_VALUE As Integer = &h2 Private REG_SZ As UInteger = 1 Private Const MAX_PATH As Integer = 260 Public Sub New(fileExtension As String) Dim retVal As Integer = 0 Dim lpType As UInteger = 0 If Not fileExtension.StartsWith(".") Then fileExtension = "." + fileExtension End If ext = fileExtension Dim hExtension As IntPtr = IntPtr.Zero ' Get the file extension value. retVal = RegOpenKeyEx(New IntPtr(HKEY_CLASSES_ROOT), fileExtension, 0, KEY_QUERY_VALUE, hExtension) if retVal <> ERROR_SUCCESS Then Throw New Win32Exception(retVal) End If ' Instantiate the first SafeRegistryHandle. hExtHandle = New SafeRegistryHandle(hExtension, True) Dim appId As New String(" "c, MAX_PATH) Dim appIdLength As UInteger = CUInt(appId.Length) retVal = RegQueryValueEx(hExtHandle.DangerousGetHandle(), String.Empty, _ 0, lpType, appId, appIdLength) if retVal <> ERROR_SUCCESS Then Throw New Win32Exception(retVal) End If ' We no longer need the hExtension handle. hExtHandle.Dispose() ' Determine the number of characters without the terminating null. appId = appId.Substring(0, CInt(appIdLength) \ 2 - 1) + "\shell\open\Command" ' Open the application identifier key. Dim exeName As New string(" "c, MAX_PATH) Dim exeNameLength As UInteger = CUInt(exeName.Length) Dim hAppId As IntPtr retVal = RegOpenKeyEx(New IntPtr(HKEY_CLASSES_ROOT), appId, 0, KEY_QUERY_VALUE Or KEY_SET_VALUE, hAppId) If retVal <> ERROR_SUCCESS Then Throw New Win32Exception(retVal) End If ' Instantiate the second SafeRegistryHandle. hAppIdHandle = New SafeRegistryHandle(hAppId, True) ' Get the executable name for this file type. Dim exePath As New string(" "c, MAX_PATH) Dim exePathLength As UInteger = CUInt(exePath.Length) retVal = RegQueryValueEx(hAppIdHandle.DangerousGetHandle(), _ String.Empty, 0, lpType, exePath, exePathLength) If retVal <> ERROR_SUCCESS Then Throw New Win32Exception(retVal) End If ' Determine the number of characters without the terminating null. exePath = exePath.Substring(0, CInt(exePathLength) \ 2 - 1) exePath = Environment.ExpandEnvironmentVariables(exePath) Dim position As Integer = exePath.IndexOf("%"c) If position >= 0 Then args = exePath.Substring(position) ' Remove command line parameters ('%0', etc.). exePath = exePath.Substring(0, position).Trim() End If openCmd = exePath End Sub Public ReadOnly Property Extension As String Get Return ext End Get End Property Public Property Open As String Get Return openCmd End Get Set If hAppIdHandle.IsInvalid Or hAppIdHandle.IsClosed Then Throw New InvalidOperationException("Cannot write to registry key.") End If If Not File.Exists(value) Then Dim message As String = String.Format("'' does not exist", value) Throw New FileNotFoundException(message) End If Dim cmd As String = value + " %1" Dim retVal As Integer = RegSetValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, REG_SZ, value, value.Length + 1) If retVal <> ERROR_SUCCESS Then Throw New Win32Exception(retVal) End If End Set End Property Public Sub Dispose() _ Implements IDisposable.Dispose Dispose(disposing:=True) GC.SuppressFinalize(Me) End Sub Protected Sub Dispose(disposing As Boolean) ' Ordinarily, we release unmanaged resources here ' but all are wrapped by safe handles. ' Release disposable objects. If disposing Then If hExtHandle IsNot Nothing Then hExtHandle.Dispose() If hAppIdHandle IsNot Nothing Then hAppIdHandle.Dispose() End If End Sub End Class 

    Применяется к

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

    • SuppressFinalize(Object)
    • ReRegisterForFinalize(Object)
    • WaitForPendingFinalizers()
    • WeakReference

    Высвобождаемые объекты

    Методы финализации могут применяться для освобождения неуправляемых ресурсов при активизации процесса сборки мусора. Однако многие неуправляемые объекты являются «ценными элементами» (например, низкоуровневые соединения с базой данных или файловые дескрипторы) и часто выгоднее освобождать их как можно раньше, еще до наступления момента сборки мусора. Поэтому вместо переопределения Finalize() в качестве альтернативного варианта также можно реализовать в классе интерфейс IDisposable, который имеет единственный метод по имени Dispose():

    public interface IDisposable

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

    Интерфейс IDisposable может быть реализован как в классах, так и в структурах (в отличие от метода Finalize(), который допускается переопределять только в классах), потому что метод Dispose() вызывается пользователем объекта (а не сборщиком мусора). Рассмотрим пример использования этого интерфейса:

    using System; namespace ConsoleApplication1 < // Данный класс реализует интерейс IDisposable class FinalizeObject : IDisposable < public int id < get; set; >public FinalizeObject(int id) < this.id = id; >// Реализуем метод Dispose() public void Dispose() < Console.WriteLine("Высвобождение объекта!"); >> class Program < static void Main(string[] args) < FinalizeObject obj = new FinalizeObject(4); obj.Dispose(); Console.Read(); >> >

    Обратите внимание, что метод Dispose() отвечает не только за освобождение неуправляемых ресурсов типа, но и за вызов аналогичного метода в отношении любых других содержащихся в нем высвобождаемых объектов. В отличие от Finalize(), в нем вполне допустимо взаимодействовать с другими управляемыми объектами. Объясняется это очень просто: сборщик мусора не имеет понятия об интерфейсе IDisposable и потому никогда не будет вызывать метод Dispose(). Следовательно, при вызове данного метода пользователем объект будет все еще существовать в управляемой куче и иметь доступ ко всем остальным находящимся там объектам.

    Этот пример раскрывает еще одно правило относительно работы с подвергаемыми сборке мусора типами: для любого создаваемого напрямую объекта, если он поддерживает интерфейс IDisposable, следует всегда вызывать метод Dispose(). Необходимо исходить из того, что в случае, если разработчик класса решил реализовать метод Dispose(), значит, классу надлежит выполнять какую-то очистку.

    Повторное использование ключевого слова using в C#

    При работе с управляемым объектом, который реализует интерфейс IDisposable, довольно часто требуется применять структурированную обработку исключений, гарантируя, что метод Dispose() типа будет вызываться даже в случае возникновения какого-то исключения:

    FinalizeObject obj = new FinalizeObject(4); try < // Выполнение необходимых операций >finally

    Хотя это является замечательными примером «безопасного программирования», истина состоит в том, что очень немногих разработчиков прельщает перспектива заключать каждый очищаемый тип в блок try/finally лишь для того, чтобы гарантировать вызов метода Dispose(). Для достижения аналогичного результата, но гораздо менее громоздким образом, в C# поддерживается специальный фрагмент синтаксиса, который выглядит следующим образом:

    using (FinalizeObject obj = new FinalizeObject(4)) < // Необходимые действия >

    Если теперь просмотреть CIL-код этого метода Main() с помощью утилиты ildasm.ехе, то обнаружится, что синтаксис using в таких случаях на самом деле расширяется до логики try/finally, которая включает в себя и ожидаемый вызов Dispose():

    CIL-код высвобождаемых объектов C#

    Хотя применение такого синтаксиса действительно избавляет от необходимости вручную помещать высвобождаемые объекты в рамки try/finally, в настоящее время, к сожалению, ключевое слово using в C# имеет двойное значение (поскольку служит и для добавления ссылки на пространства имен, и для вызова метода Dispose()). Тем не менее, при работе с типами .NET, которые поддерживают интерфейс IDisposable, данная синтаксическая конструкция будет гарантировать автоматический вызов метода Dispose() в отношении соответствующего объекта при выходе из блока using.

    Кроме того, в контексте using допускается объявлять несколько объектов одного и того же типа. Как не трудно догадаться, в таком случае компилятор будет вставлять код с вызовом Dispose() для каждого объявляемого объекта.

    Есть ли деструктор в C#?

    Итак, начнем с того, что существует два типа ресурсов — управляемые и неуправляемые. Насчет первых можно совсем не беспокоиться — ими займется сборщик мусора. А вот с неуправляемыми ресурсами дела обстоят куда сложнее. Наш мусорщик не знает, как их освободить, поэтому нам самим приходится заниматься этим вопросом.

    Чем отличается деструктор от финализатора

    Деструктор — это метод для деинициализации объекта. Здесь важно упомянуть о такой штуке, как deterministic destruction. То есть мы точно знаем, когда объект будет удален. Чаще всего это происходит, когда заканчивается область видимости объекта, или программист явно освобождает память(в с/с++).

    А вот определение финализатора из Википедии.

    Финализатор — это метод класса, который автоматически вызывается средой исполнения в промежутке времени между моментом, когда объект этого класса опознаётся сборщиком мусора как неиспользуемый, и моментом удаления объекта (освобождения занимаемой им памяти). Это уже обратная штука — nondeterministic destruction.
    То есть главный минус финализатора в том, что мы не знаем, когда он вызовется. Это может создать огромное количество проблем.

    Но деструктор и финализатор в .NET это не то же самое, что просто деструктор и финализатор в обычном мире.

    В Visual C# есть финализатор, который создается с помощью синтаксиса создания деструктора в С++, и который некоторыми даже называется как деструктор, хотя таковым не является. Выполнен он через метод Finalize, который нельзя переопределить(в C# нет, но в VB можно), поэтому и приходится использовать синтаксис деструктора через тильду(~ClassName). И только при компиляции в IL, компилятор называет его Finalize. При выполнении этот метод также вызывает финализатор родительского класса.

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

    Если заглянуть в спецификацию языка программирования C#(4.0 на данный момент), то там слово «finalizer» ни разу не встречается. Ну это еще можно объяснить. Финализатор тесно связан со сборщиком мусора, который в свою очередь является частью среды выполнения(CLR в нашем случае), но не самого языка программирования.

    Теперь пойдем еще дальше и залезем в спецификацию CLI(ECMA-335). Здесь вот что написано.

    A class definition that creates an object type can supply an instance method (called a finalizer) to be called
    when an instance of the class is no longer reachable.

    Это, несомненно, описание финализатора, хотя на мой взгляд, немного неточное.

    Далее, идем на msdn. Ни в одной статье не встречается слово finalizer в чистом виде — зато почти всегда используется слово деструктор. Возникает закономерный вопрос — почему люди называют деструктором то, что им не является. Получается, что майкрософтовские разработчики сознательно поменяли значение этого слова. И вот почему.

    Мы знаем, что в Visual C# nondeterministic destruction. Это значит, что даже если область видимости объекта закончилась, и сборщик мусора понял, что можно освобождать занимаемую им память, не факт, что это произойдет незамедлительно. То есть это чистой воды финализатор. Так как он использует синтаксис, который во всех языках используется для деструктора, можно предположить, что в Visual C# нет способа определить деструктор(в общем понимании). Это значит, что его просто-напросто нет. Да, необходимости в нем тоже особой нет, но нужно согласиться с тем, что и самого деструктора в Visual C# быть не может.

    Вывод — либо я идиот, и совсем все неправильно понял(а вероятность этого довольно высока), либо нужно смириться с этим, и называть деструктором то, что внешне на него похоже(тильда, привет), но по сути является финализатором. Надо же как-то жить в этом мире.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *