diff --git a/PowerKit.Tests/DisposableTests.cs b/PowerKit.Tests/DisposableTests.cs index 9a9514b..21110c3 100644 --- a/PowerKit.Tests/DisposableTests.cs +++ b/PowerKit.Tests/DisposableTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using FluentAssertions; using Xunit; @@ -21,6 +22,22 @@ public void Create_Test() invoked.Should().BeTrue(); } + [Fact] + public void Create_Idempotent_Test() + { + // Arrange + var count = 0; + var disposable = Disposable.Create(() => Interlocked.Increment(ref count)); + + // Act + disposable.Dispose(); + disposable.Dispose(); + disposable.Dispose(); + + // Assert + count.Should().Be(1); + } + [Fact] public void Merge_Test() { diff --git a/PowerKit/Disposable.cs b/PowerKit/Disposable.cs index ff3cf6e..648a974 100644 --- a/PowerKit/Disposable.cs +++ b/PowerKit/Disposable.cs @@ -1,11 +1,16 @@ using System; using System.Collections.Generic; +using System.Threading; namespace PowerKit; file class DelegateDisposable(Action dispose) : IDisposable { - public void Dispose() => dispose(); + private Action? _dispose = dispose; + + public void Dispose() => + // Idempotency + Interlocked.Exchange(ref _dispose, null)?.Invoke(); } /// @@ -21,6 +26,9 @@ public static class Disposable /// /// Creates a disposable that invokes the specified action when disposed. /// + /// + /// The returned disposable is idempotent and invokes the action at most once. + /// public static IDisposable Create(Action dispose) => new DelegateDisposable(dispose); ///