feat: add FirstOrEmptyAsync

This commit is contained in:
Kyle Ratti 2024-09-05 22:53:48 -04:00
parent e8e0ea8f60
commit 4dbba5c2f3
No known key found for this signature in database
GPG Key ID: 4D429B6287C68DD9
2 changed files with 149 additions and 1 deletions

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Threading.Tasks;
using FruityFoundation.Base.Structures; using FruityFoundation.Base.Structures;
using NUnit.Framework; using NUnit.Framework;
@ -51,6 +52,86 @@ public class MaybeExtensionTests
Assert.That(result.HasValue, Is.False); Assert.That(result.HasValue, Is.False);
} }
[Test]
public async Task Enumerable_FirstOrEmptyAsync_WithEmptyEnumerable_ReturnsEmptyMaybe()
{
// Arrange
var data = ToAsyncEnumerable(Array.Empty<int>());
// Act
var result = await data.FirstOrEmptyAsync();
// Assert
Assert.That(result, Is.InstanceOf<Maybe<int>>());
Assert.That(result.HasValue, Is.False);
}
[Test]
public async Task Enumerable_FirstOrEmptyAsync_WithMatchingPredicate_ReturnsMaybeWithValue()
{
// Arrange
var data = ToAsyncEnumerable([1, 2, 3, 4]);
// Act
var result = await data.FirstOrEmptyAsync(x => x > 1);
// Assert
Assert.That(result, Is.InstanceOf<Maybe<int>>());
Assert.That(result.HasValue, Is.True);
Assert.That(result.Value, Is.EqualTo(2));
}
[Test]
public async Task Enumerable_FirstOrEmptyAsync_WithMatchingAsyncPredicate_ReturnsMaybeWithValue()
{
// Arrange
var data = ToAsyncEnumerable([1, 2, 3, 4]);
// Act
var result = await data.FirstOrEmptyAsync(async x =>
{
await Task.Yield();
return x > 1;
});
// Assert
Assert.That(result, Is.InstanceOf<Maybe<int>>());
Assert.That(result.HasValue, Is.True);
Assert.That(result.Value, Is.EqualTo(2));
}
[Test]
public async Task Enumerable_FirstOrEmptyAsync_WithNonMatchingPredicate_ReturnsEmptyMaybe()
{
// Arrange
var data = ToAsyncEnumerable([1, 2, 3, 4]);
// Act
var result = await data.FirstOrEmptyAsync(x => x > 100);
// Assert
Assert.That(result, Is.InstanceOf<Maybe<int>>());
Assert.That(result.HasValue, Is.False);
}
[Test]
public async Task Enumerable_FirstOrEmptyAsync_WithNonMatchingAsyncPredicate_ReturnsEmptyMaybe()
{
// Arrange
var data = ToAsyncEnumerable([1, 2, 3, 4]);
// Act
var result = await data.FirstOrEmptyAsync(async x =>
{
await Task.Yield();
return x > 100;
});
// Assert
Assert.That(result, Is.InstanceOf<Maybe<int>>());
Assert.That(result.HasValue, Is.False);
}
[Test] [Test]
public void MaybeNullableTests() public void MaybeNullableTests()
{ {
@ -125,4 +206,12 @@ public class MaybeExtensionTests
Assert.That(result, Is.InstanceOf<Maybe<int>>()); Assert.That(result, Is.InstanceOf<Maybe<int>>());
Assert.That(result.HasValue, Is.False); Assert.That(result.HasValue, Is.False);
} }
private static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(IEnumerable<T> enumerable)
{
foreach (var item in enumerable)
yield return item;
await Task.Yield();
}
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Threading;
using System.Threading.Tasks;
namespace FruityFoundation.Base.Structures; namespace FruityFoundation.Base.Structures;
@ -22,6 +23,64 @@ public static class MaybeExtensions
return Maybe.Empty<T>(); return Maybe.Empty<T>();
} }
public static async ValueTask<Maybe<T>> FirstOrEmptyAsync<T>(this IAsyncEnumerable<T> source, CancellationToken cancellationToken = default)
{
if (source is IList<T> { Count: > 0 } list)
return Maybe.Create(list[0]);
await using var e = source
.ConfigureAwait(false)
.WithCancellation(cancellationToken)
.GetAsyncEnumerator();
if (await e.MoveNextAsync())
return e.Current;
return Maybe.Empty<T>();
}
public static async ValueTask<Maybe<T>> FirstOrEmptyAsync<T>(this IAsyncEnumerable<T> source, Func<T, bool> predicate, CancellationToken cancellationToken = default)
{
if (source is IList<T> { Count: > 0 } list)
return list[0];
await using var e = source
.ConfigureAwait(false)
.WithCancellation(cancellationToken)
.GetAsyncEnumerator();
while (await e.MoveNextAsync())
{
var value = e.Current;
if (predicate(value))
return value;
}
return Maybe.Empty<T>();
}
public static async ValueTask<Maybe<T>> FirstOrEmptyAsync<T>(this IAsyncEnumerable<T> source, Func<T, ValueTask<bool>> asyncPredicate, CancellationToken cancellationToken = default)
{
if (source is IList<T> { Count: > 0 } list)
return list[0];
await using var e = source
.ConfigureAwait(false)
.WithCancellation(cancellationToken)
.GetAsyncEnumerator();
while (await e.MoveNextAsync())
{
var value = e.Current;
if (await asyncPredicate(value).ConfigureAwait(false))
return value;
}
return Maybe.Empty<T>();
}
public static Maybe<TValue> TryGetValue<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) => public static Maybe<TValue> TryGetValue<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) =>
dict.TryGetValue(key, out var value) ? Maybe.Create(value) : Maybe.Empty<TValue>(); dict.TryGetValue(key, out var value) ? Maybe.Create(value) : Maybe.Empty<TValue>();