Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/standard/garbage-collection/implementing-dispose.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ All non-sealed classes (or Visual Basic classes not modified as `NotInheritable`

### Base class with managed resources

Here's a general example of implementing the dispose pattern for a base class that only owns managed resources.
Here's a general example of implementing the dispose pattern for a base class that only owns managed resources. The example uses <xref:System.Threading.Interlocked.CompareExchange%2A?displayProperty=nameWithType> to ensure thread-safe disposal.

:::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":::
Expand Down Expand Up @@ -153,7 +153,7 @@ The following code demonstrates how to handle unmanaged resources by implementin
> 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, because <xref:System.Runtime.InteropServices.SafeHandle> 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`, <xref:System.Runtime.InteropServices.SafeHandle> guarantees that <xref:System.Runtime.InteropServices.SafeHandle.ReleaseHandle%2A?displayProperty=nameWithType> will be called only once.
> - <xref:System.Runtime.InteropServices.SafeHandle> guarantees that <xref:System.Runtime.InteropServices.SafeHandle.ReleaseHandle%2A?displayProperty=nameWithType> will be called only once, even in multi-threaded scenarios.
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated bullet point removes important context about why synchronization is unnecessary when using SafeHandle. The original text explained that despite a race condition in the Dispose implementation, SafeHandle provides its own thread-safety guarantees. Consider revising to: '- The Dispose implementation uses xref:System.Threading.Interlocked.CompareExchange%2A?displayProperty=nameWithType to ensure thread-safe disposal. Additionally, xref:System.Runtime.InteropServices.SafeHandle guarantees that xref:System.Runtime.InteropServices.SafeHandle.ReleaseHandle%2A?displayProperty=nameWithType will be called only once, even in multi-threaded scenarios.' This maintains the educational value by explaining both the explicit thread-safety in the Dispose pattern and the built-in SafeHandle protections.

Suggested change
> - <xref:System.Runtime.InteropServices.SafeHandle> guarantees that <xref:System.Runtime.InteropServices.SafeHandle.ReleaseHandle%2A?displayProperty=nameWithType> will be called only once, even in multi-threaded scenarios.
> - The `Dispose` implementation uses <xref:System.Threading.Interlocked.CompareExchange%2A?displayProperty=nameWithType> to ensure thread-safe disposal. Additionally, <xref:System.Runtime.InteropServices.SafeHandle> guarantees that <xref:System.Runtime.InteropServices.SafeHandle.ReleaseHandle%2A?displayProperty=nameWithType> will be called only once, even in multi-threaded scenarios.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this rewording. What do you think @gewarren ?


### Built-in safe handles in .NET

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using System.IO;
using System.Threading;

public class DisposableBase : IDisposable
{
// Detect redundant Dispose() calls.
private bool _isDisposed;
// 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;

// Instantiate a disposable object owned by this class.
private Stream? _managedResource = new MemoryStream();
Expand All @@ -19,10 +22,10 @@ public void Dispose()
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
// 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)
{
_isDisposed = true;

if (disposing)
{
// Dispose managed state.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;

// Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
Expand All @@ -27,8 +28,10 @@ public static LocalAllocHandle Allocate(int numberOfBytes)

public class DisposableBaseWithSafeHandle : IDisposable
{
// Detect redundant Dispose() calls.
private bool _isDisposed;
// 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;

// Managed disposable objects owned by this class
private LocalAllocHandle? _safeHandle = LocalAllocHandle.Allocate(10);
Expand All @@ -44,10 +47,10 @@ public void Dispose()
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
// 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)
{
_isDisposed = true;

if (disposing)
{
// Dispose managed state.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
Imports System.IO
Imports System.Threading

Public Class DisposableBase
Implements IDisposable

' Detect redundant Dispose() calls.
Private _isDisposed 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()
Expand All @@ -17,9 +20,9 @@ Public Class DisposableBase

' Protected implementation of Dispose pattern.
Protected Overridable Sub Dispose(disposing As Boolean)
If Not _isDisposed Then
_isDisposed = True

' 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
' Dispose managed state.
_managedResource?.Dispose()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Imports System
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Threading
Imports Microsoft.Win32.SafeHandles

' Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
Expand Down Expand Up @@ -29,8 +30,10 @@ End Class
Public Class DisposableBaseWithSafeHandle
Implements IDisposable

' Detect redundant Dispose() calls.
Private _isDisposed 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

' Managed disposable objects owned by this class
Private _safeHandle As LocalAllocHandle = LocalAllocHandle.Allocate(10)
Expand All @@ -44,9 +47,9 @@ Public Class DisposableBaseWithSafeHandle

' Protected implementation of Dispose pattern.
Protected Overridable Sub Dispose(disposing As Boolean)
If Not _isDisposed Then
_isDisposed = True

' 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
' Dispose managed state.
_otherUnmanagedResource?.Dispose()
Expand Down