From 3bc63e04301b6cd36cc6e35bba4c321104d7edde Mon Sep 17 00:00:00 2001 From: Andrew Koryavchenko Date: Mon, 20 Apr 2020 00:27:13 +0300 Subject: [PATCH 1/3] Add AsyncDisposable --- CodeJam.Main.Tests/AsyncDisposableTest.cs | 38 +++++++++++++ CodeJam.Main.Tests/CodeJam.Main.Tests.csproj | 2 +- CodeJam.Main/AnonymousDisposable.cs | 57 ++++++++++++++++++++ CodeJam.Main/AsyncAnonymousDisposable.cs | 43 +++++++++++++++ CodeJam.Main/AsyncDisposable.cs | 49 +++++++++++++++++ CodeJam.Main/CodeJam.Main.csproj | 2 +- CodeJam.Main/Disposable.cs | 39 +------------- CodeJam.Main/DisposableExtensions.cs | 45 ++++++++++++++-- 8 files changed, 232 insertions(+), 43 deletions(-) create mode 100644 CodeJam.Main.Tests/AsyncDisposableTest.cs create mode 100644 CodeJam.Main/AnonymousDisposable.cs create mode 100644 CodeJam.Main/AsyncAnonymousDisposable.cs create mode 100644 CodeJam.Main/AsyncDisposable.cs diff --git a/CodeJam.Main.Tests/AsyncDisposableTest.cs b/CodeJam.Main.Tests/AsyncDisposableTest.cs new file mode 100644 index 000000000..2a2ce1cd9 --- /dev/null +++ b/CodeJam.Main.Tests/AsyncDisposableTest.cs @@ -0,0 +1,38 @@ +#if NETCOREAPP30_OR_GREATER || NETSTANDARD21_OR_GREATER +using System.Threading.Tasks; + +using NUnit.Framework; + +namespace CodeJam +{ + public class AsyncDisposableTest + { + [Test] + public async Task TestDisposable() + { + var value = 0; + var disposable = AsyncDisposable.Create(() => + { + value++; + return default; + }); + // Fails if Disposable.Create returns struct + var disposable2 = disposable; + + Assert.That(value, Is.EqualTo(0)); + + disposable.Dispose(); + Assert.That(value, Is.EqualTo(1)); + + disposable.Dispose(); + Assert.That(value, Is.EqualTo(1)); + + disposable2.Dispose(); + Assert.That(value, Is.EqualTo(1)); + + await disposable.DisposeAsync(); + Assert.That(value, Is.EqualTo(1)); + } + } +} +#endif \ No newline at end of file diff --git a/CodeJam.Main.Tests/CodeJam.Main.Tests.csproj b/CodeJam.Main.Tests/CodeJam.Main.Tests.csproj index 4bb4a883d..a369eb591 100644 --- a/CodeJam.Main.Tests/CodeJam.Main.Tests.csproj +++ b/CodeJam.Main.Tests/CodeJam.Main.Tests.csproj @@ -8,7 +8,7 @@ CodeJam {DD65E3F2-9658-4242-A8AA-056028473CB1} Library - net48;net472;net471;net47;net461;net45;net40;net35;net20;netcoreapp3.0;netcoreapp2.1;netcoreapp1.1 + netcoreapp3.0;net48;net472;net471;net47;net461;net45;net40;net35;net20;netcoreapp2.1;netcoreapp1.1 true true diff --git a/CodeJam.Main/AnonymousDisposable.cs b/CodeJam.Main/AnonymousDisposable.cs new file mode 100644 index 000000000..8b4780739 --- /dev/null +++ b/CodeJam.Main/AnonymousDisposable.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading; + +using JetBrains.Annotations; + +namespace CodeJam +{ + /// + /// The implementation that calls supplied action on . + /// + /// DONTTOUCH: DO NOT make it a struct, passing the structure by value will result in multiple Dispose() calls. + /// SEEALSO: https://blogs.msdn.microsoft.com/ericlippert/2011/03/14/to-box-or-not-to-box-that-is-the-question/ + public class AnonymousDisposable : IDisposable + { + /// + /// If 0 - instance not disposed. + /// + /// + /// Use int instead of bool, because support it. + /// + protected int Disposed; + + [CanBeNull] + private readonly Action _disposeAction; + + /// Initialize instance. + /// The dispose action. + public AnonymousDisposable([CanBeNull] Action disposeAction) => _disposeAction = disposeAction; + + /// + public void Dispose() + { + if (_disposeAction == null) + return; + var disposed = Interlocked.Exchange(ref Disposed, 1); + if (disposed == 0) + { + try + { + _disposeAction(); + } + catch when (OnException(disposed)) + { + } + } + } + + /// + /// Restore value and returns false. + /// + protected bool OnException(int disposed) + { + Interlocked.Exchange(ref Disposed, disposed); + return false; + } + } +} \ No newline at end of file diff --git a/CodeJam.Main/AsyncAnonymousDisposable.cs b/CodeJam.Main/AsyncAnonymousDisposable.cs new file mode 100644 index 000000000..f359486d6 --- /dev/null +++ b/CodeJam.Main/AsyncAnonymousDisposable.cs @@ -0,0 +1,43 @@ +#if NETCOREAPP30_OR_GREATER || NETSTANDARD21_OR_GREATER +using System; +using System.Threading; +using System.Threading.Tasks; + +using JetBrains.Annotations; + +namespace CodeJam +{ + /// + /// and implementation. + /// + [PublicAPI] + public class AsyncAnonymousDisposable : AnonymousDisposable, IAsyncDisposable + { + [CanBeNull] + private Func _asyncDisposeAction; + + /// + /// Initialize instance. + /// + /// Sync dispose action. + /// Async dispose action. + public AsyncAnonymousDisposable([CanBeNull] Action syncDisposeAction, [CanBeNull] Func asyncDisposeAction) + : base(syncDisposeAction) => + _asyncDisposeAction = asyncDisposeAction; + + /// + public async ValueTask DisposeAsync() + { + if (_asyncDisposeAction == null) + return; + var disposed = Interlocked.Exchange(ref Disposed, 1); + if (disposed == 0) + try + { + await _asyncDisposeAction(); + } + catch when (OnException(disposed)) { } + } + } +} +#endif \ No newline at end of file diff --git a/CodeJam.Main/AsyncDisposable.cs b/CodeJam.Main/AsyncDisposable.cs new file mode 100644 index 000000000..d5ed72ad1 --- /dev/null +++ b/CodeJam.Main/AsyncDisposable.cs @@ -0,0 +1,49 @@ +#if NETCOREAPP30_OR_GREATER || NETSTANDARD21_OR_GREATER +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using JetBrains.Annotations; + +namespace CodeJam +{ + /// + /// Helper methods for . + /// + [PublicAPI] + public static class AsyncDisposable + { + /// + /// and implementation without any action. + /// + public static readonly AsyncAnonymousDisposable Empty = + new AsyncAnonymousDisposable(null, null); + + /// + /// Creates anonymous disposable with sync and async dispose actions. + /// + public static AsyncAnonymousDisposable Create(Action syncAction, Func asyncAction) => + new AsyncAnonymousDisposable(syncAction, asyncAction); + + /// + /// Creates anonymous async disposable. + /// + public static AsyncAnonymousDisposable Create(Func asyncAction) => + new AsyncAnonymousDisposable( + () => asyncAction?.Invoke().GetAwaiter().GetResult(), + asyncAction); + + // Combine multiple instances into single one. + /// The disposables. + /// Instance of that will dispose the specified disposables. + public static AsyncAnonymousDisposable Merge([NotNull, ItemNotNull] this IEnumerable disposables) => + Create(disposables.DisposeAllAsync); + + // Combine multiple instances into single one. + /// The disposables. + /// Instance of that will dispose the specified disposables. + public static AsyncAnonymousDisposable Merge([NotNull] params IAsyncDisposable[] disposables) => + Merge((IEnumerable)disposables); + } +} +#endif \ No newline at end of file diff --git a/CodeJam.Main/CodeJam.Main.csproj b/CodeJam.Main/CodeJam.Main.csproj index 8789a691a..401fb5847 100644 --- a/CodeJam.Main/CodeJam.Main.csproj +++ b/CodeJam.Main/CodeJam.Main.csproj @@ -9,7 +9,7 @@ CodeJam {2F2046CC-FB47-4318-B335-5A82B04B6C40} Library - net472;net461;net45;net40;net35;net20;netstandard2.1;netstandard2.0;netstandard1.5;netstandard1.3;netcoreapp3.0;netcoreapp2.0;netcoreapp1.0 + netcoreapp3.0;net472;net461;net45;net40;net35;net20;netstandard2.1;netstandard2.0;netstandard1.5;netstandard1.3;netcoreapp2.0;netcoreapp1.0 true true 8.0 diff --git a/CodeJam.Main/Disposable.cs b/CodeJam.Main/Disposable.cs index c4490d86f..73d2fa88f 100644 --- a/CodeJam.Main/Disposable.cs +++ b/CodeJam.Main/Disposable.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; @@ -22,44 +23,6 @@ public sealed class EmptyDisposable : IDisposable public void Dispose() { } } - /// - /// The implementation that calls supplied action on . - /// - /// DONTTOUCH: DO NOT make it a struct, passing the structure by value will result in multiple Dispose() calls. - /// SEEALSO: https://blogs.msdn.microsoft.com/ericlippert/2011/03/14/to-box-or-not-to-box-that-is-the-question/ - private sealed class AnonymousDisposable : IDisposable - { - private Action _disposeAction; - - /// Initialize instance. - /// The dispose action. - public AnonymousDisposable(Action disposeAction) => _disposeAction = disposeAction; - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - var disposeAction = Interlocked.Exchange(ref _disposeAction, null); - if (disposeAction != null) - { - try - { - disposeAction.Invoke(); - } - catch when (OnException(disposeAction)) - { - } - } - } - - private bool OnException(Action disposeAction) - { - Interlocked.Exchange(ref _disposeAction, disposeAction); - return false; - } - } - /// /// The implementation that calls supplied action on . /// diff --git a/CodeJam.Main/DisposableExtensions.cs b/CodeJam.Main/DisposableExtensions.cs index 936a848fa..f85d2f63c 100644 --- a/CodeJam.Main/DisposableExtensions.cs +++ b/CodeJam.Main/DisposableExtensions.cs @@ -29,9 +29,7 @@ public static void DisposeAll([NotNull, ItemNotNull, InstantHandle] this IEnumer } catch (Exception ex) { - if (exceptions == null) - exceptions = new List(); - + exceptions ??= new List(); exceptions.Add(ex); } } @@ -74,6 +72,47 @@ public static ValueTask DisposeAsync([NotNull] this IDisposable disposable) disposable.Dispose(); return new ValueTask(); } + + /// Invokes the dispose for each item in the . + /// The multiple instances. + /// + public static async ValueTask DisposeAllAsync( + [NotNull, ItemNotNull, InstantHandle] this IEnumerable disposables) + { + List exceptions = null; + + foreach (var item in disposables) + try + { + await item.DisposeAsync(); + } + catch (Exception ex) + { + exceptions ??= new List(); + exceptions.Add(ex); + } + + if (exceptions != null) + throw new AggregateException(exceptions); + } + + /// Invokes the dispose for each item in the . + /// The multiple instances. + /// The exception handler. + public static async ValueTask DisposeAllAsync( + [NotNull, ItemNotNull, InstantHandle] this IEnumerable disposables, + [NotNull, InstantHandle] Func exceptionHandler) + { + foreach (var item in disposables) + try + { + await item.DisposeAsync(); + } + catch (Exception ex) when (exceptionHandler(ex)) + { + ex.LogToCodeTraceSourceOnCatch(true); + } + } #endif } } \ No newline at end of file From f53326bfa708c2727e88e4fd9f619a5bee149175 Mon Sep 17 00:00:00 2001 From: Andrew Koryavchenko Date: Mon, 20 Apr 2020 13:51:05 +0300 Subject: [PATCH 2/3] Remove AnonymouseDisposable --- CodeJam.Main/Disposable.cs | 50 ++------------------------------------ 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/CodeJam.Main/Disposable.cs b/CodeJam.Main/Disposable.cs index 73d2fa88f..460f7216b 100644 --- a/CodeJam.Main/Disposable.cs +++ b/CodeJam.Main/Disposable.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; @@ -23,51 +21,6 @@ public sealed class EmptyDisposable : IDisposable public void Dispose() { } } - /// - /// The implementation that calls supplied action on . - /// - /// Disposable state type. - /// DONTTOUCH: DO NOT make it a struct, passing the structure by value will result in multiple Dispose() calls. - /// SEEALSO: https://blogs.msdn.microsoft.com/ericlippert/2011/03/14/to-box-or-not-to-box-that-is-the-question/ - private sealed class AnonymousDisposable : IDisposable - { - private Action _disposeAction; - private T _state; - - /// Initialize instance. - /// The dispose action. - /// A value that contains data for the disposal action. - public AnonymousDisposable(Action disposeAction, T state) - { - _disposeAction = disposeAction; - _state = state; - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - var disposeAction = Interlocked.Exchange(ref _disposeAction, null); - if (disposeAction != null) - { - try - { - disposeAction.Invoke(_state); - _state = default; - } - catch when (OnException(disposeAction)) - { - } - } - } - - private bool OnException(Action disposeAction) - { - Interlocked.Exchange(ref _disposeAction, disposeAction); - return false; - } - } #endregion /// instance without any code in . @@ -93,7 +46,8 @@ private bool OnException(Action disposeAction) /// Instance of that calls on disposing. /// [NotNull, Pure] - public static IDisposable Create([NotNull] Action disposeAction, [CanBeNull] T state) => new AnonymousDisposable(disposeAction, state); + public static IDisposable Create([NotNull] Action disposeAction, [CanBeNull] T state) => + new AnonymousDisposable(() => disposeAction?.Invoke(state)); /// Combine multiple instances into single one. /// The disposables. From ace02db0e08c8305c950b7b712c076faca789b47 Mon Sep 17 00:00:00 2001 From: ig-sinicyn Date: Sun, 31 May 2020 14:49:00 +0300 Subject: [PATCH 3/3] + tests; + AsyncInitDispose + CreateNested() method --- Build/Props/CodeJam.Targeting.props | 4 +- .../CodeJam.Blocks.Tests.csproj | 2 +- CodeJam.Blocks/CodeJam.Blocks.csproj | 2 +- CodeJam.Main.Tests/AsyncDisposableTest.cs | 38 --- CodeJam.Main.Tests/AsyncDisposableTests.cs | 260 ++++++++++++++++++ CodeJam.Main.Tests/DisposableTests.cs | 29 +- CodeJam.Main/AsyncDisposable.cs | 44 ++- CodeJam.Main/AsyncInitDispose.cs | 92 +++++++ CodeJam.Main/Disposable.cs | 18 +- CodeJam.Main/InitDispose.cs | 5 +- 10 files changed, 437 insertions(+), 57 deletions(-) delete mode 100644 CodeJam.Main.Tests/AsyncDisposableTest.cs create mode 100644 CodeJam.Main.Tests/AsyncDisposableTests.cs create mode 100644 CodeJam.Main/AsyncInitDispose.cs diff --git a/Build/Props/CodeJam.Targeting.props b/Build/Props/CodeJam.Targeting.props index 71011df9d..4dbc2c181 100644 --- a/Build/Props/CodeJam.Targeting.props +++ b/Build/Props/CodeJam.Targeting.props @@ -11,8 +11,8 @@ net461;netstandard2.0 net461;netcoreapp2.0 - net472;net461;net45;net40;net35;net20;netstandard2.1;netstandard2.0;netstandard1.5;netstandard1.3;netcoreapp3.0;netcoreapp2.0;netcoreapp1.0 - net48;net472;net471;net47;net461;net45;net40;net35;net20;netcoreapp3.0;netcoreapp2.1;netcoreapp1.1 + netcoreapp3.0;net472;net461;net45;net40;net35;net20;netstandard2.1;netstandard2.0;netstandard1.5;netstandard1.3;netcoreapp2.0;netcoreapp1.0 + netcoreapp3.0;net48;net472;net471;net47;net461;net45;net40;net35;net20;netcoreapp2.1;netcoreapp1.1 diff --git a/CodeJam.Blocks.Tests/CodeJam.Blocks.Tests.csproj b/CodeJam.Blocks.Tests/CodeJam.Blocks.Tests.csproj index 0bc27df6a..1af8c8f36 100644 --- a/CodeJam.Blocks.Tests/CodeJam.Blocks.Tests.csproj +++ b/CodeJam.Blocks.Tests/CodeJam.Blocks.Tests.csproj @@ -8,7 +8,7 @@ CodeJam {2A52D7F6-AAFC-4AC4-9901-252C13D96E53} Library - net48;net472;net471;net47;net461;net45;net40;net35;net20;netcoreapp3.0;netcoreapp2.1;netcoreapp1.1 + netcoreapp3.0;net48;net472;net471;net47;net461;net45;net40;net35;net20;netcoreapp2.1;netcoreapp1.1 true diff --git a/CodeJam.Blocks/CodeJam.Blocks.csproj b/CodeJam.Blocks/CodeJam.Blocks.csproj index 2c571bf37..1afa6bd2d 100644 --- a/CodeJam.Blocks/CodeJam.Blocks.csproj +++ b/CodeJam.Blocks/CodeJam.Blocks.csproj @@ -9,7 +9,7 @@ CodeJam {0DFF0859-2400-4487-83AD-0ED10203D6D9} Library - net472;net461;net45;net40;net35;net20;netstandard2.1;netstandard2.0;netstandard1.5;netstandard1.3;netcoreapp3.0;netcoreapp2.0;netcoreapp1.0 + netcoreapp3.0;net472;net461;net45;net40;net35;net20;netstandard2.1;netstandard2.0;netstandard1.5;netstandard1.3;netcoreapp2.0;netcoreapp1.0 true 8.0 diff --git a/CodeJam.Main.Tests/AsyncDisposableTest.cs b/CodeJam.Main.Tests/AsyncDisposableTest.cs deleted file mode 100644 index 2a2ce1cd9..000000000 --- a/CodeJam.Main.Tests/AsyncDisposableTest.cs +++ /dev/null @@ -1,38 +0,0 @@ -#if NETCOREAPP30_OR_GREATER || NETSTANDARD21_OR_GREATER -using System.Threading.Tasks; - -using NUnit.Framework; - -namespace CodeJam -{ - public class AsyncDisposableTest - { - [Test] - public async Task TestDisposable() - { - var value = 0; - var disposable = AsyncDisposable.Create(() => - { - value++; - return default; - }); - // Fails if Disposable.Create returns struct - var disposable2 = disposable; - - Assert.That(value, Is.EqualTo(0)); - - disposable.Dispose(); - Assert.That(value, Is.EqualTo(1)); - - disposable.Dispose(); - Assert.That(value, Is.EqualTo(1)); - - disposable2.Dispose(); - Assert.That(value, Is.EqualTo(1)); - - await disposable.DisposeAsync(); - Assert.That(value, Is.EqualTo(1)); - } - } -} -#endif \ No newline at end of file diff --git a/CodeJam.Main.Tests/AsyncDisposableTests.cs b/CodeJam.Main.Tests/AsyncDisposableTests.cs new file mode 100644 index 000000000..15263e812 --- /dev/null +++ b/CodeJam.Main.Tests/AsyncDisposableTests.cs @@ -0,0 +1,260 @@ +#if NETCOREAPP30_OR_GREATER +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; + +using NUnit.Framework; + +namespace CodeJam +{ + [TestFixture(Category = "AsyncDisposable")] + [SuppressMessage("ReSharper", "HeapView.CanAvoidClosure")] + public static class AsyncDisposableTests + { + [Test] + public static void TestAsyncDisposable() + { + var value = 0; + var disposable = AsyncDisposable.Create( + () => + { + value++; + return default; + }); + // Fails if AsyncDisposable.Create returns struct + var disposable2 = disposable; + + Assert.That(value, Is.EqualTo(0)); + + Assert.DoesNotThrowAsync(async () => await disposable.DisposeAsync()); + Assert.That(value, Is.EqualTo(1)); + + Assert.DoesNotThrowAsync(async () => await disposable.DisposeAsync()); + Assert.That(value, Is.EqualTo(1)); + + Assert.DoesNotThrowAsync(async () => await disposable2.DisposeAsync()); + Assert.That(value, Is.EqualTo(1)); + } + + [Test] + public static async Task TestAsyncDisposableArray() + { + var result = new List(); + + await AsyncDisposable + .CreateNested( + AsyncDisposable.Create( + () => + { + result.Add(1); + return default; + }), + AsyncDisposable.Create( + () => + { + result.Add(2); + return default; + }), + AsyncDisposable.Create( + () => + { + result.Add(3); + return default; + })) + .DisposeAsync(); + + Assert.AreEqual(result, new List { 3, 2, 1 }); + } + + [Test] + public static void TestAsyncDisposableThrow() + { + var value = 0; + var disposable = AsyncDisposable.Create( + () => + { + value++; + if (value != 3) + throw new InvalidOperationException(); + return default; + }); + Assert.That(value, Is.EqualTo(0)); + + Assert.ThrowsAsync(async () => await disposable.DisposeAsync()); + Assert.That(value, Is.EqualTo(1)); + + Assert.ThrowsAsync(async () => await disposable.DisposeAsync()); + Assert.That(value, Is.EqualTo(2)); + + Assert.DoesNotThrowAsync(async () => await disposable.DisposeAsync()); + Assert.That(value, Is.EqualTo(3)); + + Assert.DoesNotThrowAsync(async () => await disposable.DisposeAsync()); + Assert.That(value, Is.EqualTo(3)); + } + + [Test] + public static void TestAsyncDisposableMerge() + { + var value1 = 0; + var disposable1 = AsyncDisposable.Create( + () => + { + value1++; + return default; + }); + var value2 = 0; + var disposable2 = AsyncDisposable.Create( + () => + { + value1++; + value2++; + return default; + }); + + var value3 = 0; + var disposable3 = AsyncDisposable.Create( + () => + { + value3++; + if (value3 != 3) + throw new InvalidOperationException("Test message"); + return default; + }); + + var disposable = AsyncDisposable.Merge(disposable1, disposable2, disposable3); + Assert.That(value1, Is.EqualTo(0)); + Assert.That(value2, Is.EqualTo(0)); + Assert.That(value3, Is.EqualTo(0)); + + var ex = Assert.ThrowsAsync(async () => await disposable.DisposeAsync()); + Assert.That(ex.InnerExceptions.Count, Is.EqualTo(1)); + Assert.That( + ex.InnerExceptions.Cast().Single().Message, + Is.EqualTo("Test message")); + Assert.That(value1, Is.EqualTo(2)); + Assert.That(value2, Is.EqualTo(1)); + Assert.That(value3, Is.EqualTo(1)); + + Assert.ThrowsAsync(async () => await disposable.DisposeAsync()); + Assert.That(value1, Is.EqualTo(2)); + Assert.That(value2, Is.EqualTo(1)); + Assert.That(value3, Is.EqualTo(2)); + + Assert.DoesNotThrowAsync(async () => await disposable.DisposeAsync()); + Assert.That(value1, Is.EqualTo(2)); + Assert.That(value2, Is.EqualTo(1)); + Assert.That(value3, Is.EqualTo(3)); + + Assert.DoesNotThrowAsync(async () => await disposable.DisposeAsync()); + Assert.That(value1, Is.EqualTo(2)); + Assert.That(value2, Is.EqualTo(1)); + Assert.That(value3, Is.EqualTo(3)); + } + + [Test] + public static async Task TestParameterizedAnonymousAsyncDisposable() + { + var state = ""; + var disposed = false; + + await using (AsyncDisposable.Create( + s => + { + disposed = true; + state = s; + return default; + }, "state")) { } + + Assert.IsTrue(disposed); + Assert.AreEqual("state", state); + } + + [Test] + public static async Task AsyncInitDisposeTest1() + { + var i = 0; + + await using (await AsyncInitDispose.CreateAsync( + () => + { + Assert.That(++i, Is.EqualTo(1)); + return new ValueTask(i); + }, + _ => + { + Assert.That(++i, Is.EqualTo(3)); + return new ValueTask(); + })) + { + Assert.That(++i, Is.EqualTo(2)); + } + + Assert.That(++i, Is.EqualTo(4)); + } + + [Test] + public static async Task AsyncInitDisposeTest2() + { + var i = 0; + + await using (await AsyncInitDispose.CreateAsync( + init => + { + Assert.That(++i, Is.EqualTo(init ? 1 : 3)); + return default; + })) + { + Assert.That(++i, Is.EqualTo(2)); + } + + Assert.That(++i, Is.EqualTo(4)); + } + + [Test] + public static async Task AsyncInitDisposeTest3() + { + var i = 0; + + await using (await AsyncInitDispose.CreateAsync( + isInit => + { + Assert.That(i += isInit ? 1 : 2, Is.EqualTo(1).Or.EqualTo(4)); + return default; + })) + { + Assert.That(++i, Is.EqualTo(2)); + } + + Assert.That(++i, Is.EqualTo(5)); + } + + [Test] + public static async Task AsyncInitDisposeTest4() + { + var i = 0; + + await using (await AsyncInitDispose.CreateAsync( + () => + { + Assert.That(++i, Is.EqualTo(1)); + return new ValueTask("123"); + }, + s => + { + Assert.That(++i, Is.EqualTo(3)); + Assert.That(s, Is.EqualTo("123")); + return default; + })) + { + Assert.That(++i, Is.EqualTo(2)); + } + + Assert.That(++i, Is.EqualTo(4)); + } + } +} + +#endif \ No newline at end of file diff --git a/CodeJam.Main.Tests/DisposableTests.cs b/CodeJam.Main.Tests/DisposableTests.cs index dae6d9da7..19db73cb4 100644 --- a/CodeJam.Main.Tests/DisposableTests.cs +++ b/CodeJam.Main.Tests/DisposableTests.cs @@ -3,6 +3,7 @@ #endif using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -34,6 +35,21 @@ public static void TestDisposable() Assert.That(value, Is.EqualTo(1)); } + [Test] + public static void TestDisposableArray() + { + var result = new List(); + + Disposable + .CreateNested( + Disposable.Create(() => result.Add(1)), + Disposable.Create(() => result.Add(2)), + Disposable.Create(() => result.Add(3))) + .Dispose(); + + Assert.AreEqual(result, new List { 3, 2, 1 }); + } + [Test] public static void TestDisposableThrow() { @@ -118,9 +134,12 @@ public static void TestParameterizedAnonymousDisposable() var state = ""; var disposed = false; - using (Disposable.Create(s => {disposed = true; state = s;}, "state")) - { - } + using (Disposable.Create( + s => + { + disposed = true; + state = s; + }, "state")) { } Assert.IsTrue(disposed); Assert.AreEqual("state", state); @@ -183,7 +202,7 @@ public static void InitDisposeTest4() s => { Assert.That(++i, Is.EqualTo(3)); - Assert.That(s, Is.EqualTo("123")); + Assert.That(s, Is.EqualTo("123")); })) { Assert.That(++i, Is.EqualTo(2)); @@ -192,4 +211,4 @@ public static void InitDisposeTest4() Assert.That(++i, Is.EqualTo(4)); } } -} +} \ No newline at end of file diff --git a/CodeJam.Main/AsyncDisposable.cs b/CodeJam.Main/AsyncDisposable.cs index d5ed72ad1..60e1977c3 100644 --- a/CodeJam.Main/AsyncDisposable.cs +++ b/CodeJam.Main/AsyncDisposable.cs @@ -1,6 +1,7 @@ #if NETCOREAPP30_OR_GREATER || NETSTANDARD21_OR_GREATER using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; @@ -28,22 +29,51 @@ public static AsyncAnonymousDisposable Create(Action syncAction, Func /// /// Creates anonymous async disposable. /// - public static AsyncAnonymousDisposable Create(Func asyncAction) => + public static AsyncAnonymousDisposable Create([NotNull] Func asyncAction) => new AsyncAnonymousDisposable( - () => asyncAction?.Invoke().GetAwaiter().GetResult(), + () => asyncAction.Invoke().GetAwaiter().GetResult(), asyncAction); - // Combine multiple instances into single one. + /// + /// Creates anonymous disposable with sync and async dispose actions. + /// + public static AsyncAnonymousDisposable Create(Action syncAction, Func asyncAction, [CanBeNull] T state) => + new AsyncAnonymousDisposable(() => syncAction?.Invoke(state), () => asyncAction?.Invoke(state) ?? default); + + /// + /// Creates anonymous async disposable. + /// + public static AsyncAnonymousDisposable Create([NotNull] Func asyncAction, [CanBeNull] T state) => + new AsyncAnonymousDisposable( + () => asyncAction.Invoke(state).GetAwaiter().GetResult(), + () => asyncAction.Invoke(state)); + + /// + /// Creates instance that calls on disposing in reverse order. + /// + /// The dispose action. + /// + /// Instance of that calls on disposing in reverse order. + /// + [NotNull, Pure] + public static AsyncAnonymousDisposable CreateNested([NotNull, ItemNotNull] params IAsyncDisposable[] disposables) + { + var copy = disposables.ToArray(); + Array.Reverse(copy); + return Merge(copy); + } + + /// Combine multiple instances into single one. /// The disposables. /// Instance of that will dispose the specified disposables. - public static AsyncAnonymousDisposable Merge([NotNull, ItemNotNull] this IEnumerable disposables) => - Create(disposables.DisposeAllAsync); + public static AsyncAnonymousDisposable Merge([NotNull, ItemNotNull] params IAsyncDisposable[] disposables) => + Merge((IEnumerable)disposables); // Combine multiple instances into single one. /// The disposables. /// Instance of that will dispose the specified disposables. - public static AsyncAnonymousDisposable Merge([NotNull] params IAsyncDisposable[] disposables) => - Merge((IEnumerable)disposables); + public static AsyncAnonymousDisposable Merge([NotNull, ItemNotNull] this IEnumerable disposables) => + Create(disposables.DisposeAllAsync); } } #endif \ No newline at end of file diff --git a/CodeJam.Main/AsyncInitDispose.cs b/CodeJam.Main/AsyncInitDispose.cs new file mode 100644 index 000000000..d117ca356 --- /dev/null +++ b/CodeJam.Main/AsyncInitDispose.cs @@ -0,0 +1,92 @@ +#if NETSTANDARD21_OR_GREATER || NETCOREAPP30_OR_GREATER +using System; +using System.Threading.Tasks; + +using JetBrains.Annotations; + +namespace CodeJam +{ + /// Helper methods for + [PublicAPI] + public static class AsyncInitDispose + { + /// + /// Calls and + /// creates instance that calls on disposing. + /// + /// The init action. + /// The dispose action. + /// + /// Instance of that calls on disposing. + /// + [ItemNotNull, Pure] + public static async Task CreateAsync([NotNull] Func initAction, [NotNull] Func disposeAction) + { + Code.NotNull(initAction, nameof(initAction)); + Code.NotNull(disposeAction, nameof(disposeAction)); + + await initAction(); + return AsyncDisposable.Create(disposeAction); + } + + /// + /// Calls and + /// creates instance that calls on disposing. + /// + /// Type of the initialization value. + /// The init action. + /// The dispose action. + /// + /// Instance of that calls on disposing. + /// + [ItemNotNull, Pure] + public static async Task CreateAsync([NotNull] Func> initAction, [NotNull] Func disposeAction) + { + Code.NotNull(initAction, nameof(initAction)); + Code.NotNull(disposeAction, nameof(disposeAction)); + + var initState = await initAction(); + return AsyncDisposable.Create(disposeAction, initState); + } + + /// + /// Calls and + /// creates instance that calls on disposing. + /// + /// The init and dispose action. + /// + /// Instance of that calls on disposing. + /// + [ItemNotNull, Pure] + [Obsolete("Please use overload with Func argument")] + public static async Task CreateAsync([NotNull] Func initDisposeAction) + { + Code.NotNull(initDisposeAction, nameof(initDisposeAction)); + + await initDisposeAction(); + return AsyncDisposable.Create(initDisposeAction); + } + + /// + /// Calls and + /// creates instance that calls on disposing. + /// + /// + /// The init and dispose action. + /// takes true if this is initAction. + /// It takes false if this is disposeAction. + /// + /// + /// Instance of that calls on disposing. + /// + [ItemNotNull, Pure] + public static async Task CreateAsync([NotNull] Func initDisposeAction) + { + Code.NotNull(initDisposeAction, nameof(initDisposeAction)); + + await initDisposeAction(true); + return AsyncDisposable.Create(initDisposeAction, false); + } + } +} +#endif diff --git a/CodeJam.Main/Disposable.cs b/CodeJam.Main/Disposable.cs index 460f7216b..30a4a3d6b 100644 --- a/CodeJam.Main/Disposable.cs +++ b/CodeJam.Main/Disposable.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; @@ -47,7 +48,22 @@ public void Dispose() { } /// [NotNull, Pure] public static IDisposable Create([NotNull] Action disposeAction, [CanBeNull] T state) => - new AnonymousDisposable(() => disposeAction?.Invoke(state)); + new AnonymousDisposable(() => disposeAction.Invoke(state)); + + /// + /// Creates instance that calls on disposing in reverse order. + /// + /// The dispose action. + /// + /// Instance of that calls on disposing in reverse order. + /// + [NotNull, Pure] + public static IDisposable CreateNested([NotNull, ItemNotNull] params IDisposable[] disposables) + { + var copy = disposables.ToArray(); + Array.Reverse(copy); + return Merge(copy); + } /// Combine multiple instances into single one. /// The disposables. diff --git a/CodeJam.Main/InitDispose.cs b/CodeJam.Main/InitDispose.cs index f3a70220e..c56f39ca2 100644 --- a/CodeJam.Main/InitDispose.cs +++ b/CodeJam.Main/InitDispose.cs @@ -20,7 +20,7 @@ public static class InitDispose [NotNull, Pure] public static IDisposable Create([NotNull] Action initAction, [NotNull] Action disposeAction) { - Code.NotNull(initAction, nameof(initAction)); + Code.NotNull(initAction, nameof(initAction)); Code.NotNull(disposeAction, nameof(disposeAction)); initAction(); @@ -40,7 +40,7 @@ public static IDisposable Create([NotNull] Action initAction, [NotNull] Action d [NotNull, Pure] public static IDisposable Create([NotNull] Func initAction, [NotNull] Action disposeAction) { - Code.NotNull(initAction, nameof(initAction)); + Code.NotNull(initAction, nameof(initAction)); Code.NotNull(disposeAction, nameof(disposeAction)); var initState = initAction(); @@ -56,6 +56,7 @@ public static IDisposable Create([NotNull] Func initAction, [NotNull] Acti /// Instance of that calls on disposing. /// [NotNull, Pure] + [Obsolete("Please use overload with Action argument")] public static IDisposable Create([NotNull] Action initDisposeAction) { Code.NotNull(initDisposeAction, nameof(initDisposeAction));