Skip to content

Commit

Permalink
Merge pull request #328 from atc-net/feature/IEnumerable-extensions
Browse files Browse the repository at this point in the history
Feature/i enumerable extensions
  • Loading branch information
davidkallesen authored Aug 14, 2024
2 parents cd333af + 6218869 commit 1303010
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 3 deletions.
26 changes: 26 additions & 0 deletions docs/CodeDoc/Atc/Atc.Factories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div style='text-align: right'>

[References](Index.md)&nbsp;&nbsp;-&nbsp;&nbsp;[References extended](IndexExtended.md)
</div>

# Atc.Factories

<br />

## AsyncEnumerableFactory
Provides factory methods for creating instances of `System.Collections.Generic.IAsyncEnumerable`1`.

>```csharp
>public static class AsyncEnumerableFactory
>```
### Static Methods
#### EmptyAsyncEnumerable
>```csharp
>IAsyncEnumerable<T> EmptyAsyncEnumerable()
>```
><b>Summary:</b> Returns an empty `System.Collections.Generic.IAsyncEnumerable`1`.
>
><b>Returns:</b> An empty `System.Collections.Generic.IAsyncEnumerable`1`.
<hr /><div style='text-align: right'><i>Generated by MarkdownCodeDoc version 1.2</i></div>
4 changes: 4 additions & 0 deletions docs/CodeDoc/Atc/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions docs/CodeDoc/Atc/IndexExtended.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -5294,7 +5300,9 @@

- [EnumerableExtensions](System.Collections.Generic.md#enumerableextensions)
- Static Methods
- CountAsync(this IEnumerable&lt;T&gt; source, CancellationToken cancellationToken = null)
- ToAsyncEnumerable(this IEnumerable&lt;T&gt; source, CancellationToken cancellationToken = null)
- ToListAsync(this IEnumerable&lt;T&gt; source, CancellationToken cancellationToken = null)
- [ReadOnlyListExtensions](System.Collections.Generic.md#readonlylistextensions)
- Static Methods
- GetPowerSet(this IReadOnlyList&lt;T&gt; list)
Expand Down
22 changes: 22 additions & 0 deletions docs/CodeDoc/Atc/System.Collections.Generic.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ Provides extension methods for asynchronous enumeration of collections.
### Static Methods
#### CountAsync
>```csharp
>Task<int> CountAsync(this IEnumerable<T> source, CancellationToken cancellationToken = null)
>```
><b>Summary:</b> Asynchronously counts the elements in a sequence.
>
><b>Parameters:</b><br>
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`source`&nbsp;&nbsp;-&nbsp;&nbsp;The source sequence to count.<br />
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`cancellationToken`&nbsp;&nbsp;-&nbsp;&nbsp;A to observe while waiting for the asynchronous operation to complete.<br />
>
><b>Returns:</b> A task that represents the asynchronous operation. The task result contains the number of elements in the sequence.
#### ToAsyncEnumerable
>```csharp
>IAsyncEnumerable<T> ToAsyncEnumerable(this IEnumerable<T> source, CancellationToken cancellationToken = null)
Expand All @@ -27,6 +38,17 @@ Provides extension methods for asynchronous enumeration of collections.
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`cancellationToken`&nbsp;&nbsp;-&nbsp;&nbsp;A to observe while waiting for the asynchronous operation to complete.<br />
>
><b>Returns:</b> An `System.Collections.Generic.IAsyncEnumerable`1` that contains the elements from the input sequence.
#### ToListAsync
>```csharp
>Task<List<T>> ToListAsync(this IEnumerable<T> source, CancellationToken cancellationToken = null)
>```
><b>Summary:</b> Asynchronously creates a `System.Collections.Generic.List`1` from an `System.Collections.Generic.IEnumerable`1`.
>
><b>Parameters:</b><br>
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`source`&nbsp;&nbsp;-&nbsp;&nbsp;The source sequence to convert to a list.<br />
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`cancellationToken`&nbsp;&nbsp;-&nbsp;&nbsp;A to observe while waiting for the asynchronous operation to complete.<br />
>
><b>Returns:</b> A task that represents the asynchronous operation. The task result contains a list with the elements from the input sequence.
<br />
Expand Down
47 changes: 47 additions & 0 deletions src/Atc/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,53 @@ public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(
}
}

/// <summary>
/// Asynchronously counts the elements in a sequence.
/// </summary>
/// <typeparam name="T">The type of the elements in the source sequence.</typeparam>
/// <param name="source">The source sequence to count.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the asynchronous operation to complete.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the number of elements in the sequence.</returns>
/// <exception cref="ArgumentNullException">Thrown when the source sequence is null.</exception>
public static Task<int> CountAsync<T>(
this IEnumerable<T> source,
CancellationToken cancellationToken = default)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}

return Task.Run(source.Count, cancellationToken);
}

/// <summary>
/// Asynchronously creates a <see cref="List{T}"/> from an <see cref="IEnumerable{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the elements in the source sequence.</typeparam>
/// <param name="source">The source sequence to convert to a list.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the asynchronous operation to complete.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a list with the elements from the input sequence.</returns>
/// <exception cref="ArgumentNullException">Thrown when the source sequence is null.</exception>
public static Task<List<T>> ToListAsync<T>(
this IEnumerable<T> source,
CancellationToken cancellationToken = default)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}

return Task.Run(source.ToList, cancellationToken);
}

/// <summary>
/// Iterates asynchronously over the elements of a source sequence.
/// </summary>
/// <typeparam name="T">The type of the elements in the source sequence.</typeparam>
/// <param name="source">The source sequence to iterate over.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while iterating over the sequence.</param>
/// <returns>An <see cref="IAsyncEnumerable{T}"/> that contains the elements from the input sequence.</returns>
private static async IAsyncEnumerable<T> IterateAsync<T>(
IEnumerable<T> source,
[EnumeratorCancellation] CancellationToken cancellationToken)
Expand Down
18 changes: 18 additions & 0 deletions src/Atc/Factories/AsyncEnumerableFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Atc.Factories;

/// <summary>
/// Provides factory methods for creating instances of <see cref="IAsyncEnumerable{T}"/>.
/// </summary>
public static class AsyncEnumerableFactory
{
/// <summary>
/// Returns an empty <see cref="IAsyncEnumerable{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
/// <returns>An empty <see cref="IAsyncEnumerable{T}"/>.</returns>
public static async IAsyncEnumerable<T> Empty<T>()
{
await Task.CompletedTask;
yield break;
}
}
1 change: 1 addition & 0 deletions test/Atc.Tests/CodeComplianceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
73 changes: 72 additions & 1 deletion test/Atc.Tests/Extensions/EnumerableExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -19,4 +22,72 @@ public async Task ToAsyncEnumerable()
// Assert
Assert.Equal(source, result);
}

[Fact]
public async Task ToAsyncEnumerable_ThrowsArgumentNullException_ForNullSource()
{
// Arrange
IEnumerable<int> source = null;

// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(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<int> source = null;

// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(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<int> source = null;

// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
{
await source.ToListAsync();
});
}
}
89 changes: 89 additions & 0 deletions test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -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<int>();

// Act
await foreach (var item in AsyncEnumerableFactory.Empty<int>())
{
result.Add(item);
}

// Assert
Assert.Empty(result);
}

[Fact]
public async Task Empty_ReturnsEmptySequence_ForReferenceType()
{
// Arrange
var result = new List<string>();

// Act
await foreach (var item in AsyncEnumerableFactory.Empty<string>())
{
result.Add(item);
}

// Assert
Assert.Empty(result);
}

[Fact]
public async Task Empty_ReturnsEmptySequence_WhenEnumeratedMultipleTimes()
{
// Arrange & Act
var emptyEnumerable = AsyncEnumerableFactory.Empty<int>();
var firstResult = new List<int>();
var secondResult = new List<int>();

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<int>();

// Act & Assert
await foreach (var item in AsyncEnumerableFactory.Empty<int>())
{
intResult.Add(item);
}

var stringResult = new List<string>();
await foreach (var item in AsyncEnumerableFactory.Empty<string>())
{
stringResult.Add(item);
}

var customTypeResult = new List<LogKeyValueItem>();
await foreach (var item in AsyncEnumerableFactory.Empty<LogKeyValueItem>())
{
customTypeResult.Add(item);
}

Assert.Empty(intResult);
Assert.Empty(stringResult);
Assert.Empty(customTypeResult);
}
}
1 change: 1 addition & 0 deletions test/Atc.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions test/Atc.Tests/Helpers/TaskHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 1303010

Please sign in to comment.