From b02bf4e26fad8bb0221fa8c041dc2173b89e418f Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Wed, 14 Aug 2024 23:56:03 +0200 Subject: [PATCH 1/4] feat: add IEnumerable extensions: CountAsync, ToListAsync --- src/Atc/Extensions/EnumerableExtensions.cs | 47 ++++++++++++ .../Extensions/EnumerableExtensionsTests.cs | 73 ++++++++++++++++++- 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/src/Atc/Extensions/EnumerableExtensions.cs b/src/Atc/Extensions/EnumerableExtensions.cs index dd764ca2..2e52c7ca 100644 --- a/src/Atc/Extensions/EnumerableExtensions.cs +++ b/src/Atc/Extensions/EnumerableExtensions.cs @@ -30,6 +30,53 @@ public static async IAsyncEnumerable ToAsyncEnumerable( } } + /// + /// Asynchronously counts the elements in a sequence. + /// + /// The type of the elements in the source sequence. + /// The source sequence to count. + /// A to observe while waiting for the asynchronous operation to complete. + /// A task that represents the asynchronous operation. The task result contains the number of elements in the sequence. + /// Thrown when the source sequence is null. + public static Task CountAsync( + this IEnumerable source, + CancellationToken cancellationToken = default) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return Task.Run(source.Count, cancellationToken); + } + + /// + /// Asynchronously creates a from an . + /// + /// The type of the elements in the source sequence. + /// The source sequence to convert to a list. + /// A to observe while waiting for the asynchronous operation to complete. + /// A task that represents the asynchronous operation. The task result contains a list with the elements from the input sequence. + /// Thrown when the source sequence is null. + public static Task> ToListAsync( + this IEnumerable source, + CancellationToken cancellationToken = default) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return Task.Run(source.ToList, cancellationToken); + } + + /// + /// Iterates asynchronously over the elements of a source sequence. + /// + /// The type of the elements in the source sequence. + /// The source sequence to iterate over. + /// A to observe while iterating over the sequence. + /// An that contains the elements from the input sequence. private static async IAsyncEnumerable IterateAsync( IEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken) diff --git a/test/Atc.Tests/Extensions/EnumerableExtensionsTests.cs b/test/Atc.Tests/Extensions/EnumerableExtensionsTests.cs index f71fd1de..59c985c0 100644 --- a/test/Atc.Tests/Extensions/EnumerableExtensionsTests.cs +++ b/test/Atc.Tests/Extensions/EnumerableExtensionsTests.cs @@ -1,10 +1,13 @@ // ReSharper disable PossibleMultipleEnumeration +// ReSharper disable AssignNullToNotNullAttribute namespace Atc.Tests.Extensions; +[SuppressMessage("AsyncUsage", "AsyncFixer01:Unnecessary async/await usage", Justification = "OK for testing")] +[SuppressMessage("AsyncUsage", "AsyncFixer02:Long-running or blocking operations inside an async method", Justification = "OK for testing")] public class EnumerableExtensionsTests { [Fact] - public async Task ToAsyncEnumerable() + public async Task ToAsyncEnumerable_ConvertsIEnumerableToIAsyncEnumerable() { // Arrange var source = Enumerable.Range(1, 5); @@ -19,4 +22,72 @@ public async Task ToAsyncEnumerable() // Assert Assert.Equal(source, result); } + + [Fact] + public async Task ToAsyncEnumerable_ThrowsArgumentNullException_ForNullSource() + { + // Arrange + IEnumerable source = null; + + // Act & Assert + await Assert.ThrowsAsync(async () => + { + await foreach (var item in source.ToAsyncEnumerable()) + { + // Should not get here + } + }); + } + + [Fact] + public async Task CountAsync_ReturnsCorrectCount() + { + // Arrange + var source = Enumerable.Range(1, 5); + + // Act + var count = await source.CountAsync(); + + // Assert + Assert.Equal(5, count); + } + + [Fact] + public async Task CountAsync_ThrowsArgumentNullException_ForNullSource() + { + // Arrange + IEnumerable source = null; + + // Act & Assert + await Assert.ThrowsAsync(async () => + { + await source.CountAsync(); + }); + } + + [Fact] + public async Task ToListAsync_ReturnsCorrectList() + { + // Arrange + var source = Enumerable.Range(1, 5); + + // Act + var result = await source.ToListAsync(); + + // Assert + Assert.Equal(source.ToList(), result); + } + + [Fact] + public async Task ToListAsync_ThrowsArgumentNullException_ForNullSource() + { + // Arrange + IEnumerable source = null; + + // Act & Assert + await Assert.ThrowsAsync(async () => + { + await source.ToListAsync(); + }); + } } \ No newline at end of file From 5481068447538dd3cf0934ab38436b97b742e976 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Wed, 14 Aug 2024 23:57:10 +0200 Subject: [PATCH 2/4] test: fix some async, since the new ToListAsync can be used --- test/Atc.Tests/Helpers/TaskHelperTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Atc.Tests/Helpers/TaskHelperTests.cs b/test/Atc.Tests/Helpers/TaskHelperTests.cs index ea171a3d..1de3d44a 100644 --- a/test/Atc.Tests/Helpers/TaskHelperTests.cs +++ b/test/Atc.Tests/Helpers/TaskHelperTests.cs @@ -47,7 +47,7 @@ public async Task WhenAll_AsIEnumerable() }; // Act - var actual = (await TaskHelper.WhenAll(tasks)).ToList(); + var actual = await (await TaskHelper.WhenAll(tasks)).ToListAsync(); // Assert actual @@ -73,7 +73,7 @@ public async Task WhenAll_AsParams() taskCompletionSource2.TrySetResult(expected); // Act - var actual = (await TaskHelper.WhenAll(taskCompletionSource1.Task, taskCompletionSource2.Task)).ToList(); + var actual = await (await TaskHelper.WhenAll(taskCompletionSource1.Task, taskCompletionSource2.Task)).ToListAsync(); // Assert actual From 1e717a97cb1f0ddcd86e9b0461c65172952db1c1 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Wed, 14 Aug 2024 23:57:32 +0200 Subject: [PATCH 3/4] feat: add AsyncEnumerableFactory.Empty --- src/Atc/Factories/AsyncEnumerableFactory.cs | 18 ++++ test/Atc.Tests/CodeComplianceTests.cs | 1 + .../Factories/AsyncEnumerableFactoryTests.cs | 89 +++++++++++++++++++ test/Atc.Tests/GlobalUsings.cs | 1 + 4 files changed, 109 insertions(+) create mode 100644 src/Atc/Factories/AsyncEnumerableFactory.cs create mode 100644 test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs diff --git a/src/Atc/Factories/AsyncEnumerableFactory.cs b/src/Atc/Factories/AsyncEnumerableFactory.cs new file mode 100644 index 00000000..04026a1c --- /dev/null +++ b/src/Atc/Factories/AsyncEnumerableFactory.cs @@ -0,0 +1,18 @@ +namespace Atc.Factories; + +/// +/// Provides factory methods for creating instances of . +/// +public static class AsyncEnumerableFactory +{ + /// + /// Returns an empty . + /// + /// The type of the elements in the sequence. + /// An empty . + public static async IAsyncEnumerable Empty() + { + await Task.CompletedTask; + yield break; + } +} \ No newline at end of file diff --git a/test/Atc.Tests/CodeComplianceTests.cs b/test/Atc.Tests/CodeComplianceTests.cs index 3c0351b3..21a5f844 100644 --- a/test/Atc.Tests/CodeComplianceTests.cs +++ b/test/Atc.Tests/CodeComplianceTests.cs @@ -40,6 +40,7 @@ public class CodeComplianceTests typeof(StackTraceHelper), // UnitTests are made, but CodeCompliance test cannot detect this + typeof(AsyncEnumerableFactory), typeof(EnumerableExtensions), typeof(EnumAtcExtensions), typeof(DynamicJson), diff --git a/test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs b/test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs new file mode 100644 index 00000000..6897abda --- /dev/null +++ b/test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs @@ -0,0 +1,89 @@ +// ReSharper disable PossibleMultipleEnumeration +namespace Atc.Tests.Factories; + +public class AsyncEnumerableFactoryTests +{ + [Fact] + public async Task Empty_ReturnsEmptySequence() + { + // Arrange + var result = new List(); + + // Act + await foreach (var item in AsyncEnumerableFactory.Empty()) + { + result.Add(item); + } + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task Empty_ReturnsEmptySequence_ForReferenceType() + { + // Arrange + var result = new List(); + + // Act + await foreach (var item in AsyncEnumerableFactory.Empty()) + { + result.Add(item); + } + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task Empty_ReturnsEmptySequence_WhenEnumeratedMultipleTimes() + { + // Arrange & Act + var emptyEnumerable = AsyncEnumerableFactory.Empty(); + var firstResult = new List(); + var secondResult = new List(); + + await foreach (var item in emptyEnumerable) + { + firstResult.Add(item); + } + + await foreach (var item in emptyEnumerable) + { + secondResult.Add(item); + } + + // Assert + Assert.Empty(firstResult); + Assert.Empty(secondResult); + } + + [Fact] + public async Task Empty_ReturnsEmptySequence_WithDifferentTypes() + { + // Arrange + var intResult = new List(); + + // Act & Assert + await foreach (var item in AsyncEnumerableFactory.Empty()) + { + intResult.Add(item); + } + + var stringResult = new List(); + await foreach (var item in AsyncEnumerableFactory.Empty()) + { + stringResult.Add(item); + } + + var customTypeResult = new List(); + await foreach (var item in AsyncEnumerableFactory.Empty()) + { + customTypeResult.Add(item); + } + + Assert.Empty(intResult); + Assert.Empty(stringResult); + Assert.Empty(customTypeResult); + } +} \ No newline at end of file diff --git a/test/Atc.Tests/GlobalUsings.cs b/test/Atc.Tests/GlobalUsings.cs index 143ca43e..0fcf5445 100644 --- a/test/Atc.Tests/GlobalUsings.cs +++ b/test/Atc.Tests/GlobalUsings.cs @@ -19,6 +19,7 @@ global using Atc.Data; global using Atc.Data.Models; global using Atc.Data.SemVer; +global using Atc.Factories; global using Atc.Helpers; global using Atc.Math; global using Atc.Math.Geometry; From 62188695ce904191d1d26fcf5cbf11ee5e7290ba Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Wed, 14 Aug 2024 23:57:45 +0200 Subject: [PATCH 4/4] docs: update --- docs/CodeDoc/Atc/Atc.Factories.md | 26 +++++++++++++++++++ docs/CodeDoc/Atc/Index.md | 4 +++ docs/CodeDoc/Atc/IndexExtended.md | 8 ++++++ .../CodeDoc/Atc/System.Collections.Generic.md | 22 ++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 docs/CodeDoc/Atc/Atc.Factories.md diff --git a/docs/CodeDoc/Atc/Atc.Factories.md b/docs/CodeDoc/Atc/Atc.Factories.md new file mode 100644 index 00000000..c639e18c --- /dev/null +++ b/docs/CodeDoc/Atc/Atc.Factories.md @@ -0,0 +1,26 @@ +
+ +[References](Index.md)  -  [References extended](IndexExtended.md) +
+ +# Atc.Factories + +
+ +## AsyncEnumerableFactory +Provides factory methods for creating instances of `System.Collections.Generic.IAsyncEnumerable`1`. + +>```csharp +>public static class AsyncEnumerableFactory +>``` + +### Static Methods + +#### EmptyAsyncEnumerable +>```csharp +>IAsyncEnumerable EmptyAsyncEnumerable() +>``` +>Summary: Returns an empty `System.Collections.Generic.IAsyncEnumerable`1`. +> +>Returns: An empty `System.Collections.Generic.IAsyncEnumerable`1`. +
Generated by MarkdownCodeDoc version 1.2
diff --git a/docs/CodeDoc/Atc/Index.md b/docs/CodeDoc/Atc/Index.md index 880c19ac..af59e64f 100644 --- a/docs/CodeDoc/Atc/Index.md +++ b/docs/CodeDoc/Atc/Index.md @@ -77,6 +77,10 @@ - [SemanticVersion](Atc.Data.SemVer.md#semanticversion) +## [Atc.Factories](Atc.Factories.md) + +- [AsyncEnumerableFactory](Atc.Factories.md#asyncenumerablefactory) + ## [Atc.Helpers](Atc.Helpers.md) - [ArticleNumberHelper](Atc.Helpers.md#articlenumberhelper) diff --git a/docs/CodeDoc/Atc/IndexExtended.md b/docs/CodeDoc/Atc/IndexExtended.md index 6f9151b7..25ac379a 100644 --- a/docs/CodeDoc/Atc/IndexExtended.md +++ b/docs/CodeDoc/Atc/IndexExtended.md @@ -4391,6 +4391,12 @@ - ToString() - ToVersion() +## [Atc.Factories](Atc.Factories.md) + +- [AsyncEnumerableFactory](Atc.Factories.md#asyncenumerablefactory) + - Static Methods + - EmptyAsyncEnumerable() + ## [Atc.Helpers](Atc.Helpers.md) - [ArticleNumberHelper](Atc.Helpers.md#articlenumberhelper) @@ -5294,7 +5300,9 @@ - [EnumerableExtensions](System.Collections.Generic.md#enumerableextensions) - Static Methods + - CountAsync(this IEnumerable<T> source, CancellationToken cancellationToken = null) - ToAsyncEnumerable(this IEnumerable<T> source, CancellationToken cancellationToken = null) + - ToListAsync(this IEnumerable<T> source, CancellationToken cancellationToken = null) - [ReadOnlyListExtensions](System.Collections.Generic.md#readonlylistextensions) - Static Methods - GetPowerSet(this IReadOnlyList<T> list) diff --git a/docs/CodeDoc/Atc/System.Collections.Generic.md b/docs/CodeDoc/Atc/System.Collections.Generic.md index 7f822188..91669efe 100644 --- a/docs/CodeDoc/Atc/System.Collections.Generic.md +++ b/docs/CodeDoc/Atc/System.Collections.Generic.md @@ -16,6 +16,17 @@ Provides extension methods for asynchronous enumeration of collections. ### Static Methods +#### CountAsync +>```csharp +>Task CountAsync(this IEnumerable source, CancellationToken cancellationToken = null) +>``` +>Summary: Asynchronously counts the elements in a sequence. +> +>Parameters:
+>     `source`  -  The source sequence to count.
+>     `cancellationToken`  -  A to observe while waiting for the asynchronous operation to complete.
+> +>Returns: A task that represents the asynchronous operation. The task result contains the number of elements in the sequence. #### ToAsyncEnumerable >```csharp >IAsyncEnumerable ToAsyncEnumerable(this IEnumerable source, CancellationToken cancellationToken = null) @@ -27,6 +38,17 @@ Provides extension methods for asynchronous enumeration of collections. >     `cancellationToken`  -  A to observe while waiting for the asynchronous operation to complete.
> >Returns: An `System.Collections.Generic.IAsyncEnumerable`1` that contains the elements from the input sequence. +#### ToListAsync +>```csharp +>Task> ToListAsync(this IEnumerable source, CancellationToken cancellationToken = null) +>``` +>Summary: Asynchronously creates a `System.Collections.Generic.List`1` from an `System.Collections.Generic.IEnumerable`1`. +> +>Parameters:
+>     `source`  -  The source sequence to convert to a list.
+>     `cancellationToken`  -  A to observe while waiting for the asynchronous operation to complete.
+> +>Returns: A task that represents the asynchronous operation. The task result contains a list with the elements from the input sequence.