diff --git a/csharp/Platform.Disposables.Tests/DisposableTests.cs b/csharp/Platform.Disposables.Tests/DisposableTests.cs index caa19fb..4ab8d95 100644 --- a/csharp/Platform.Disposables.Tests/DisposableTests.cs +++ b/csharp/Platform.Disposables.Tests/DisposableTests.cs @@ -42,7 +42,7 @@ private static ProcessStartInfo CreateProcessStartInfo(string logPath, bool wait return new ProcessStartInfo { FileName = "dotnet", - Arguments = $"run -p \"{projectPath}\" -f net7 \"{logPath}\" {waitForCancellation.ToString()}", + Arguments = $"run -p \"{projectPath}\" -f net8 \"{logPath}\" {waitForCancellation.ToString()}", UseShellExecute = false, CreateNoWindow = true }; @@ -79,5 +79,114 @@ private static string GetDisposalObjectTestProjectFilePath() } return path; } + + [Fact] + public static void AllowMultipleDisposeCallsConstructorTest() + { + var disposeCount = 0; + Action incrementCount = () => disposeCount++; + + var disposable = new Disposable(incrementCount, allowMultipleDisposeCalls: true); + + disposable.Dispose(); + disposable.Dispose(); + disposable.Dispose(); + + Assert.Equal(1, disposeCount); + } + + [Fact] + public static void AllowMultipleDisposeAttemptsConstructorTest() + { + var disposeCount = 0; + Disposal incrementCount = (manual, wasDisposed) => + { + if (!wasDisposed) disposeCount++; + }; + + var disposable = new Disposable(incrementCount, allowMultipleDisposeCalls: true, allowMultipleDisposeAttempts: true); + + disposable.Dispose(); + disposable.Dispose(); + + Assert.Equal(1, disposeCount); + } + + [Fact] + public static void ParameterlessConstructorWithAllowMultipleDisposeCallsTest() + { + var disposable = new Disposable(allowMultipleDisposeCalls: true); + + disposable.Dispose(); + disposable.Dispose(); + } + + [Fact] + public static void ParameterlessConstructorWithAllowMultipleDisposeAttemptsTest() + { + var disposable = new Disposable(allowMultipleDisposeCalls: true, allowMultipleDisposeAttempts: true); + + disposable.Dispose(); + disposable.Dispose(); + } + + [Fact] + public static void DefaultParameterlessConstructorTest() + { + var disposable = new Disposable(); + + disposable.Dispose(); + + Assert.Throws(() => disposable.Dispose()); + } + + [Fact] + public static void DefaultActionConstructorTest() + { + var disposeCount = 0; + Action incrementCount = () => disposeCount++; + + var disposable = new Disposable(incrementCount); + + disposable.Dispose(); + + Assert.Equal(1, disposeCount); + Assert.Throws(() => disposable.Dispose()); + } + + [Fact] + public static void DefaultDisposalConstructorTest() + { + var disposeCount = 0; + Disposal incrementCount = (manual, wasDisposed) => + { + if (!wasDisposed) disposeCount++; + }; + + var disposable = new Disposable(incrementCount); + + disposable.Dispose(); + + Assert.Equal(1, disposeCount); + Assert.Throws(() => disposable.Dispose()); + } + + [Fact] + public static void ReplacesWorkaroundFromIssueTest() + { + var disposeCount = 0; + Disposal incrementCount = (manual, wasDisposed) => + { + if (!wasDisposed) disposeCount++; + }; + + var disposableWithNewConstructor = new Disposable(incrementCount, allowMultipleDisposeCalls: true); + + disposableWithNewConstructor.Dispose(); + disposableWithNewConstructor.Dispose(); + disposableWithNewConstructor.Dispose(); + + Assert.Equal(1, disposeCount); + } } } diff --git a/csharp/Platform.Disposables/Disposable.cs b/csharp/Platform.Disposables/Disposable.cs index 9d0c028..2fc34ee 100644 --- a/csharp/Platform.Disposables/Disposable.cs +++ b/csharp/Platform.Disposables/Disposable.cs @@ -11,20 +11,47 @@ public class Disposable : DisposableBase { private static readonly Disposal _emptyDelegate = (manual, wasDisposed) => { }; + private readonly bool? _allowMultipleDisposeCalls; + private readonly bool? _allowMultipleDisposeAttempts; + /// /// Occurs when the object is being disposed. /// Возникает, когда объект высвобождается. /// public event Disposal OnDispose; + /// + /// Gets a value indicating whether multiple attempts to dispose this object are allowed. + /// Возвращает значение определяющие разрешено ли выполнять несколько попыток высвободить этот объект. + /// + protected override bool AllowMultipleDisposeAttempts + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _allowMultipleDisposeAttempts ?? base.AllowMultipleDisposeAttempts; + } + + /// + /// Gets a value indicating whether it is allowed to call this object disposal multiple times. + /// Возвращает значение определяющие разрешено ли несколько раз вызывать высвобождение этого объекта. + /// + protected override bool AllowMultipleDisposeCalls + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _allowMultipleDisposeCalls ?? base.AllowMultipleDisposeCalls; + } + /// /// Initializes a new instance of the object. /// Инициализирует новый экземпляр объекта . /// /// The delegate.Делегат . + /// A value indicating whether it is allowed to call this object disposal multiple times.Значение, определяющее разрешено ли несколько раз вызывать высвобождение этого объекта. + /// A value indicating whether multiple attempts to dispose this object are allowed.Значение, определяющее разрешено ли выполнять несколько попыток высвободить этот объект. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Disposable(Action action) + public Disposable(Action action, bool allowMultipleDisposeCalls = false, bool allowMultipleDisposeAttempts = false) { + _allowMultipleDisposeCalls = allowMultipleDisposeCalls; + _allowMultipleDisposeAttempts = allowMultipleDisposeAttempts; OnDispose = (manual, wasDisposed) => { if (!wasDisposed) @@ -39,8 +66,15 @@ public Disposable(Action action) /// Инициализирует новый экземпляр объекта . /// /// The delegate.Делегат . + /// A value indicating whether it is allowed to call this object disposal multiple times.Значение, определяющее разрешено ли несколько раз вызывать высвобождение этого объекта. + /// A value indicating whether multiple attempts to dispose this object are allowed.Значение, определяющее разрешено ли выполнять несколько попыток высвободить этот объект. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Disposable(Disposal disposal) => OnDispose = disposal; + public Disposable(Disposal disposal, bool allowMultipleDisposeCalls = false, bool allowMultipleDisposeAttempts = false) + { + _allowMultipleDisposeCalls = allowMultipleDisposeCalls; + _allowMultipleDisposeAttempts = allowMultipleDisposeAttempts; + OnDispose = disposal; + } /// /// Initializes a new instance of the object. @@ -49,6 +83,20 @@ public Disposable(Action action) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Disposable() => OnDispose = _emptyDelegate; + /// + /// Initializes a new instance of the object. + /// Инициализирует новый экземпляр объекта . + /// + /// A value indicating whether it is allowed to call this object disposal multiple times.Значение, определяющее разрешено ли несколько раз вызывать высвобождение этого объекта. + /// A value indicating whether multiple attempts to dispose this object are allowed.Значение, определяющее разрешено ли выполнять несколько попыток высвободить этот объект. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Disposable(bool allowMultipleDisposeCalls, bool allowMultipleDisposeAttempts = false) + { + _allowMultipleDisposeCalls = allowMultipleDisposeCalls; + _allowMultipleDisposeAttempts = allowMultipleDisposeAttempts; + OnDispose = _emptyDelegate; + } + /// /// Creates a new object initialized with specified delegate . /// Создает новый объект , инициализированную с помощью указанного делегата .