Skip to content

Commit 920bfa3

Browse files
antonfirsovIEvangelistgewarren
authored
implementing-dispose.md: Better illustration of SafeHandles (#45173)
* `implementing-dispose.md`: Better illustration of SafeHandles * fix warnings and improve content * fix references * fix warning * Apply suggestions from code review Co-authored-by: Genevieve Warren <[email protected]> --------- Co-authored-by: David Pine <[email protected]> Co-authored-by: Genevieve Warren <[email protected]>
1 parent d1d12a9 commit 920bfa3

File tree

13 files changed

+314
-128
lines changed

13 files changed

+314
-128
lines changed

docs/standard/garbage-collection/implementing-dispose.md

Lines changed: 61 additions & 33 deletions
Large diffs are not rendered by default.
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
static class Program
1+
Test();
2+
3+
void Test()
24
{
3-
static void Main()
4-
{
5-
using var disposable = new BaseClassWithSafeHandle();
6-
}
5+
using DisposableDerived a = new();
6+
using DisposableDerivedWithFinalizer b = new();
7+
b.Dispose();
8+
using DisposableBaseWithSafeHandle c = new();
79
}
10+
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
using Microsoft.Win32.SafeHandles;
2-
using System;
3-
using System.Runtime.InteropServices;
1+
using System;
2+
using System.IO;
43

5-
public class BaseClassWithSafeHandle : IDisposable
4+
public class DisposableBase : IDisposable
65
{
7-
// To detect redundant calls
8-
private bool _disposedValue;
6+
// Detect redundant Dispose() calls.
7+
private bool _isDisposed;
98

10-
// Instantiate a SafeHandle instance.
11-
private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);
9+
// Instantiate a disposable object owned by this class.
10+
private Stream? _managedResource = new MemoryStream();
1211

1312
// Public implementation of Dispose pattern callable by consumers.
1413
public void Dispose()
@@ -20,15 +19,16 @@ public void Dispose()
2019
// Protected implementation of Dispose pattern.
2120
protected virtual void Dispose(bool disposing)
2221
{
23-
if (!_disposedValue)
22+
if (!_isDisposed)
2423
{
24+
_isDisposed = true;
25+
2526
if (disposing)
2627
{
27-
_safeHandle?.Dispose();
28-
_safeHandle = null;
28+
// Dispose managed state.
29+
_managedResource?.Dispose();
30+
_managedResource = null;
2931
}
30-
31-
_disposedValue = true;
3232
}
3333
}
3434
}
Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
using System.Threading;
25

3-
public class BaseClassWithFinalizer : IDisposable
6+
public class DisposableBaseWithFinalizer : IDisposable
47
{
5-
// To detect redundant calls
6-
private bool _disposedValue;
8+
// Detect redundant Dispose() calls in a thread-safe manner.
9+
// _isDisposed == 0 means Dispose(bool) has not been called yet.
10+
// _isDisposed == 1 means Dispose(bool) has been already called.
11+
private int _isDisposed;
712

8-
~BaseClassWithFinalizer() => Dispose(false);
13+
// Instantiate a disposable object owned by this class.
14+
private Stream? _managedResource = new MemoryStream();
15+
16+
// A pointer to 10 bytes allocated on the unmanaged heap.
17+
private IntPtr _unmanagedResource = Marshal.AllocHGlobal(10);
18+
19+
~DisposableBaseWithFinalizer() => Dispose(false);
920

1021
// Public implementation of Dispose pattern callable by consumers.
1122
public void Dispose()
@@ -17,16 +28,17 @@ public void Dispose()
1728
// Protected implementation of Dispose pattern.
1829
protected virtual void Dispose(bool disposing)
1930
{
20-
if (!_disposedValue)
31+
// In case _isDisposed is 0, atomically set it to 1.
32+
// Enter the branch only if the original value is 0.
33+
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
2134
{
2235
if (disposing)
2336
{
24-
// TODO: dispose managed state (managed objects)
37+
_managedResource?.Dispose();
38+
_managedResource = null;
2539
}
2640

27-
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
28-
// TODO: set large fields to null
29-
_disposedValue = true;
41+
Marshal.FreeHGlobal(_unmanagedResource);
3042
}
3143
}
3244
}

samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
1-
using Microsoft.Win32.SafeHandles;
2-
using System;
3-
using System.Runtime.InteropServices;
1+
using System.IO;
42

5-
public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle
3+
public class DisposableDerived : DisposableBase
64
{
75
// To detect redundant calls
8-
private bool _disposedValue;
6+
private bool _isDisposed;
97

10-
// Instantiate a SafeHandle instance.
11-
private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);
8+
// Instantiate a disposable object owned by this class.
9+
private Stream? _managedResource = new MemoryStream();
1210

1311
// Protected implementation of Dispose pattern.
1412
protected override void Dispose(bool disposing)
1513
{
16-
if (!_disposedValue)
14+
if (!_isDisposed)
1715
{
16+
_isDisposed = true;
17+
1818
if (disposing)
1919
{
20-
_safeHandle?.Dispose();
21-
_safeHandle = null;
20+
_managedResource?.Dispose();
21+
_managedResource = null;
2222
}
23-
24-
_disposedValue = true;
2523
}
2624

2725
// Call base class implementation.

samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
public class DerivedClassWithFinalizer : BaseClassWithFinalizer
1+
using System.Threading;
2+
3+
public class DisposableDerivedWithFinalizer : DisposableBaseWithFinalizer
24
{
3-
// To detect redundant calls
4-
private bool _disposedValue;
5+
// Detect redundant Dispose() calls in a thread-safe manner.
6+
// _isDisposed == 0 means Dispose(bool) has not been called yet.
7+
// _isDisposed == 1 means Dispose(bool) has been already called.
8+
private int _isDisposed;
59

6-
~DerivedClassWithFinalizer() => Dispose(false);
10+
~DisposableDerivedWithFinalizer() => Dispose(false);
711

812
// Protected implementation of Dispose pattern.
913
protected override void Dispose(bool disposing)
1014
{
11-
if (!_disposedValue)
15+
// In case _isDisposed is 0, atomically set it to 1.
16+
// Enter the branch only if the original value is 0.
17+
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
1218
{
1319
if (disposing)
1420
{
@@ -17,7 +23,6 @@ protected override void Dispose(bool disposing)
1723

1824
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
1925
// TODO: set large fields to null.
20-
_disposedValue = true;
2126
}
2227

2328
// Call the base class implementation.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
using Microsoft.Win32.SafeHandles;
5+
6+
// Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
7+
class LocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
8+
{
9+
private LocalAllocHandle() : base(ownsHandle: true) { }
10+
11+
// No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you.
12+
protected override bool ReleaseHandle()
13+
{
14+
Marshal.FreeHGlobal(handle);
15+
return true;
16+
}
17+
18+
// Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle.
19+
public static LocalAllocHandle Allocate(int numberOfBytes)
20+
{
21+
IntPtr nativeHandle = Marshal.AllocHGlobal(numberOfBytes);
22+
LocalAllocHandle safeHandle = new LocalAllocHandle();
23+
safeHandle.SetHandle(nativeHandle);
24+
return safeHandle;
25+
}
26+
}
27+
28+
public class DisposableBaseWithSafeHandle : IDisposable
29+
{
30+
// Detect redundant Dispose() calls.
31+
private bool _isDisposed;
32+
33+
// Managed disposable objects owned by this class
34+
private LocalAllocHandle? _safeHandle = LocalAllocHandle.Allocate(10);
35+
private Stream? _otherUnmanagedResource = new MemoryStream();
36+
37+
// Public implementation of Dispose pattern callable by consumers.
38+
public void Dispose()
39+
{
40+
Dispose(true);
41+
GC.SuppressFinalize(this);
42+
}
43+
44+
// Protected implementation of Dispose pattern.
45+
protected virtual void Dispose(bool disposing)
46+
{
47+
if (!_isDisposed)
48+
{
49+
_isDisposed = true;
50+
51+
if (disposing)
52+
{
53+
// Dispose managed state.
54+
_otherUnmanagedResource?.Dispose();
55+
_safeHandle?.Dispose();
56+
_otherUnmanagedResource = null;
57+
_safeHandle = null;
58+
}
59+
}
60+
}
61+
}
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Module Program
22
Sub Main()
3-
Using disposable As New BaseClassWithSafeHandle
3+
Using a As New DisposableDerived
4+
End Using
5+
6+
Using b As New DisposableDerivedWithFinalizer
7+
b.Dispose()
8+
End Using
9+
10+
Using c As New DisposableBaseWithSafeHandle
411
End Using
512
End Sub
613
End Module
Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
1-
Imports Microsoft.Win32.SafeHandles
2-
Imports System.Runtime.InteropServices
1+
Imports System.IO
32

4-
Public Class BaseClassWithSafeHandle
3+
Public Class DisposableBase
54
Implements IDisposable
65

7-
' To detect redundant calls
8-
Private _disposedValue As Boolean
6+
' Detect redundant Dispose() calls.
7+
Private _isDisposed As Boolean
98

10-
' Instantiate a SafeHandle instance.
11-
Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
9+
' Instantiate a disposable object owned by this class.
10+
Private _managedResource As Stream = New MemoryStream()
1211

1312
' Public implementation of Dispose pattern callable by consumers.
14-
Public Sub Dispose() _
15-
Implements IDisposable.Dispose
13+
Public Sub Dispose() Implements IDisposable.Dispose
1614
Dispose(True)
1715
GC.SuppressFinalize(Me)
1816
End Sub
1917

2018
' Protected implementation of Dispose pattern.
21-
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
22-
If Not _disposedValue Then
19+
Protected Overridable Sub Dispose(disposing As Boolean)
20+
If Not _isDisposed Then
21+
_isDisposed = True
2322

2423
If disposing Then
25-
_safeHandle?.Dispose()
26-
_safeHandle = Nothing
24+
' Dispose managed state.
25+
_managedResource?.Dispose()
26+
_managedResource = Nothing
2727
End If
28-
29-
_disposedValue = True
3028
End If
3129
End Sub
3230
End Class
Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,43 @@
1-
Public Class BaseClassWithFinalizer
1+
Imports System
2+
Imports System.IO
3+
Imports System.Runtime.InteropServices
4+
Imports System.Threading
5+
6+
Public Class DisposableBaseWithFinalizer
27
Implements IDisposable
38

4-
' To detect redundant calls
5-
Private _disposedValue As Boolean
9+
' Detect redundant Dispose() calls in a thread-safe manner.
10+
' _isDisposed == 0 means Dispose(bool) has not been called yet.
11+
' _isDisposed == 1 means Dispose(bool) has been already called.
12+
Private _isDisposed As Integer
13+
14+
' Instantiate a disposable object owned by this class.
15+
Private _managedResource As Stream = New MemoryStream()
16+
17+
' A pointer to 10 bytes allocated on the unmanaged heap.
18+
Private _unmanagedResource As IntPtr = Marshal.AllocHGlobal(10)
619

720
Protected Overrides Sub Finalize()
821
Dispose(False)
922
End Sub
1023

1124
' Public implementation of Dispose pattern callable by consumers.
12-
Public Sub Dispose() _
13-
Implements IDisposable.Dispose
25+
Public Sub Dispose() Implements IDisposable.Dispose
1426
Dispose(True)
1527
GC.SuppressFinalize(Me)
1628
End Sub
1729

1830
' Protected implementation of Dispose pattern.
19-
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
20-
If Not _disposedValue Then
21-
31+
Protected Overridable Sub Dispose(disposing As Boolean)
32+
' In case _isDisposed is 0, atomically set it to 1.
33+
' Enter the branch only if the original value is 0.
34+
If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then
2235
If disposing Then
23-
' TODO: dispose managed state (managed objects)
36+
_managedResource?.Dispose()
37+
_managedResource = Nothing
2438
End If
2539

26-
' TODO free unmanaged resources (unmanaged objects) And override finalizer
27-
' TODO: set large fields to null
28-
_disposedValue = True
40+
Marshal.FreeHGlobal(_unmanagedResource)
2941
End If
3042
End Sub
3143
End Class

samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
1-
Imports Microsoft.Win32.SafeHandles
2-
Imports System.Runtime.InteropServices
1+
Imports System.IO
32

4-
Public Class DerivedClassWithSafeHandle
5-
Inherits BaseClassWithSafeHandle
3+
Public Class DisposableDerived
4+
Inherits DisposableBase
65

76
' To detect redundant calls
8-
Private _disposedValue As Boolean
7+
Private _isDisposed As Boolean
98

10-
' Instantiate a SafeHandle instance.
11-
Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
9+
' Instantiate a disposable object owned by this class.
10+
Private _managedResource As Stream = New MemoryStream()
1211

13-
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
14-
If Not _disposedValue Then
12+
' Protected implementation of Dispose pattern.
13+
Protected Overrides Sub Dispose(disposing As Boolean)
14+
If Not _isDisposed Then
15+
_isDisposed = True
1516

1617
If disposing Then
17-
_safeHandle?.Dispose()
18-
_safeHandle = Nothing
18+
_managedResource?.Dispose()
19+
_managedResource = Nothing
1920
End If
20-
21-
_disposedValue = True
2221
End If
2322

2423
' Call base class implementation.

0 commit comments

Comments
 (0)