Add DisposeAll(Async) and ClearAndDisposeAll(Async)

This commit is contained in:
Oliver Booth 2022-05-02 22:31:09 +01:00
parent 7b8c344ddd
commit adf2281f21
No known key found for this signature in database
GPG Key ID: 32A00B35503AF634
4 changed files with 253 additions and 1 deletions

View File

@ -0,0 +1,76 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections;
namespace X10D.Tests.Collections;
[TestClass]
public class CollectionTests
{
[TestMethod]
public void ClearAndDisposeAll_ShouldDispose_GivenCollection()
{
var collection = new List<Disposable> {new(), new(), new()};
var copy = new List<Disposable>(collection);
collection.ClearAndDisposeAll();
Assert.IsTrue(copy.All(x => x.IsDisposed));
Assert.AreEqual(0, collection.Count);
}
[TestMethod]
public async Task ClearAndDisposeAllAsync_ShouldDispose_GivenCollection()
{
var collection = new List<Disposable> {new(), new(), new()};
var copy = new List<Disposable>(collection);
await collection.ClearAndDisposeAllAsync();
Assert.IsTrue(copy.All(x => x.IsDisposed));
Assert.AreEqual(0, collection.Count);
}
[TestMethod]
public void ClearAndDisposeAll_ShouldThrow_GivenNull()
{
List<Disposable>? collection = null;
Assert.ThrowsException<ArgumentNullException>(() => collection!.ClearAndDisposeAll());
}
[TestMethod]
public void ClearAndDisposeAllAsync_ShouldThrow_GivenNull()
{
List<Disposable>? collection = null;
Assert.ThrowsExceptionAsync<ArgumentNullException>(async () => await collection!.ClearAndDisposeAllAsync());
}
[TestMethod]
public void ClearAndDisposeAll_ShouldThrow_GivenReadOnlyCollection()
{
var collection = new List<Disposable>().AsReadOnly();
Assert.ThrowsException<InvalidOperationException>(() => collection.ClearAndDisposeAll());
}
[TestMethod]
public void ClearAndDisposeAllAsync_ShouldThrow_GivenReadOnlyCollection()
{
var collection = new List<Disposable>().AsReadOnly();
Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => await collection.ClearAndDisposeAllAsync());
}
private class Disposable : IDisposable, IAsyncDisposable
{
public bool IsDisposed { get; private set; }
public void Dispose()
{
Assert.IsTrue(IsDisposed = true);
}
public ValueTask DisposeAsync()
{
Assert.IsTrue(IsDisposed = true);
return ValueTask.CompletedTask;
}
}
}

View File

@ -1,4 +1,4 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections;
namespace X10D.Tests.Collections;
@ -6,6 +6,36 @@ namespace X10D.Tests.Collections;
[TestClass]
public class EnumerableTests
{
[TestMethod]
public void DisposeAll_ShouldDispose_GivenCollection()
{
var collection = new List<Disposable> {new(), new(), new()};
collection.DisposeAll();
Assert.IsTrue(collection.All(x => x.IsDisposed));
}
[TestMethod]
public async Task DisposeAllAsync_ShouldDispose_GivenCollection()
{
var collection = new List<Disposable> {new(), new(), new()};
await collection.DisposeAllAsync();
Assert.IsTrue(collection.All(x => x.IsDisposed));
}
[TestMethod]
public void DisposeAll_ShouldThrow_GivenNull()
{
List<Disposable>? collection = null;
Assert.ThrowsException<ArgumentNullException>(() => collection!.DisposeAll());
}
[TestMethod]
public async Task DisposeAllAsync_ShouldThrow_GivenNull()
{
List<Disposable>? collection = null;
await Assert.ThrowsExceptionAsync<ArgumentNullException>(async () => await collection!.DisposeAllAsync());
}
[TestMethod]
public void For_ShouldTransform_GivenTransformationDelegate()
{
@ -86,4 +116,20 @@ public class EnumerableTests
{
public int Value { get; set; }
}
private class Disposable : IDisposable, IAsyncDisposable
{
public bool IsDisposed { get; private set; }
public void Dispose()
{
Assert.IsTrue(IsDisposed = true);
}
public ValueTask DisposeAsync()
{
Assert.IsTrue(IsDisposed = true);
return ValueTask.CompletedTask;
}
}
}

View File

@ -0,0 +1,77 @@
namespace X10D.Collections;
/// <summary>
/// Collection-related extension methods for <see cref="ICollection{T}" />.
/// </summary>
public static class CollectionExtensions
{
/// <summary>
/// Calls <see cref="IDisposable.Dispose" /> on each item in the collection, then clears the collection by calling
/// <see cref="ICollection{T}.Clear" />.
/// </summary>
/// <param name="source">The collection to clear, and whose elements should be disposed.</param>
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
/// <exception cref="InvalidOperationException"><paramref name="source" /> is read-only.</exception>
/// <seealso cref="EnumerableExtensions.DisposeAll{T}" />
public static void ClearAndDisposeAll<T>(this ICollection<T> source) where T : IDisposable
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (source.IsReadOnly)
{
throw new InvalidOperationException("Collection is read-only. Try using DisposeAll instead.");
}
foreach (T item in source)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (item is null)
{
continue;
}
item.Dispose();
}
source.Clear();
}
/// <summary>
/// Asynchronously calls <see cref="IAsyncDisposable.DisposeAsync" /> on each item in the collection, then clears the
/// collection by calling <see cref="ICollection{T}.Clear" />.
/// </summary>
/// <param name="source">The collection to clear, and whose elements should be disposed.</param>
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
/// <exception cref="InvalidOperationException"><paramref name="source" /> is read-only.</exception>
/// <seealso cref="EnumerableExtensions.DisposeAllAsync{T}" />
public static async Task ClearAndDisposeAllAsync<T>(this ICollection<T> source) where T : IAsyncDisposable
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (source.IsReadOnly)
{
throw new InvalidOperationException("Collection is read-only. Try using DisposeAllAsync instead.");
}
foreach (T item in source)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (item is null)
{
continue;
}
await item.DisposeAsync();
}
source.Clear();
}
}

View File

@ -75,6 +75,59 @@ public static class EnumerableExtensions
}
}
/// <summary>
/// Calls <see cref="IDisposable.Dispose" /> on all elements of the <see cref="IEnumerable{T}" />.
/// </summary>
/// <param name="source">The enumerable collection whose elements to dispose.</param>
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
/// <seealso cref="CollectionExtensions.ClearAndDisposeAll{T}" />
public static void DisposeAll<T>(this IEnumerable<T> source) where T : IDisposable
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
foreach (T item in source)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (item is null)
{
continue;
}
item.Dispose();
}
}
/// <summary>
/// Asynchronously calls <see cref="IAsyncDisposable.DisposeAsync" /> on all elements of the
/// <see cref="IEnumerable{T}" />.
/// </summary>
/// <param name="source">The enumerable collection whose elements to dispose.</param>
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
/// <seealso cref="CollectionExtensions.ClearAndDisposeAllAsync{T}" />
public static async Task DisposeAllAsync<T>(this IEnumerable<T> source) where T : IAsyncDisposable
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
foreach (T item in source)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (item is null)
{
continue;
}
await item.DisposeAsync();
}
}
/// <summary>
/// Reorganizes the elements in an enumerable by implementing a Fisher-Yates shuffle, and returns th shuffled result.
/// </summary>