diff --git a/.nuget/Cuemon.Extensions.Core/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Core/PackageReleaseNotes.txt
index 8d8f5a45b..6db089461 100644
--- a/.nuget/Cuemon.Extensions.Core/PackageReleaseNotes.txt
+++ b/.nuget/Cuemon.Extensions.Core/PackageReleaseNotes.txt
@@ -15,6 +15,7 @@ Availability: .NET 9, .NET 8 and .NET Standard 2.0
- ADDED FuncFactory class in the Cuemon.Extensions namespace that provides access to factory methods for creating FuncFactory{TTuple, TResult} objects that encapsulate a function delegate with a variable amount of generic arguments
- ADDED MutableTupleFactory class in the Cuemon.Extensions namespace that provides access to factory methods for creating MutableTuple objects
- ADDED TesterFuncFactory class in the Cuemon.Extensions namespace that provides access to factory methods for creating TesterFuncFactory{TTuple, TResult, TSuccess} objects that encapsulate a tester function delegate with a variable amount of generic arguments
+- ADDED AsyncDisposable class in the Cuemon.Extensions namespace that provides a mechanism for asynchronously releasing both managed and unmanaged resources with focus on the former
Version 8.3.2
Availability: .NET 8, .NET 6 and .NET Standard 2.0
diff --git a/.nuget/Cuemon.Extensions.Text.Json/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Text.Json/PackageReleaseNotes.txt
index 6999f71e3..ffa8f931f 100644
--- a/.nuget/Cuemon.Extensions.Text.Json/PackageReleaseNotes.txt
+++ b/.nuget/Cuemon.Extensions.Text.Json/PackageReleaseNotes.txt
@@ -15,7 +15,7 @@ Availability: .NET 9, .NET 8 and .NET Standard 2.0
# Bug Fixes
- FIXED ExceptionConverter class in the Cuemon.Extensions.Text.Json.Converters namespace to use JsonSerializerOptions when converting JSON to Exception
-- FIXED the JSON converter that converts a Failure to JSON so it uses the actual key-value from the Data property of an exception instead of always writing key
+- FIXED the JSON converter in the Cuemon.Extensions.Text.Json.Converters namespace that converts a Failure to JSON so it uses the actual key-value from the Data property of an exception instead of always writing key
Version 8.3.2
Availability: .NET 8 and .NET 6
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eb96bf472..eabb946a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -53,6 +53,7 @@ Highlighted features included in this release:
- FuncFactory class in the Cuemon.Extensions namespace that provides access to factory methods for creating FuncFactory{TTuple, TResult} objects that encapsulate a function delegate with a variable amount of generic arguments
- MutableTupleFactory class in the Cuemon.Extensions namespace that provides access to factory methods for creating MutableTuple objects
- TesterFuncFactory class in the Cuemon.Extensions namespace that provides access to factory methods for creating TesterFuncFactory{TTuple, TResult, TSuccess} objects that encapsulate a tester function delegate with a variable amount of generic arguments
+- AsyncDisposable class in the Cuemon.Extensions namespace that provides a mechanism for asynchronously releasing both managed and unmanaged resources with focus on the former
### Changed
diff --git a/Directory.Build.props b/Directory.Build.props
index 1079ae37d..73f47be3d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -93,7 +93,7 @@
-
+
diff --git a/src/Cuemon.Core/Disposable.cs b/src/Cuemon.Core/Disposable.cs
index 64fac7a34..531e1c65f 100644
--- a/src/Cuemon.Core/Disposable.cs
+++ b/src/Cuemon.Core/Disposable.cs
@@ -4,7 +4,6 @@ namespace Cuemon
{
///
/// Provides a mechanism for releasing both managed and unmanaged resources with focus on the former.
- /// Implements the
///
///
public abstract class Disposable : IDisposable
diff --git a/src/Cuemon.Core/FinalizeDisposable.cs b/src/Cuemon.Core/FinalizeDisposable.cs
index 036e18a1e..313f8cf51 100644
--- a/src/Cuemon.Core/FinalizeDisposable.cs
+++ b/src/Cuemon.Core/FinalizeDisposable.cs
@@ -34,4 +34,4 @@ protected override void OnDisposeManagedResources()
///
protected abstract override void OnDisposeUnmanagedResources();
}
-}
\ No newline at end of file
+}
diff --git a/src/Cuemon.Extensions.Core/AsyncDisposable.cs b/src/Cuemon.Extensions.Core/AsyncDisposable.cs
new file mode 100644
index 000000000..2a981adc5
--- /dev/null
+++ b/src/Cuemon.Extensions.Core/AsyncDisposable.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Cuemon.Extensions
+{
+ ///
+ /// Provides a mechanism for asynchronously releasing both managed and unmanaged resources with focus on the former.
+ ///
+ ///
+ ///
+ public abstract class AsyncDisposable : Disposable, IAsyncDisposable
+ {
+ ///
+ /// Called when this object is being disposed by either or having disposing set to true and is false.
+ ///
+ /// You should almost never override this - unless you want to call it from .
+ protected override void OnDisposeManagedResources()
+ {
+ }
+
+ ///
+ /// Called when this object is being disposed by .
+ ///
+ protected abstract ValueTask OnDisposeManagedResourcesAsync();
+
+ ///
+ /// Asynchronously releases the resources used by the .
+ ///
+ /// A that represents the asynchronous dispose operation.
+ /// https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#the-disposeasync-method
+ public async ValueTask DisposeAsync()
+ {
+ await OnDisposeManagedResourcesAsync().ConfigureAwait(false);
+ Dispose(false);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Cuemon.Extensions.Core/Cuemon.Extensions.Core.csproj b/src/Cuemon.Extensions.Core/Cuemon.Extensions.Core.csproj
index fac98679c..579036525 100644
--- a/src/Cuemon.Extensions.Core/Cuemon.Extensions.Core.csproj
+++ b/src/Cuemon.Extensions.Core/Cuemon.Extensions.Core.csproj
@@ -13,8 +13,13 @@
extension-methods extensions to-byte-array flatten to-encoded-string configure create-instance action-delegate options-pattern
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/Cuemon.AspNetCore.Authentication.Tests.csproj b/test/Cuemon.AspNetCore.Authentication.Tests/Cuemon.AspNetCore.Authentication.Tests.csproj
index 6c81d0ab3..a8acf1692 100644
--- a/test/Cuemon.AspNetCore.Authentication.Tests/Cuemon.AspNetCore.Authentication.Tests.csproj
+++ b/test/Cuemon.AspNetCore.Authentication.Tests/Cuemon.AspNetCore.Authentication.Tests.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/test/Cuemon.AspNetCore.FunctionalTests/Cuemon.AspNetCore.FunctionalTests.csproj b/test/Cuemon.AspNetCore.FunctionalTests/Cuemon.AspNetCore.FunctionalTests.csproj
index 67ba24356..e11c28fb8 100644
--- a/test/Cuemon.AspNetCore.FunctionalTests/Cuemon.AspNetCore.FunctionalTests.csproj
+++ b/test/Cuemon.AspNetCore.FunctionalTests/Cuemon.AspNetCore.FunctionalTests.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/test/Cuemon.AspNetCore.Mvc.FunctionalTests/Cuemon.AspNetCore.Mvc.FunctionalTests.csproj b/test/Cuemon.AspNetCore.Mvc.FunctionalTests/Cuemon.AspNetCore.Mvc.FunctionalTests.csproj
index 1042a620b..7ceb271b9 100644
--- a/test/Cuemon.AspNetCore.Mvc.FunctionalTests/Cuemon.AspNetCore.Mvc.FunctionalTests.csproj
+++ b/test/Cuemon.AspNetCore.Mvc.FunctionalTests/Cuemon.AspNetCore.Mvc.FunctionalTests.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Cuemon.AspNetCore.Mvc.Tests.csproj b/test/Cuemon.AspNetCore.Mvc.Tests/Cuemon.AspNetCore.Mvc.Tests.csproj
index 5a751cc71..057560c1b 100644
--- a/test/Cuemon.AspNetCore.Mvc.Tests/Cuemon.AspNetCore.Mvc.Tests.csproj
+++ b/test/Cuemon.AspNetCore.Mvc.Tests/Cuemon.AspNetCore.Mvc.Tests.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/test/Cuemon.AspNetCore.Razor.TagHelpers.Tests/Cuemon.AspNetCore.Razor.TagHelpers.Tests.csproj b/test/Cuemon.AspNetCore.Razor.TagHelpers.Tests/Cuemon.AspNetCore.Razor.TagHelpers.Tests.csproj
index 4907c7de9..d2c7c3a42 100644
--- a/test/Cuemon.AspNetCore.Razor.TagHelpers.Tests/Cuemon.AspNetCore.Razor.TagHelpers.Tests.csproj
+++ b/test/Cuemon.AspNetCore.Razor.TagHelpers.Tests/Cuemon.AspNetCore.Razor.TagHelpers.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/test/Cuemon.AspNetCore.Tests/Cuemon.AspNetCore.Tests.csproj b/test/Cuemon.AspNetCore.Tests/Cuemon.AspNetCore.Tests.csproj
index 78a83f058..29f0997a4 100644
--- a/test/Cuemon.AspNetCore.Tests/Cuemon.AspNetCore.Tests.csproj
+++ b/test/Cuemon.AspNetCore.Tests/Cuemon.AspNetCore.Tests.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/test/Cuemon.Data.SqlClient.Tests/Cuemon.Data.SqlClient.Tests.csproj b/test/Cuemon.Data.SqlClient.Tests/Cuemon.Data.SqlClient.Tests.csproj
index d06f1d798..c9e3e47b2 100644
--- a/test/Cuemon.Data.SqlClient.Tests/Cuemon.Data.SqlClient.Tests.csproj
+++ b/test/Cuemon.Data.SqlClient.Tests/Cuemon.Data.SqlClient.Tests.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj b/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj
index 71303432e..c5e5f8dc2 100644
--- a/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj
+++ b/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj
@@ -57,7 +57,7 @@
-
+
diff --git a/test/Cuemon.Extensions.AspNetCore.Authentication.Tests/Cuemon.Extensions.AspNetCore.Authentication.Tests.csproj b/test/Cuemon.Extensions.AspNetCore.Authentication.Tests/Cuemon.Extensions.AspNetCore.Authentication.Tests.csproj
index 2b768b62f..736f45c68 100644
--- a/test/Cuemon.Extensions.AspNetCore.Authentication.Tests/Cuemon.Extensions.AspNetCore.Authentication.Tests.csproj
+++ b/test/Cuemon.Extensions.AspNetCore.Authentication.Tests/Cuemon.Extensions.AspNetCore.Authentication.Tests.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/test/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json.Tests/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json.Tests.csproj b/test/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json.Tests/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json.Tests.csproj
index a4d35ef2b..03727d0f3 100644
--- a/test/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json.Tests/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json.Tests.csproj
+++ b/test/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json.Tests/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json.Tests.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/test/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Tests/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Tests.csproj b/test/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Tests/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Tests.csproj
index b4f1d4429..a622efa82 100644
--- a/test/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Tests/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Tests.csproj
+++ b/test/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Tests/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Tests.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/test/Cuemon.Extensions.AspNetCore.Mvc.RazorPages.Tests/Cuemon.Extensions.AspNetCore.Mvc.RazorPages.Tests.csproj b/test/Cuemon.Extensions.AspNetCore.Mvc.RazorPages.Tests/Cuemon.Extensions.AspNetCore.Mvc.RazorPages.Tests.csproj
index 3fce3f080..1b6879a6e 100644
--- a/test/Cuemon.Extensions.AspNetCore.Mvc.RazorPages.Tests/Cuemon.Extensions.AspNetCore.Mvc.RazorPages.Tests.csproj
+++ b/test/Cuemon.Extensions.AspNetCore.Mvc.RazorPages.Tests/Cuemon.Extensions.AspNetCore.Mvc.RazorPages.Tests.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/test/Cuemon.Extensions.AspNetCore.Mvc.Tests/Cuemon.Extensions.AspNetCore.Mvc.Tests.csproj b/test/Cuemon.Extensions.AspNetCore.Mvc.Tests/Cuemon.Extensions.AspNetCore.Mvc.Tests.csproj
index e42af0a35..0bcab967c 100644
--- a/test/Cuemon.Extensions.AspNetCore.Mvc.Tests/Cuemon.Extensions.AspNetCore.Mvc.Tests.csproj
+++ b/test/Cuemon.Extensions.AspNetCore.Mvc.Tests/Cuemon.Extensions.AspNetCore.Mvc.Tests.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/test/Cuemon.Extensions.AspNetCore.Tests/Cuemon.Extensions.AspNetCore.Tests.csproj b/test/Cuemon.Extensions.AspNetCore.Tests/Cuemon.Extensions.AspNetCore.Tests.csproj
index 723a4d5b5..47bf11be6 100644
--- a/test/Cuemon.Extensions.AspNetCore.Tests/Cuemon.Extensions.AspNetCore.Tests.csproj
+++ b/test/Cuemon.Extensions.AspNetCore.Tests/Cuemon.Extensions.AspNetCore.Tests.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/test/Cuemon.Extensions.Core.Tests/AsyncDisposableTests.cs b/test/Cuemon.Extensions.Core.Tests/AsyncDisposableTests.cs
new file mode 100644
index 000000000..be3cf91d5
--- /dev/null
+++ b/test/Cuemon.Extensions.Core.Tests/AsyncDisposableTests.cs
@@ -0,0 +1,88 @@
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Codebelt.Extensions.Xunit;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Cuemon.Extensions
+{
+ public class AsyncDisposableTest : Test
+ {
+ public AsyncDisposableTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ private class TestAsyncDisposable : AsyncDisposable
+ {
+ public bool ManagedResourcesDisposed { get; private set; }
+
+ public bool IsAsyncOperationCompleted { get; private set; }
+
+ public bool UnmanagedResourcesDisposed { get; private set; }
+
+ protected override ValueTask OnDisposeManagedResourcesAsync()
+ {
+ ManagedResourcesDisposed = true;
+ return new ValueTask(Task.Run(async () =>
+ {
+ await Task.Delay(100); // Simulate async work
+ IsAsyncOperationCompleted = true;
+ }));
+ }
+
+ protected override void OnDisposeUnmanagedResources()
+ {
+ UnmanagedResourcesDisposed = true;
+ }
+ }
+
+ [Fact]
+ public async Task DisposeAsync_ShouldCallOnDisposeManagedResourcesAsync()
+ {
+ // Arrange
+ var disposable = new TestAsyncDisposable();
+
+ // Act
+ await disposable.DisposeAsync();
+
+ // Assert
+ Assert.True(disposable.ManagedResourcesDisposed);
+ }
+
+ [Fact]
+ public async Task DisposeAsync_ShouldSuppressFinalize()
+ {
+ // Arrange
+ var disposable = new TestAsyncDisposable();
+
+ // Act
+ await disposable.DisposeAsync();
+
+ // Assert
+ Assert.True(disposable.Disposed);
+ }
+
+ [Theory]
+ [InlineData(100)] // Fast disposal
+ [InlineData(1000)] // Slower disposal
+ public async Task DisposeAsync_ShouldHandleVariousAsyncScenarios(int delayMs)
+ {
+ // Arrange
+ var disposable = new TestAsyncDisposable();
+ var sw = Stopwatch.StartNew();
+
+ // Act
+ await using (disposable)
+ {
+ await Task.Delay(delayMs);
+ }
+
+ sw.Stop();
+
+ // Assert
+ Assert.True(disposable.ManagedResourcesDisposed);
+ Assert.True(disposable.IsAsyncOperationCompleted);
+ Assert.True(sw.ElapsedMilliseconds >= delayMs);
+ }
+ }
+}
diff --git a/test/Cuemon.Extensions.Hosting.Tests/Cuemon.Extensions.Hosting.Tests.csproj b/test/Cuemon.Extensions.Hosting.Tests/Cuemon.Extensions.Hosting.Tests.csproj
index c5c4fb80b..d8b4ee373 100644
--- a/test/Cuemon.Extensions.Hosting.Tests/Cuemon.Extensions.Hosting.Tests.csproj
+++ b/test/Cuemon.Extensions.Hosting.Tests/Cuemon.Extensions.Hosting.Tests.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/test/Cuemon.Extensions.Runtime.Caching.Tests/Cuemon.Extensions.Runtime.Caching.Tests.csproj b/test/Cuemon.Extensions.Runtime.Caching.Tests/Cuemon.Extensions.Runtime.Caching.Tests.csproj
index 9ca10b451..f3391730e 100644
--- a/test/Cuemon.Extensions.Runtime.Caching.Tests/Cuemon.Extensions.Runtime.Caching.Tests.csproj
+++ b/test/Cuemon.Extensions.Runtime.Caching.Tests/Cuemon.Extensions.Runtime.Caching.Tests.csproj
@@ -9,7 +9,7 @@
-
+
\ No newline at end of file
diff --git a/test/Cuemon.Runtime.Caching.Tests/Cuemon.Runtime.Caching.Tests.csproj b/test/Cuemon.Runtime.Caching.Tests/Cuemon.Runtime.Caching.Tests.csproj
index f0b35a7bd..7185b1075 100644
--- a/test/Cuemon.Runtime.Caching.Tests/Cuemon.Runtime.Caching.Tests.csproj
+++ b/test/Cuemon.Runtime.Caching.Tests/Cuemon.Runtime.Caching.Tests.csproj
@@ -15,7 +15,7 @@
-
+
\ No newline at end of file