From 850092efe6c983c48686fe8a82dffc5b2ac3c345 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sat, 8 Mar 2025 19:44:38 +0100 Subject: [PATCH 1/5] `implementing-dispose.md`: Better illustration of SafeHandles --- .../implementing-dispose.md | 94 ++++++++++++------- .../system.idisposable/cs/Program.cs | 13 ++- .../system.idisposable/cs/base1.cs | 26 ++--- .../system.idisposable/cs/base2.cs | 30 ++++-- .../system.idisposable/cs/derived1.cs | 22 ++--- .../system.idisposable/cs/derived2.cs | 17 ++-- .../system.idisposable/cs/safe.cs | 61 ++++++++++++ .../system.idisposable/vb/Program.vb | 9 +- .../system.idisposable/vb/base1.vb | 28 +++--- .../system.idisposable/vb/base2.vb | 36 ++++--- .../system.idisposable/vb/derived1.vb | 25 +++-- .../system.idisposable/vb/derived2.vb | 22 +++-- .../system.idisposable/vb/safe.vb | 59 ++++++++++++ 13 files changed, 314 insertions(+), 128 deletions(-) create mode 100644 samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs create mode 100644 samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb diff --git a/docs/standard/garbage-collection/implementing-dispose.md b/docs/standard/garbage-collection/implementing-dispose.md index 78f2294f24d62..a56e3cca95eb9 100644 --- a/docs/standard/garbage-collection/implementing-dispose.md +++ b/docs/standard/garbage-collection/implementing-dispose.md @@ -15,7 +15,7 @@ ms.topic: how-to The method is primarily implemented to release unmanaged resources. When working with instance members that are implementations, it's common to cascade calls. There are other reasons for implementing , for example, to free memory that was allocated, remove an item that was added to a collection, or signal the release of a lock that was acquired. -The [.NET garbage collector](index.md) doesn't allocate or release unmanaged memory. The pattern for disposing an object, referred to as the dispose pattern, imposes order on the lifetime of an object. The dispose pattern is used for objects that implement the interface. This pattern is common when interacting with file and pipe handles, registry handles, wait handles, or pointers to blocks of unmanaged memory, because the garbage collector is unable to reclaim unmanaged objects. +The [.NET garbage collector](index.md) doesn't allocate or release unmanaged memory. The pattern for disposing an object, referred to as the [dispose pattern](../design-guidelines/dispose-pattern.md), imposes order on the lifetime of an object. The dispose pattern is used for objects that implement the interface. This pattern is common when interacting with file and pipe handles, registry handles, wait handles, or pointers to blocks of unmanaged memory, because the garbage collector is unable to reclaim unmanaged objects. To help ensure that resources are always cleaned up appropriately, a method should be idempotent, such that it's callable multiple times without throwing an exception. Furthermore, subsequent invocations of should do nothing. @@ -23,23 +23,18 @@ The code example provided for the objects instead of implementing a finalizer. - -A is an abstract managed type that wraps an that identifies an unmanaged resource. On Windows it might identify a handle, and on Unix, a file descriptor. The `SafeHandle` provides all of the logic necessary to ensure that this resource is released once and only once, either when the `SafeHandle` is disposed of or when all references to the `SafeHandle` have been dropped and the `SafeHandle` instance is finalized. +## Cascade dispose calls -The is an abstract base class. Derived classes provide specific instances for different kinds of handle. These derived classes validate what values for the are considered invalid and how to actually free the handle. For example, derives from `SafeHandle` to wrap `IntPtrs` that identify open file handles/descriptors, and overrides its method to close it (via the `close` function on Unix or `CloseHandle` function on Windows). Most APIs in .NET libraries that create an unmanaged resource wraps it in a `SafeHandle` and return that `SafeHandle` to you as needed, rather than handing back the raw pointer. In situations where you interact with an unmanaged component and get an `IntPtr` for an unmanaged resource, you can create your own `SafeHandle` type to wrap it. As a result, few non-`SafeHandle` types need to implement finalizers. Most disposable pattern implementations only end up wrapping other managed resources, some of which may be `SafeHandle` objects. +If your class owns an instance of another type that implements storing it in a field or property, the containing class itself should also implement . Typically a class that instantiates an implementation and stores it as an instance member is also responsible for its cleanup. This helps ensure that the referenced disposable types are given the opportunity to deterministically perform cleanup through the method. In the following example, the class is `sealed` (or `NotInheritable` in Visual Basic). -The following derived classes in the namespace provide safe handles. +:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/Foo.cs"::: +:::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/Foo.vb"::: -| Class | Resources it holds | -| - | - | -|

| Files, memory mapped files, and pipes | -| | Memory views | -|

|Cryptography constructs | -| | Registry keys | -| | Wait handles | +> [!TIP] +> +> - If your class has an field or property but doesn't *own* it, then the class doesn't need to implement . +> - It's the developers' responsibility to define a clean ownership model that ensures the disposal of all instances in an object graph. Typically a class creating and storing the child object also becomes the owner, but in some cases the ownership can be transferred to another type. +> - There are cases when you may want to perform `null`-checking in a finalizer (which includes the `Dispose(false)` method invoked by a finalizer). One of the primary reasons is if you're unsure whether the instance got fully initialized (for example, an exception might be thrown in a constructor). ## Dispose() and Dispose(bool) @@ -81,42 +76,44 @@ The body of the method consists of three blocks of code: If the method call comes from a finalizer, only the code that frees unmanaged resources should execute. The implementer is responsible for ensuring that the false path doesn't interact with managed objects that may have been disposed. This is important because the order in which the garbage collector disposes managed objects during finalization is nondeterministic. -## Cascade dispose calls - -If your class owns a field or property and its type implements , the containing class itself should also implement . A class that instantiates an implementation and stores it as an instance member is also responsible for its cleanup. This helps ensure that the referenced disposable types are given the opportunity to deterministically perform cleanup through the method. In the following example, the class is `sealed` (or `NotInheritable` in Visual Basic). - -:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/Foo.cs"::: -:::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/Foo.vb"::: - -> [!TIP] -> -> - If your class has an field or property but doesn't *own* it, meaning the class doesn't create the object, then the class doesn't need to implement . -> - There are cases when you may want to perform `null`-checking in a finalizer (which includes the `Dispose(false)` method invoked by a finalizer). One of the primary reasons is if you're unsure whether the instance got fully initialized (for example, an exception might be thrown in a constructor). - ## Implement the dispose pattern -All non-sealed classes (or Visual Basic classes not modified as `NotInheritable`) should be considered a potential base class, because they could be inherited. If you implement the dispose pattern for any potential base class, you must provide the following: +All non-sealed classes (or Visual Basic classes not modified as `NotInheritable`) should be considered a potential base class, because they could be inherited. If you implement the dispose pattern for any potential base class, you must add the following methods to your class: - A implementation that calls the `Dispose(bool)` method. - A `Dispose(bool)` method that performs the actual cleanup. -- Either a class derived from that wraps your unmanaged resource (recommended), or an override to the method. The class provides a finalizer, so you don't have to write one yourself. +- If your class deals with unmanaged resources either provide an override to the method, or wrap the unmanaged resource in a . + +Either a class derived from that wraps your unmanaged resource (recommended) > [!IMPORTANT] -> It's possible for a base class to only reference managed objects and implement the dispose pattern. In these cases, a finalizer is unnecessary. A finalizer is only required if you directly reference unmanaged resources. +> A finalizer (a override) is only required if you directly reference unmanaged resources. This is only needed in a few advanced scenarios, therefore in most cases finalizers can be avoided. +> +> - It's possible for a class to only reference managed objects and implement the dispose pattern. There is no need to implement a finalizer in such cases and the base class will always call `Dispose(bool)` with `disposing: true`. The reason the dispose pattern is still encouraged is that it enables sub classes to implement a finalizer and call `Dispose(disposing: false)` on the base class. +> - In the rare cases when it's necessary to deal with unmanaged resources (which are typically represented by handles), **we strongly recommend to wrap the handle into a **. The provides a finalizer so you don't have to write one yourself. See the [Safe handles](#safe-handles) paragraph for more information. + +### Base class with managed resources -Here's a general example of implementing the dispose pattern for a base class that uses a safe handle. +Here's a general example of implementing the dispose pattern for a base class that only owns managed resources. :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb"::: > [!NOTE] -> The previous example uses a object to illustrate the pattern; any object derived from could be used instead. Note that the example does not properly instantiate its object. +> The previous example uses a **dummy** object to illustrate the pattern. Any could be used instead. + +### Base class with unmanaged resources -Here's the general pattern for implementing the dispose pattern for a base class that overrides . +Here's an example for implementing the dispose pattern for a base class that overrides in order to clean up unmanaged resources it owns. The example also demonstrates a way to implement `Dispose(bool)` in a thread-safe manner. Synchronization might be critical when dealing with unmanaged resources in a multi-threaded application. :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb"::: +> [!NOTE] +> +> - The previous example uses to allocate 10 bytes on the unmanaged heap in the constructor and free the buffer in `Dispose(bool)` by calling . This is a **dummy** allocation for illustrational purpose. +> - The approach in the previous example might lead to complex code and it's discouraged. It is recommended to avoid implementing a finalizer by [wrapping unmanaged resources into instances](#implement-the-dispose-pattern-using-a-custom-safe-handle). + > [!TIP] > In C#, you implement a finalization by providing a [finalizer](../../csharp/programming-guide/classes-and-structs/finalizers.md), not by overriding . In Visual Basic, you create a finalizer with `Protected Overrides Sub Finalize()`. @@ -140,6 +137,36 @@ Here's the general pattern for implementing the dispose pattern for a derived cl :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb"::: +## Safe handles + +Writing code for an object's finalizer is a complex task that can cause problems if not done correctly. Therefore, we recommend that you construct objects instead of implementing a finalizer. + +A is an abstract managed type that wraps an that identifies an unmanaged resource. On Windows it might identify a handle, and on Unix, a file descriptor. The `SafeHandle` provides all of the logic necessary to ensure that this resource is released once and only once, either when the `SafeHandle` is disposed of or when all references to the `SafeHandle` have been dropped and the `SafeHandle` instance is finalized. + +The is an abstract base class. Derived classes provide specific instances for different kinds of handle. These derived classes validate what values for the are considered invalid and how to actually free the handle. For example, derives from `SafeHandle` to wrap `IntPtrs` that identify open file handles/descriptors, and overrides its method to close it (via the `close` function on Unix or `CloseHandle` function on Windows). Most APIs in .NET libraries that create an unmanaged resource wraps it in a `SafeHandle` and return that `SafeHandle` to you as needed, rather than handing back the raw pointer. In situations where you interact with an unmanaged component and get an `IntPtr` for an unmanaged resource, you can create your own `SafeHandle` type to wrap it. As a result, few non-`SafeHandle` types need to implement finalizers. Most disposable pattern implementations only end up wrapping other managed resources, some of which may be `SafeHandle` objects. + +### Implement the dispose pattern using a custom safe handle + +The following code demonstrates how to handle unmanaged resources by implementing a . It's behavior is equivalent to the code [in the previous example](#base-class-with-unmanaged-resources) demonstrating unmanaged resource handling, however this approach is considered to be safer: + +- There is no need to implement a finalizer, will take care of finalization. +- There is no need for synchronization to guarantee thread-safety. Even though there is a race condition allowing the `if (!_isDisposed)` branch to be entered multiple times, guarantees that will be only called once. + +:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs"::: +:::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb"::: + +### Built-in safe handles in .NET + +The following derived classes in the namespace provide safe handles. + +| Class | Resources it holds | +| - | - | +|

| Files, memory mapped files, and pipes | +| | Memory views | +|

|Cryptography constructs | +| | Registry keys | +| | Wait handles | + ## See also - [Disposal of services](../../core/extensions/dependency-injection-guidelines.md#disposal-of-services) @@ -150,3 +177,4 @@ Here's the general pattern for implementing the dispose pattern for a derived cl - - - [Define and consume classes and structs (C++/CLI)](/cpp/dotnet/how-to-define-and-consume-classes-and-structs-cpp-cli) +- [The SafeHandle class](../../fundamentals/runtime-libraries/system-runtime-interopservices-safehandle.md) diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/Program.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/Program.cs index 37a893aeb54fd..cc9eaffd75e4c 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/Program.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/Program.cs @@ -1,7 +1,10 @@ -static class Program +Test(); + +void Test() { - static void Main() - { - using var disposable = new BaseClassWithSafeHandle(); - } + using DisposableDerived a = new(); + using DisposableDerivedWithFinalizer b = new(); + b.Dispose(); + using DisposableWithSafeHandle c = new(); } + diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs index 9a68d1b29e500..182367f60ac73 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs @@ -1,14 +1,13 @@ -using Microsoft.Win32.SafeHandles; -using System; -using System.Runtime.InteropServices; +using System; +using System.IO; -public class BaseClassWithSafeHandle : IDisposable +public class DisposableBase : IDisposable { - // To detect redundant calls - private bool _disposedValue; + // Detect redundant Dispose() calls. + private bool _isDisposed; - // Instantiate a SafeHandle instance. - private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true); + // Instantiate a disposable object owned by this class. + private Stream? _managedResource = new MemoryStream(); // Public implementation of Dispose pattern callable by consumers. public void Dispose() @@ -20,15 +19,16 @@ public void Dispose() // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + if (!_isDisposed) { + _isDisposed = true; + if (disposing) { - _safeHandle?.Dispose(); - _safeHandle = null; + // Dispose managed state. + _managedResource?.Dispose(); + _managedResource = null; } - - _disposedValue = true; } } } diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs index 4b45bdef29187..14c354fdb40bf 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs @@ -1,11 +1,22 @@ using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; -public class BaseClassWithFinalizer : IDisposable +public class DisposableBaseWithFinalizer : IDisposable { - // To detect redundant calls - private bool _disposedValue; + // Detect redundant Dispose() calls in a thread-safe manner. + // _isDisposed == 0 means Dispose(bool) has not been called yet. + // _isDisposed == 1 means Dispose(bool) has been already called. + private int _isDisposed; - ~BaseClassWithFinalizer() => Dispose(false); + // Instantiate a disposable object owned by this class. + private Stream? _managedResource = new MemoryStream(); + + // A pointer to 10 bytes allocated on the unmanaged heap. + private IntPtr _unmanagedResource = Marshal.AllocHGlobal(10); + + ~DisposableBaseWithFinalizer() => Dispose(false); // Public implementation of Dispose pattern callable by consumers. public void Dispose() @@ -17,16 +28,17 @@ public void Dispose() // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + // In case _isDisposed is 0, atomically set it to 1. + // Enter the branch only if the original value is 0. + if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) { if (disposing) { - // TODO: dispose managed state (managed objects) + _managedResource?.Dispose(); + _managedResource = null; } - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - _disposedValue = true; + Marshal.FreeHGlobal(_unmanagedResource); } } } diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs index 25794ca4c3147..51d40090193d2 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs @@ -1,27 +1,25 @@ -using Microsoft.Win32.SafeHandles; -using System; -using System.Runtime.InteropServices; +using System.IO; -public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle +public class DisposableDerived : DisposableBase { // To detect redundant calls - private bool _disposedValue; + private bool _isDisposed; - // Instantiate a SafeHandle instance. - private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true); + // Instantiate a disposable object owned by this class. + private Stream? _managedResource = new MemoryStream(); // Protected implementation of Dispose pattern. protected override void Dispose(bool disposing) { - if (!_disposedValue) + if (!_isDisposed) { + _isDisposed = true; + if (disposing) { - _safeHandle?.Dispose(); - _safeHandle = null; + _managedResource?.Dispose(); + _managedResource = null; } - - _disposedValue = true; } // Call base class implementation. diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs index 0966b3e633a08..0326e96c65d3e 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs @@ -1,14 +1,20 @@ -public class DerivedClassWithFinalizer : BaseClassWithFinalizer +using System.Threading; + +public class DisposableDerivedWithFinalizer : DisposableBaseWithFinalizer { - // To detect redundant calls - private bool _disposedValue; + // Detect redundant Dispose() calls in a thread-safe manner. + // _isDisposed == 0 means Dispose(bool) has not been called yet. + // _isDisposed == 1 means Dispose(bool) has been already called. + private int _isDisposed; - ~DerivedClassWithFinalizer() => Dispose(false); + ~DisposableDerivedWithFinalizer() => Dispose(false); // Protected implementation of Dispose pattern. protected override void Dispose(bool disposing) { - if (!_disposedValue) + // In case _isDisposed is 0, atomically set it to 1. + // Enter the branch only if the original value is 0. + if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) { if (disposing) { @@ -17,7 +23,6 @@ protected override void Dispose(bool disposing) // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. // TODO: set large fields to null. - _disposedValue = true; } // Call the base class implementation. diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs new file mode 100644 index 0000000000000..11081f0683531 --- /dev/null +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +// Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle. +class LocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid +{ + private LocalAllocHandle() : base(ownsHandle: true) { } + + // No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you. + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } + + // Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle. + public static LocalAllocHandle Allocate(int numberOfBytes) + { + IntPtr nativeHandle = Marshal.AllocHGlobal(numberOfBytes); + LocalAllocHandle safeHandle = new LocalAllocHandle(); + safeHandle.SetHandle(nativeHandle); + return safeHandle; + } +} + +public class DisposableWithSafeHandle : IDisposable +{ + // Detect redundant Dispose() calls. + private bool _isDisposed; + + // Managed disposable objects owned by this class + private LocalAllocHandle? _safeHandle = LocalAllocHandle.Allocate(10); + private Stream? _otherUnmanagedResource = new MemoryStream(); + + // 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 (!_isDisposed) + { + _isDisposed = true; + + if (disposing) + { + // Dispose managed state. + _otherUnmanagedResource?.Dispose(); + _safeHandle?.Dispose(); + _otherUnmanagedResource = null; + _safeHandle = null; + } + } + } +} diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/Program.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/Program.vb index 841c53fa1991d..d64f491e67004 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/Program.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/Program.vb @@ -1,6 +1,13 @@ Module Program Sub Main() - Using disposable As New BaseClassWithSafeHandle + Using a As New DisposableDerived + End Using + + Using b As New DisposableDerivedWithFinalizer + b.Dispose() + End Using + + Using c As New DisposableWithSafeHandle End Using End Sub End Module diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb index e0f548ed5b046..c35a2a80e8a03 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb @@ -1,32 +1,30 @@ -Imports Microsoft.Win32.SafeHandles -Imports System.Runtime.InteropServices +Imports System.IO -Public Class BaseClassWithSafeHandle +Public Class DisposableBase Implements IDisposable - ' To detect redundant calls - Private _disposedValue As Boolean + ' Detect redundant Dispose() calls. + Private _isDisposed As Boolean - ' Instantiate a SafeHandle instance. - Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True) + ' Instantiate a disposable object owned by this class. + Private _managedResource As Stream = New MemoryStream() ' Public implementation of Dispose pattern callable by consumers. - Public Sub Dispose() _ - Implements IDisposable.Dispose + 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 + Protected Overridable Sub Dispose(disposing As Boolean) + If Not _isDisposed Then + _isDisposed = True If disposing Then - _safeHandle?.Dispose() - _safeHandle = Nothing + ' Dispose managed state. + _managedResource?.Dispose() + _managedResource = Nothing End If - - _disposedValue = True End If End Sub End Class diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb index 54fb833eb5cba..674b4ac999bdf 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb @@ -1,31 +1,43 @@ -Public Class BaseClassWithFinalizer +Imports System +Imports System.IO +Imports System.Runtime.InteropServices +Imports System.Threading + +Public Class DisposableBaseWithFinalizer Implements IDisposable - ' To detect redundant calls - Private _disposedValue As Boolean + ' Detect redundant Dispose() calls in a thread-safe manner. + ' _isDisposed == 0 means Dispose(bool) has not been called yet. + ' _isDisposed == 1 means Dispose(bool) has been already called. + Private _isDisposed As Integer + + ' Instantiate a disposable object owned by this class. + Private _managedResource As Stream = New MemoryStream() + + ' A pointer to 10 bytes allocated on the unmanaged heap. + Private _unmanagedResource As IntPtr = Marshal.AllocHGlobal(10) Protected Overrides Sub Finalize() Dispose(False) End Sub ' Public implementation of Dispose pattern callable by consumers. - Public Sub Dispose() _ - Implements IDisposable.Dispose + 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 - + Protected Overridable Sub Dispose(disposing As Boolean) + ' In case _isDisposed is 0, atomically set it to 1. + ' Enter the branch only if the original value is 0. + If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then If disposing Then - ' TODO: dispose managed state (managed objects) + _managedResource?.Dispose() + _managedResource = Nothing End If - ' TODO free unmanaged resources (unmanaged objects) And override finalizer - ' TODO: set large fields to null - _disposedValue = True + Marshal.FreeHGlobal(_unmanagedResource) End If End Sub End Class diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb index 83a005f785382..bff2a8aa89b3a 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb @@ -1,24 +1,23 @@ -Imports Microsoft.Win32.SafeHandles -Imports System.Runtime.InteropServices +Imports System.IO -Public Class DerivedClassWithSafeHandle - Inherits BaseClassWithSafeHandle +Public Class DisposableDerived + Inherits DisposableBase ' To detect redundant calls - Private _disposedValue As Boolean + Private _isDisposed As Boolean - ' Instantiate a SafeHandle instance. - Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True) + ' Instantiate a disposable object owned by this class. + Private _managedResource As Stream = New MemoryStream() - Protected Overrides Sub Dispose(ByVal disposing As Boolean) - If Not _disposedValue Then + ' Protected implementation of Dispose pattern. + Protected Overrides Sub Dispose(disposing As Boolean) + If Not _isDisposed Then + _isDisposed = True If disposing Then - _safeHandle?.Dispose() - _safeHandle = Nothing + _managedResource?.Dispose() + _managedResource = Nothing End If - - _disposedValue = True End If ' Call base class implementation. diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb index ed6e5b13b1ac1..662bc4a1db214 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb @@ -1,24 +1,28 @@ -Public Class DerivedClassWithFinalizer - Inherits BaseClassWithFinalizer +Imports System.Threading - ' To detect redundant calls - Private _disposedValue As Boolean +Public Class DisposableDerivedWithFinalizer + Inherits DisposableBaseWithFinalizer + + ' Detect redundant Dispose() calls in a thread-safe manner. + ' _isDisposed == 0 means Dispose(bool) has not been called yet. + ' _isDisposed == 1 means Dispose(bool) has been already called. + Private _isDisposed As Integer 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 - + Protected Overrides Sub Dispose(disposing As Boolean) + ' In case _isDisposed is 0, atomically set it to 1. + ' Enter the branch only if the original value is 0. + If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then If disposing Then ' TODO: dispose managed state (managed objects). End If - ' TODO free unmanaged resources (unmanaged objects) And override a finalizer below. + ' 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. diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb new file mode 100644 index 0000000000000..770d98445864c --- /dev/null +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb @@ -0,0 +1,59 @@ +Imports System +Imports System.IO +Imports System.Runtime.InteropServices +Imports Microsoft.Win32.SafeHandles + +' Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle. +Public Class LocalAllocHandle + Inherits SafeHandleZeroOrMinusOneIsInvalid + + Private Sub New() + MyBase.New(True) + End Sub + + ' No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you. + Protected Overrides Function ReleaseHandle() As Boolean + Marshal.FreeHGlobal(handle) + Return True + End Function + + ' Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle. + Public Shared Function Allocate(numberOfBytes As Integer) As LocalAllocHandle + Dim nativeHandle As IntPtr = Marshal.AllocHGlobal(numberOfBytes) + Dim safeHandle As New LocalAllocHandle() + safeHandle.SetHandle(nativeHandle) + Return safeHandle + End Function +End Class + +Public Class DisposableWithSafeHandle + Implements IDisposable + + ' Detect redundant Dispose() calls. + Private _isDisposed As Boolean + + ' Managed disposable objects owned by this class + Private _safeHandle As LocalAllocHandle = LocalAllocHandle.Allocate(10) + Private _otherUnmanagedResource As Stream = New MemoryStream() + + ' 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 Not _isDisposed Then + _isDisposed = True + + If disposing Then + ' Dispose managed state. + _otherUnmanagedResource?.Dispose() + _safeHandle?.Dispose() + _otherUnmanagedResource = Nothing + _safeHandle = Nothing + End If + End If + End Sub +End Class From 67154475d19af6c04cc08651c1da09869eb65db7 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sun, 9 Mar 2025 18:31:32 +0100 Subject: [PATCH 2/5] fix warnings and improve content --- .../implementing-dispose.md | 30 +++++++++---------- .../system.idisposable/cs/Program.cs | 2 +- .../system.idisposable/cs/safe.cs | 2 +- .../system.idisposable/vb/Program.vb | 2 +- .../system.idisposable/vb/safe.vb | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/standard/garbage-collection/implementing-dispose.md b/docs/standard/garbage-collection/implementing-dispose.md index a56e3cca95eb9..2fa2c32e098a1 100644 --- a/docs/standard/garbage-collection/implementing-dispose.md +++ b/docs/standard/garbage-collection/implementing-dispose.md @@ -25,15 +25,14 @@ The code example provided for the storing it in a field or property, the containing class itself should also implement . Typically a class that instantiates an implementation and stores it as an instance member is also responsible for its cleanup. This helps ensure that the referenced disposable types are given the opportunity to deterministically perform cleanup through the method. In the following example, the class is `sealed` (or `NotInheritable` in Visual Basic). +If your class owns an instance of another type that implements , the containing class itself should also implement . Typically a class that instantiates an implementation and stores it as an instance member (or property) is also responsible for its cleanup. This helps ensure that the referenced disposable types are given the opportunity to deterministically perform cleanup through the method. In the following example, the class is `sealed` (or `NotInheritable` in Visual Basic). :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/Foo.cs"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/Foo.vb"::: > [!TIP] > -> - If your class has an field or property but doesn't *own* it, then the class doesn't need to implement . -> - It's the developers' responsibility to define a clean ownership model that ensures the disposal of all instances in an object graph. Typically a class creating and storing the child object also becomes the owner, but in some cases the ownership can be transferred to another type. +> - If your class has an field or property but doesn't *own* it, then the class doesn't need to implement . Typically a class creating and storing the child object also becomes the owner, but in some cases the ownership can be transferred to another type. > - There are cases when you may want to perform `null`-checking in a finalizer (which includes the `Dispose(false)` method invoked by a finalizer). One of the primary reasons is if you're unsure whether the instance got fully initialized (for example, an exception might be thrown in a constructor). ## Dispose() and Dispose(bool) @@ -84,13 +83,11 @@ All non-sealed classes (or Visual Basic classes not modified as `NotInheritable` - A `Dispose(bool)` method that performs the actual cleanup. - If your class deals with unmanaged resources either provide an override to the method, or wrap the unmanaged resource in a . -Either a class derived from that wraps your unmanaged resource (recommended) - > [!IMPORTANT] -> A finalizer (a override) is only required if you directly reference unmanaged resources. This is only needed in a few advanced scenarios, therefore in most cases finalizers can be avoided. +> A finalizer (a override) is only required if you directly reference unmanaged resources. This is a highly advanced scenario that can be typically avoided: > -> - It's possible for a class to only reference managed objects and implement the dispose pattern. There is no need to implement a finalizer in such cases and the base class will always call `Dispose(bool)` with `disposing: true`. The reason the dispose pattern is still encouraged is that it enables sub classes to implement a finalizer and call `Dispose(disposing: false)` on the base class. -> - In the rare cases when it's necessary to deal with unmanaged resources (which are typically represented by handles), **we strongly recommend to wrap the handle into a **. The provides a finalizer so you don't have to write one yourself. See the [Safe handles](#safe-handles) paragraph for more information. +> 1. **If your class references only managed objects**, it's still possible for the class to implement the dispose pattern. There is no need to implement a finalizer in such cases. +> 1. **If you need to deal with unmanaged resources** we strongly recommend to wrap the unmanaged handle into a . The provides a finalizer so you don't have to write one yourself. See the [Safe handles](#safe-handles) paragraph for more information. ### Base class with managed resources @@ -104,15 +101,15 @@ Here's a general example of implementing the dispose pattern for a base class th ### Base class with unmanaged resources -Here's an example for implementing the dispose pattern for a base class that overrides in order to clean up unmanaged resources it owns. The example also demonstrates a way to implement `Dispose(bool)` in a thread-safe manner. Synchronization might be critical when dealing with unmanaged resources in a multi-threaded application. +Here's an example for implementing the dispose pattern for a base class that overrides in order to clean up unmanaged resources it owns. The example also demonstrates a way to implement `Dispose(bool)` in a thread-safe manner. Synchronization might be critical when dealing with unmanaged resources in a multi-threaded application. As mentioned earlier, this is an advanced scenario. :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb"::: > [!NOTE] > -> - The previous example uses to allocate 10 bytes on the unmanaged heap in the constructor and free the buffer in `Dispose(bool)` by calling . This is a **dummy** allocation for illustrational purpose. -> - The approach in the previous example might lead to complex code and it's discouraged. It is recommended to avoid implementing a finalizer by [wrapping unmanaged resources into instances](#implement-the-dispose-pattern-using-a-custom-safe-handle). +> - The previous example uses to allocate 10 bytes on the unmanaged heap in the constructor and free the buffer in `Dispose(bool)` by calling . This is a dummy allocation for illustrational purpose. +> - Again, it's recommended to avoid implementing a finalizer. See [Implement the dispose pattern using a custom safe handle](#implement-the-dispose-pattern-using-a-custom-safe-handle) for an equivalent of the previous example that delegates non-deterministic finalization and synchronization to . > [!TIP] > In C#, you implement a finalization by providing a [finalizer](../../csharp/programming-guide/classes-and-structs/finalizers.md), not by overriding . In Visual Basic, you create a finalizer with `Protected Overrides Sub Finalize()`. @@ -147,14 +144,17 @@ The . It's behavior is equivalent to the code [in the previous example](#base-class-with-unmanaged-resources) demonstrating unmanaged resource handling, however this approach is considered to be safer: - -- There is no need to implement a finalizer, will take care of finalization. -- There is no need for synchronization to guarantee thread-safety. Even though there is a race condition allowing the `if (!_isDisposed)` branch to be entered multiple times, guarantees that will be only called once. +The following code demonstrates how to handle unmanaged resources by implementing a . :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb"::: +> [!NOTE] +> The behavior of the `DisposableBaseWithSafeHandle` class is equivalent to the beahavior of the [`DisposableBaseWithFinalizer` class in a previous example](#base-class-with-unmanaged-resources), however the approach demonstrated in the current paragraph is safer: +> +> - There is no need to implement a finalizer, will take care of finalization. +> - There is no need for synchronization to guarantee thread-safety. Even though there is a race condition in the `Dispose` implementation of `DisposableBaseWithSafeHandle`, guarantees that will be only called once. + ### Built-in safe handles in .NET The following derived classes in the namespace provide safe handles. diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/Program.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/Program.cs index cc9eaffd75e4c..e182826520686 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/Program.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/Program.cs @@ -5,6 +5,6 @@ void Test() using DisposableDerived a = new(); using DisposableDerivedWithFinalizer b = new(); b.Dispose(); - using DisposableWithSafeHandle c = new(); + using DisposableBaseWithSafeHandle c = new(); } diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs index 11081f0683531..9e681a3a3a53a 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs @@ -25,7 +25,7 @@ public static LocalAllocHandle Allocate(int numberOfBytes) } } -public class DisposableWithSafeHandle : IDisposable +public class DisposableBaseWithSafeHandle : IDisposable { // Detect redundant Dispose() calls. private bool _isDisposed; diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/Program.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/Program.vb index d64f491e67004..1ac27c8fd7cef 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/Program.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/Program.vb @@ -7,7 +7,7 @@ b.Dispose() End Using - Using c As New DisposableWithSafeHandle + Using c As New DisposableBaseWithSafeHandle End Using End Sub End Module diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb index 770d98445864c..ce4df58ea1509 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb @@ -26,7 +26,7 @@ Public Class LocalAllocHandle End Function End Class -Public Class DisposableWithSafeHandle +Public Class DisposableBaseWithSafeHandle Implements IDisposable ' Detect redundant Dispose() calls. From d919682c8c753c6974713935db38278a370b7088 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sun, 9 Mar 2025 20:53:25 +0100 Subject: [PATCH 3/5] fix references --- docs/standard/garbage-collection/implementing-dispose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/standard/garbage-collection/implementing-dispose.md b/docs/standard/garbage-collection/implementing-dispose.md index 2fa2c32e098a1..f9efb611b4764 100644 --- a/docs/standard/garbage-collection/implementing-dispose.md +++ b/docs/standard/garbage-collection/implementing-dispose.md @@ -108,7 +108,7 @@ Here's an example for implementing the dispose pattern for a base class that ove > [!NOTE] > -> - The previous example uses to allocate 10 bytes on the unmanaged heap in the constructor and free the buffer in `Dispose(bool)` by calling . This is a dummy allocation for illustrational purpose. +> - The previous example uses to allocate 10 bytes on the unmanaged heap in the constructor and free the buffer in `Dispose(bool)` by calling . This is a dummy allocation for illustrational purpose. > - Again, it's recommended to avoid implementing a finalizer. See [Implement the dispose pattern using a custom safe handle](#implement-the-dispose-pattern-using-a-custom-safe-handle) for an equivalent of the previous example that delegates non-deterministic finalization and synchronization to . > [!TIP] From 90a893cb753e8669931c25f69a54b103107c0c45 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Mon, 10 Mar 2025 15:03:21 +0100 Subject: [PATCH 4/5] fix warning --- docs/standard/garbage-collection/implementing-dispose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/standard/garbage-collection/implementing-dispose.md b/docs/standard/garbage-collection/implementing-dispose.md index f9efb611b4764..b0ac8fdb1b1c3 100644 --- a/docs/standard/garbage-collection/implementing-dispose.md +++ b/docs/standard/garbage-collection/implementing-dispose.md @@ -108,7 +108,7 @@ Here's an example for implementing the dispose pattern for a base class that ove > [!NOTE] > -> - The previous example uses to allocate 10 bytes on the unmanaged heap in the constructor and free the buffer in `Dispose(bool)` by calling . This is a dummy allocation for illustrational purpose. +> - The previous example uses to allocate 10 bytes on the unmanaged heap in the constructor and free the buffer in `Dispose(bool)` by calling . This is a dummy allocation for illustrational purpose. > - Again, it's recommended to avoid implementing a finalizer. See [Implement the dispose pattern using a custom safe handle](#implement-the-dispose-pattern-using-a-custom-safe-handle) for an equivalent of the previous example that delegates non-deterministic finalization and synchronization to . > [!TIP] From 8915bbaa46714a0624ed6d8204a854063766e876 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 11 Mar 2025 09:28:04 -0500 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- .../garbage-collection/implementing-dispose.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/standard/garbage-collection/implementing-dispose.md b/docs/standard/garbage-collection/implementing-dispose.md index b0ac8fdb1b1c3..d2fd4f5845170 100644 --- a/docs/standard/garbage-collection/implementing-dispose.md +++ b/docs/standard/garbage-collection/implementing-dispose.md @@ -33,7 +33,7 @@ If your class owns an instance of another type that implements [!TIP] > > - If your class has an field or property but doesn't *own* it, then the class doesn't need to implement . Typically a class creating and storing the child object also becomes the owner, but in some cases the ownership can be transferred to another type. -> - There are cases when you may want to perform `null`-checking in a finalizer (which includes the `Dispose(false)` method invoked by a finalizer). One of the primary reasons is if you're unsure whether the instance got fully initialized (for example, an exception might be thrown in a constructor). +> - There are cases when you might want to perform `null`-checking in a finalizer (which includes the `Dispose(false)` method invoked by a finalizer). One of the primary reasons is if you're unsure whether the instance got fully initialized (for example, an exception might be thrown in a constructor). ## Dispose() and Dispose(bool) @@ -81,13 +81,13 @@ All non-sealed classes (or Visual Basic classes not modified as `NotInheritable` - A implementation that calls the `Dispose(bool)` method. - A `Dispose(bool)` method that performs the actual cleanup. -- If your class deals with unmanaged resources either provide an override to the method, or wrap the unmanaged resource in a . +- If your class deals with unmanaged resources, either provide an override to the method or wrap the unmanaged resource in a . > [!IMPORTANT] > A finalizer (a override) is only required if you directly reference unmanaged resources. This is a highly advanced scenario that can be typically avoided: > -> 1. **If your class references only managed objects**, it's still possible for the class to implement the dispose pattern. There is no need to implement a finalizer in such cases. -> 1. **If you need to deal with unmanaged resources** we strongly recommend to wrap the unmanaged handle into a . The provides a finalizer so you don't have to write one yourself. See the [Safe handles](#safe-handles) paragraph for more information. +> - **If your class references only managed objects**, it's still possible for the class to implement the dispose pattern. There's no need to implement a finalizer. +> - **If you need to deal with unmanaged resources**, we strongly recommend wrapping the unmanaged handle into a . The provides a finalizer so you don't have to write one yourself. For more information, see the [Safe handles](#safe-handles) paragraph. ### Base class with managed resources @@ -140,7 +140,7 @@ Writing code for an object's finalizer is a complex task that can cause problems A is an abstract managed type that wraps an that identifies an unmanaged resource. On Windows it might identify a handle, and on Unix, a file descriptor. The `SafeHandle` provides all of the logic necessary to ensure that this resource is released once and only once, either when the `SafeHandle` is disposed of or when all references to the `SafeHandle` have been dropped and the `SafeHandle` instance is finalized. -The is an abstract base class. Derived classes provide specific instances for different kinds of handle. These derived classes validate what values for the are considered invalid and how to actually free the handle. For example, derives from `SafeHandle` to wrap `IntPtrs` that identify open file handles/descriptors, and overrides its method to close it (via the `close` function on Unix or `CloseHandle` function on Windows). Most APIs in .NET libraries that create an unmanaged resource wraps it in a `SafeHandle` and return that `SafeHandle` to you as needed, rather than handing back the raw pointer. In situations where you interact with an unmanaged component and get an `IntPtr` for an unmanaged resource, you can create your own `SafeHandle` type to wrap it. As a result, few non-`SafeHandle` types need to implement finalizers. Most disposable pattern implementations only end up wrapping other managed resources, some of which may be `SafeHandle` objects. +The is an abstract base class. Derived classes provide specific instances for different kinds of handle. These derived classes validate what values for the are considered invalid and how to actually free the handle. For example, derives from `SafeHandle` to wrap `IntPtrs` that identify open file handles and descriptors, and overrides its method to close it (via the `close` function on Unix or `CloseHandle` function on Windows). Most APIs in .NET libraries that create an unmanaged resource wrap it in a `SafeHandle` and return that `SafeHandle` to you as needed, rather than handing back the raw pointer. In situations where you interact with an unmanaged component and get an `IntPtr` for an unmanaged resource, you can create your own `SafeHandle` type to wrap it. As a result, few non-`SafeHandle` types need to implement finalizers. Most disposable pattern implementations only end up wrapping other managed resources, some of which might be `SafeHandle` objects. ### Implement the dispose pattern using a custom safe handle @@ -150,10 +150,10 @@ The following code demonstrates how to handle unmanaged resources by implementin :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb"::: > [!NOTE] -> The behavior of the `DisposableBaseWithSafeHandle` class is equivalent to the beahavior of the [`DisposableBaseWithFinalizer` class in a previous example](#base-class-with-unmanaged-resources), however the approach demonstrated in the current paragraph is safer: +> The behavior of the `DisposableBaseWithSafeHandle` class is equivalent to the behavior of the [`DisposableBaseWithFinalizer` class in a previous example](#base-class-with-unmanaged-resources), however the approach demonstrated here is safer: > -> - There is no need to implement a finalizer, will take care of finalization. -> - There is no need for synchronization to guarantee thread-safety. Even though there is a race condition in the `Dispose` implementation of `DisposableBaseWithSafeHandle`, guarantees that will be only called once. +> - There is no need to implement a finalizer, because will take care of finalization. +> - There is no need for synchronization to guarantee thread safety. Even though there is a race condition in the `Dispose` implementation of `DisposableBaseWithSafeHandle`, guarantees that will be called only once. ### Built-in safe handles in .NET @@ -163,7 +163,7 @@ The following derived classes in the namespac | - | - | |

| Files, memory mapped files, and pipes | | | Memory views | -|

|Cryptography constructs | +|

| Cryptography constructs | | | Registry keys | | | Wait handles |