Skip to content

implementing-dispose.md: Better illustration of SafeHandles #45173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
94 changes: 61 additions & 33 deletions docs/standard/garbage-collection/implementing-dispose.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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 DisposableBaseWithSafeHandle c = new();
}

Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
{
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 DisposableBaseWithSafeHandle : 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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 DisposableBaseWithSafeHandle
End Using
End Sub
End Module
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Loading