Skip to content

Commit 6f50a79

Browse files
author
Oren (electricessence)
committed
Updates to eliminate inherent finalization.
1 parent 23656b1 commit 6f50a79

File tree

8 files changed

+34
-118
lines changed

8 files changed

+34
-118
lines changed

AsyncDisposableBase.cs

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,16 @@ namespace Open.Disposable
1111
/// A base class for properly implementing IAsyncDisposable but also allowing for synchronous use of IDispose.
1212
/// Only implementing OnDisposeAsync is enough to properly handle disposal.
1313
/// </summary>
14-
public abstract class AsyncDisposableBase : DisposableBase
14+
public abstract class AsyncDisposableBase : DisposableStateBase
1515
#if NETSTANDARD2_1
1616
,System.IAsyncDisposable
1717
#endif
1818
{
1919
/// <summary>
2020
/// Without overriding OnDispose, OnDisposeAsync will be called no matter what depending on how the object is disposed.
2121
/// </summary>
22-
/// <param name="mode">The method by which disposal was triggered.</param>
23-
protected abstract ValueTask OnDisposeAsync(AsyncDisposeMode mode);
24-
25-
/// <inheritdoc />
26-
/// <summary>
27-
/// If overridden, expect OnDisposeAsync to be called only when DisposeAsync is called, unless base.OnDispose(calledExplicitly) is implemented.
28-
/// </summary>
29-
protected override void OnDispose(bool calledExplicitly)
30-
{
31-
var dispose = OnDisposeAsync(calledExplicitly ? AsyncDisposeMode.Dispose : AsyncDisposeMode.Finalized);
32-
// Was it purely synchronous?
33-
if(!dispose.IsCompletedSuccessfully)
34-
{
35-
/*
36-
* If the OnDisposeAsync method doesn't complete immediately
37-
* (it could internally defer an action with a task)
38-
* then it's a signal that we must wait.
39-
*/
40-
41-
dispose.AsTask().Wait();
42-
43-
/* This is obviously not ideal, but is a good fallback for this case. */
44-
}
45-
}
22+
/// <param name="asynchronous">If true, was called by .DisposeAsync(), otherwise.</param>
23+
protected abstract ValueTask OnDisposeAsync();
4624

4725
/// <inheritdoc />
4826
public ValueTask DisposeAsync()
@@ -53,13 +31,13 @@ public ValueTask DisposeAsync()
5331
* A subscriber should smartly defer responses when possible, or only respond in a properly synchronous non-blockin way.
5432
*/
5533

56-
if (!StartDispose(true))
34+
if (!StartDispose())
5735
return new ValueTask();
5836

5937
var dispose = true;
6038
try
6139
{
62-
var d = OnDisposeAsync(AsyncDisposeMode.DisposeAsync);
40+
var d = OnDisposeAsync();
6341
if (!d.IsCompletedSuccessfully)
6442
{
6543
dispose = false;

AsyncDisposeHandler.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
using System;
7-
using System.Threading;
87
using System.Threading.Tasks;
98

109
namespace Open.Disposable
@@ -18,7 +17,7 @@ public AsyncDisposeHandler(Func<ValueTask> action)
1817

1918
Func<ValueTask> _action;
2019

21-
protected override ValueTask OnDisposeAsync(AsyncDisposeMode mode)
20+
protected override ValueTask OnDisposeAsync()
2221
=> Nullify(ref _action).Invoke();
2322
}
2423

@@ -31,10 +30,10 @@ public AsyncDisposeHandler(T value, Func<ValueTask> action) : base(action)
3130

3231
public T Value { get; private set; }
3332

34-
protected override ValueTask OnDisposeAsync(AsyncDisposeMode mode)
33+
protected override ValueTask OnDisposeAsync()
3534
{
3635
Value = default;
37-
return base.OnDisposeAsync(mode);
36+
return base.OnDisposeAsync();
3837
}
3938
}
4039
}

AsyncDisposeMode.cs

Lines changed: 0 additions & 14 deletions
This file was deleted.

DeferredCleanupBase.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,12 @@ private void Cleanup(object state = null)
161161

162162
protected abstract void OnCleanup();
163163

164-
protected override void OnDispose(bool calledExplicitly)
164+
protected override void OnDispose()
165+
{
166+
ResetTimer();
167+
}
168+
169+
~DeferredCleanupBase()
165170
{
166171
ResetTimer();
167172
}

DisposableBase.cs

Lines changed: 10 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,35 @@
44
*/
55

66
using System;
7-
using System.Diagnostics;
87

98
namespace Open.Disposable
109
{
1110
/// <summary>
1211
/// A base class for properly implementing the synchronous dispose pattern.
1312
/// Implement OnDispose to handle disposal.
1413
/// </summary>
15-
public abstract class DisposableBase : DisposableStateBase, IDisposable
14+
public abstract class DisposableBase : DisposableStateBase, IDisposable
1615
{
17-
/// <inheritdoc />
16+
/// <inheritdoc />
1817
public void Dispose()
1918
{
20-
Dispose(true);
21-
}
22-
23-
// Being called by the GC...
24-
~DisposableBase()
25-
{
26-
Dispose(false);
27-
}
28-
29-
/// <summary>
30-
/// When implemented will be called (only once) when being disposed.
31-
/// </summary>
32-
/// <param name="calledExplicitly">True if called through code in the runtime, or false if finalized by the garbage collector.</param>
33-
protected abstract void OnDispose(bool calledExplicitly);
19+
if (!StartDispose())
20+
return;
3421

35-
internal protected bool StartDispose(bool calledExplicitly)
36-
{
3722
try
3823
{
39-
if (StartDispose())
40-
return true;
41-
42-
// Already called? No need to GC suppress.
43-
calledExplicitly = false;
44-
return false;
45-
}
46-
catch (Exception eventBeforeDisposeException) when (!calledExplicitly)
47-
{
48-
if (Debugger.IsAttached)
49-
Debug.Fail(eventBeforeDisposeException.ToString());
50-
51-
return true; // If we even got this far with !calledExplicitly then it must be the first time and we should proceed.
24+
OnDispose();
5225
}
5326
finally
5427
{
55-
if (calledExplicitly)
56-
GC.SuppressFinalize(this);
28+
Disposed();
5729
}
5830
}
5931

60-
private void Dispose(bool calledExplicitly)
61-
{
62-
if (!StartDispose(calledExplicitly))
63-
return;
64-
65-
try
66-
{
67-
OnDispose(calledExplicitly);
68-
}
69-
catch (Exception onDisposeException) when (!calledExplicitly)
70-
{
71-
if (Debugger.IsAttached)
72-
Debug.Fail(onDisposeException.ToString());
73-
}
74-
finally
75-
{
76-
Disposed();
77-
}
78-
}
32+
/// <summary>
33+
/// When implemented will be called (only once) when being disposed.
34+
/// </summary>
35+
protected abstract void OnDispose();
7936
}
8037

8138
}

DisposeHandler.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
using System;
7-
using System.Threading;
87

98
namespace Open.Disposable
109
{
@@ -17,8 +16,8 @@ public DisposeHandler(Action action)
1716

1817
Action _action;
1918

20-
protected override void OnDispose(bool calledExplicitly)
21-
=> Interlocked.Exchange(ref _action, null).Invoke();
19+
protected override void OnDispose()
20+
=> Nullify(ref _action).Invoke();
2221

2322
}
2423

@@ -31,10 +30,10 @@ public DisposeHandler(T value, Action action) :base(action)
3130

3231
public T Value { get; private set; }
3332

34-
protected override void OnDispose(bool calledExplicitly)
33+
protected override void OnDispose()
3534
{
3635
Value = default;
37-
base.OnDispose(calledExplicitly);
36+
base.OnDispose();
3837
}
3938
}
4039
}

Open.Disposable.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
<PackageProjectUrl>https://github.com/electricessence/Open.Disposable/</PackageProjectUrl>
1818
<RepositoryUrl>https://github.com/electricessence/Open.Disposable/</RepositoryUrl>
1919
<RepositoryType>git</RepositoryType>
20-
<Version>2.0.1</Version>
20+
<Version>2.1.0</Version>
2121
<PackageReleaseNotes></PackageReleaseNotes>
22-
<AssemblyVersion>2.0.1.0</AssemblyVersion>
23-
<FileVersion>2.0.1.0</FileVersion>
22+
<AssemblyVersion>2.1.0.0</AssemblyVersion>
23+
<FileVersion>2.1.0.0</FileVersion>
2424
<PackageLicenseExpression>MIT</PackageLicenseExpression>
2525
</PropertyGroup>
2626

README.md

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Provides a set of useful classes when implementing a disposable.
55
## Core principles
66

77
* For most use cases, disposal should only occur once and be final.
8-
* Implementing `IDisposable` in combination with `IAsyncDisposable` is not typical but can be facilitated.
8+
* Implementing `IDisposable` in combination with `IAsyncDisposable` is not typical and should be avoided. The consumer should understand the difference.
99
* `.Dispose()` and `.DisposeAsync()` should be thread-safe and calling either multiple times should not block or throw. (Typically done through an interlock method.)
1010

1111
### Avoid anti-patterns
@@ -26,21 +26,13 @@ Aggressively attempting to help out the garbage collector can be serious anti-pa
2626

2727
### `DisposableBase`
2828

29-
Simply implement `void OnDispose(bool calledExplicitly)` in order to manage disposal.
30-
31-
If `calledExplicitly` is `true`, then the `.Dispose()` method was called by the code. If `false`, the class was finalized by the GC.
29+
Simply implement `void OnDispose()` in order to manage disposal.
3230

3331
> Note: `DisposableBase` exposes a `BeforeDispose` event which will be triggered once just before disposing commences. This allows for others to react to this disposal event. The `DisposableBase` is still considered 'live' until after the `BeforeDispose` event cycle has completed.
3432
3533
### `AsyncDisposableBase`
3634

37-
Inherits from `DisposableBase`.
38-
39-
Simply implement `ValueTask OnDisposeAsync(AsyncDisposeMode mode)` in order to manage disposal.
40-
41-
Dispose accordingly based upon the `AsyncDisposeMode`.
42-
43-
It is also possible to override `void OnDispose(bool calledExplicitly)` to manage synchronous disposal separate from asynchronous disposal, but should it must be understood that overriding without calling `base.OnDispose(calledExplicitly)` will mean that `OnDisposeAsync(AsyncDisposeMode mode)` will not automatically be called. Essentially meaning if you override, you should prepare for one or the other, but not both.
35+
Simply implement `ValueTask OnDisposeAsync()` in order to manage disposal.
4436

4537
### `DisposeHandler` & `AsyncDisposeHandler`
4638

0 commit comments

Comments
 (0)