From 0cb9b62a1379c0ae6a785c8f2e7e0d5abc3285fd Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Tue, 4 Apr 2023 13:31:10 -0500 Subject: [PATCH] Add `While` operator --- Source/SuperLinq.Async/While.cs | 68 +++++++++++++++++++++++++ Tests/SuperLinq.Async.Test/WhileTest.cs | 35 +++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 Source/SuperLinq.Async/While.cs create mode 100644 Tests/SuperLinq.Async.Test/WhileTest.cs diff --git a/Source/SuperLinq.Async/While.cs b/Source/SuperLinq.Async/While.cs new file mode 100644 index 00000000..b9f95deb --- /dev/null +++ b/Source/SuperLinq.Async/While.cs @@ -0,0 +1,68 @@ +namespace SuperLinq.Async; + +public static partial class AsyncSuperEnumerable +{ + /// + /// Generates an enumerable sequence by repeating a source sequence as long as the given loop condition holds. + /// + /// Source sequence element type. + /// Loop condition. + /// Sequence to repeat while the condition evaluates true. + /// Sequence generated by repeating the given sequence while the condition evaluates to true. + /// or is . + /// + /// + /// is evaluated lazily, once at the start of each loop of . + /// + /// + /// is cached via , so that it + /// is only iterated once during the first loop. Successive loops will enumerate the cache instead of . + /// + /// + public static IAsyncEnumerable While(Func condition, IAsyncEnumerable source) + { + return While(condition.ToAsync(), source); + } + + /// + /// Generates an enumerable sequence by repeating a source sequence as long as the given loop condition holds. + /// + /// Source sequence element type. + /// Loop condition. + /// Sequence to repeat while the condition evaluates true. + /// Sequence generated by repeating the given sequence while the condition evaluates to true. + /// or is . + /// + /// + /// is evaluated lazily, once at the start of each loop of . + /// + /// + /// is cached via , so that it + /// is only iterated once during the first loop. Successive loops will enumerate the cache instead of . + /// + /// + public static IAsyncEnumerable While(Func> condition, IAsyncEnumerable source) + { + Guard.IsNotNull(condition); + Guard.IsNotNull(source); + + return Core(condition, source); + + static async IAsyncEnumerable Core( + Func> condition, + IAsyncEnumerable source, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await using var memo = source.Memoize(); + while (await condition().ConfigureAwait(false)) + { + await foreach (var item in memo.WithCancellation(cancellationToken).ConfigureAwait(false)) + yield return item; + } + } + } +} diff --git a/Tests/SuperLinq.Async.Test/WhileTest.cs b/Tests/SuperLinq.Async.Test/WhileTest.cs new file mode 100644 index 00000000..b1527c7f --- /dev/null +++ b/Tests/SuperLinq.Async.Test/WhileTest.cs @@ -0,0 +1,35 @@ +namespace Test.Async; + +public class WhileTest +{ + [Fact] + public void WhileIsLazy() + { + _ = AsyncSuperEnumerable.While( + BreakingFunc.Of(), + new AsyncBreakingSequence()); + _ = AsyncSuperEnumerable.While( + BreakingFunc.Of>(), + new AsyncBreakingSequence()); + } + + [Fact] + public async Task WhileBehavior() + { + var starts = 0; + var seq = AsyncSuperEnumerable.While( + () => + starts++ switch + { + 0 or 1 => true, + 2 => false, + _ => throw new TestException(), + }, + AsyncEnumerable.Range(1, 10)); + + Assert.Equal(0, starts); + await seq.AssertSequenceEqual( + Enumerable.Range(1, 10).Concat(Enumerable.Range(1, 10))); + Assert.Equal(3, starts); + } +}