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