Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ namespace System.Linq
{
public static partial class AsyncEnumerable
{
/// <summary>Generates a sequence that begins with <paramref name="start"/> and yields additional values each incremented by <paramref name="step"/> until <paramref name="endInclusive"/> is reached.</summary>
/// <summary>Generates a sequence that begins with <paramref name="start"/> and yields additional values each incremented by <paramref name="step"/> until <paramref name="endInclusive"/> is reached.</summary>
/// <typeparam name="T">The type of the value to be yielded in the result sequence.</typeparam>
/// <param name="start">The starting value. This value will always be included in the resulting sequence.</param>
/// <param name="endInclusive">The ending bound beyond which values will not be included in the sequence.</param>
/// <param name="step">The amount by which the next value in the sequence should be incremented from the previous value.</param>
/// <returns>An <see cref="IAsyncEnumerable{T}"/> that contains the sequence.</returns>
/// <exception cref="ArgumentNullException"><paramref name="start"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="step"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="endInclusive"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="step"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="start"/> is NaN.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="endInclusive"/> is NaN.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="step"/> is NaN.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="step"/> is greater than zero but <paramref name="endInclusive"/> is less than <paramref name="start"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="step"/> is less than zero but <paramref name="endInclusive"/> is greater than <paramref name="start"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="step"/> is zero and <paramref name="endInclusive"/> does not equal <paramref name="start"/>.</exception>
Expand All @@ -30,17 +33,43 @@ public static IAsyncEnumerable<T> Sequence<T>(T start, T endInclusive, T step) w
ThrowHelper.ThrowArgumentNullException(nameof(start));
}

if (T.IsNaN(start))
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(start));
}

if (endInclusive is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(endInclusive));
}

if (T.IsNaN(endInclusive))
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(endInclusive));
}

if (step is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(step));
}

if (step > T.Zero)
if (T.IsNaN(step))
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(step));
}

if (T.IsZero(step))
{
// step == 0. If start != endInclusive, then the sequence would be infinite. As such, we validate
// that they're equal, and if they are, we return a sequence that yields the start/endInclusive value once.
if (start != endInclusive)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(step));
}

return Repeat(start, 1);
}
else if (T.IsPositive(step))
{
// Presumed to be the most common case, step > 0. Validate that endInclusive >= start, as otherwise we can't easily
// guarantee that the sequence will terminate.
Expand All @@ -52,7 +81,7 @@ public static IAsyncEnumerable<T> Sequence<T>(T start, T endInclusive, T step) w
// Otherwise, just produce an incrementing sequence.
return IncrementingIterator(start, endInclusive, step);
}
else if (step < T.Zero)
else
{
// step < 0. Validate that endInclusive <= start, as otherwise we can't easily guarantee that the sequence will terminate.
if (endInclusive > start)
Expand All @@ -63,31 +92,20 @@ public static IAsyncEnumerable<T> Sequence<T>(T start, T endInclusive, T step) w
// Then produce the decrementing sequence.
return DecrementingIterator(start, endInclusive, step);
}
else
{
// step == 0. If start != endInclusive, then the sequence would be infinite. As such, we validate
// that they're equal, and if they are, we return a sequence that yields the start/endInclusive value once.
if (start != endInclusive)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(step));
}

return Repeat(start, 1);
}

static async IAsyncEnumerable<T> IncrementingIterator(T start, T endInclusive, T step)
static async IAsyncEnumerable<T> IncrementingIterator(T current, T endInclusive, T step)
{
Debug.Assert(step > T.Zero);

yield return start;
yield return current;

while (true)
{
T next = start + step;
T next = current + step;

if (next >= endInclusive || next <= start)
if (next >= endInclusive || next <= current) // handle overflow and saturation
{
if (next == endInclusive && start != next)
if (next == endInclusive && current != next)
{
yield return next;
}
Expand All @@ -96,24 +114,24 @@ static async IAsyncEnumerable<T> IncrementingIterator(T start, T endInclusive, T
}

yield return next;
start = next;
current = next;
}
}


static async IAsyncEnumerable<T> DecrementingIterator(T start, T endInclusive, T step)
static async IAsyncEnumerable<T> DecrementingIterator(T current, T endInclusive, T step)
{
Debug.Assert(step < T.Zero);

yield return start;
yield return current;

while (true)
{
T next = start + step;
T next = current + step;

if (next <= endInclusive || next >= start)
if (next <= endInclusive || next >= current) // handle overflow and saturation
{
if (next == endInclusive && start != next)
if (next == endInclusive && current != next)
{
yield return next;
}
Expand All @@ -122,7 +140,7 @@ static async IAsyncEnumerable<T> DecrementingIterator(T start, T endInclusive, T
}

yield return next;
start = next;
current = next;
}
}
}
Expand Down
14 changes: 9 additions & 5 deletions src/libraries/System.Linq.AsyncEnumerable/tests/SequenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ namespace System.Linq.Tests
public class SequenceTests : AsyncEnumerableTests
{
[Fact]
public void NullArguments_Throws()
public void InvalidArguments_Throws()
{
AssertExtensions.Throws<ArgumentNullException>("start", () => AsyncEnumerable.Sequence((ReferenceAddable)null!, new(1), new(2)));
AssertExtensions.Throws<ArgumentNullException>("endInclusive", () => AsyncEnumerable.Sequence(new(1), (ReferenceAddable)null!, new(2)));
AssertExtensions.Throws<ArgumentNullException>("step", () => AsyncEnumerable.Sequence(new(1), new(2), (ReferenceAddable)null!));

AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => AsyncEnumerable.Sequence(float.NaN, 1.0f, 1.0f));
AssertExtensions.Throws<ArgumentOutOfRangeException>("endInclusive", () => AsyncEnumerable.Sequence(1.0f, float.NaN, 1.0f));
AssertExtensions.Throws<ArgumentOutOfRangeException>("step", () => AsyncEnumerable.Sequence(1.0f, 1.0f, float.NaN));
}

[Fact]
Expand Down Expand Up @@ -251,16 +255,16 @@ private sealed class ReferenceAddable(int value) : INumber<ReferenceAddable>
public static bool IsImaginaryNumber(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsInfinity(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsInteger(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsNaN(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsNegative(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsNaN(ReferenceAddable value) => false;
public static bool IsNegative(ReferenceAddable value) => false;
public static bool IsNegativeInfinity(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsNormal(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsOddInteger(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsPositive(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsPositive(ReferenceAddable value) => false;
public static bool IsPositiveInfinity(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsRealNumber(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsSubnormal(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsZero(ReferenceAddable value) => throw new NotImplementedException();
public static bool IsZero(ReferenceAddable value) => false;
public static ReferenceAddable MaxMagnitude(ReferenceAddable x, ReferenceAddable y) => throw new NotImplementedException();
public static ReferenceAddable MaxMagnitudeNumber(ReferenceAddable x, ReferenceAddable y) => throw new NotImplementedException();
public static ReferenceAddable MinMagnitude(ReferenceAddable x, ReferenceAddable y) => throw new NotImplementedException();
Expand Down
72 changes: 45 additions & 27 deletions src/libraries/System.Linq/src/System/Linq/Sequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ namespace System.Linq
{
public static partial class Enumerable
{
/// <summary>Generates a sequence that begins with <paramref name="start"/> and yields additional values each incremented by <paramref name="step"/> until <paramref name="endInclusive"/> is reached.</summary>
/// <summary>Generates a sequence that begins with <paramref name="start"/> and yields additional values each incremented by <paramref name="step"/> until <paramref name="endInclusive"/> is reached.</summary>
/// <typeparam name="T">The type of the value to be yielded in the result sequence.</typeparam>
/// <param name="start">The starting value. This value will always be included in the resulting sequence.</param>
/// <param name="endInclusive">The ending bound beyond which values will not be included in the sequence.</param>
/// <param name="step">The amount by which the next value in the sequence should be incremented from the previous value.</param>
/// <returns>An <see cref="IEnumerable{T}"/> that contains the sequence.</returns>
/// <exception cref="ArgumentNullException"><paramref name="start"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="step"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="endInclusive"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="step"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="start"/> is NaN.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="endInclusive"/> is NaN.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="step"/> is NaN.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="step"/> is greater than zero but <paramref name="endInclusive"/> is less than <paramref name="start"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="step"/> is less than zero but <paramref name="endInclusive"/> is greater than <paramref name="start"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="step"/> is zero and <paramref name="endInclusive"/> does not equal <paramref name="start"/>.</exception>
Expand All @@ -30,17 +33,43 @@ public static IEnumerable<T> Sequence<T>(T start, T endInclusive, T step) where
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.start);
}

if (T.IsNaN(start))
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
}

if (endInclusive is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.endInclusive);
}

if (T.IsNaN(endInclusive))
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.endInclusive);
}

if (step is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.step);
}

if (step > T.Zero)
if (T.IsNaN(step))
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.step);
}

if (T.IsZero(step))
{
// If start != endInclusive, then the sequence would be infinite. As such, we validate
// that they're equal, and if they are, we return a sequence that yields the start/endInclusive value once.
if (start != endInclusive)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.step);
}

return Repeat(start, 1);
}
else if (T.IsPositive(step))
{
// Presumed to be the most common case, step > 0. Validate that endInclusive >= start, as otherwise we can't easily
// guarantee that the sequence will terminate.
Expand All @@ -67,7 +96,7 @@ public static IEnumerable<T> Sequence<T>(T start, T endInclusive, T step) where
// Otherwise, just produce an incrementing sequence.
return IncrementingIterator(start, endInclusive, step);
}
else if (step < T.Zero)
else
{
// step < 0. Validate that endInclusive <= start, as otherwise we can't easily guarantee that the sequence will terminate.
if (endInclusive > start)
Expand All @@ -78,17 +107,6 @@ public static IEnumerable<T> Sequence<T>(T start, T endInclusive, T step) where
// Then produce the decrementing sequence.
return DecrementingIterator(start, endInclusive, step);
}
else
{
// step == 0. If start != endInclusive, then the sequence would be infinite. As such, we validate
// that they're equal, and if they are, we return a sequence that yields the start/endInclusive value once.
if (start != endInclusive)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.step);
}

return Repeat(start, 1);
}

static RangeIterator<T>? TryUseRange<TLarger>(T start, T endInclusive, T step, TLarger maxValue) where TLarger : INumber<TLarger>
{
Expand All @@ -101,19 +119,19 @@ public static IEnumerable<T> Sequence<T>(T start, T endInclusive, T step) where
return null;
}

static IEnumerable<T> IncrementingIterator(T start, T endInclusive, T step)
static IEnumerable<T> IncrementingIterator(T current, T endInclusive, T step)
{
Debug.Assert(step > T.Zero);

yield return start;
yield return current;

while (true)
{
T next = start + step;
T next = current + step;

if (next >= endInclusive || next <= start)
if (next >= endInclusive || next <= current) // handle overflow and saturation
{
if (next == endInclusive && start != next)
if (next == endInclusive && current != next)
{
yield return next;
}
Expand All @@ -122,24 +140,24 @@ static IEnumerable<T> IncrementingIterator(T start, T endInclusive, T step)
}

yield return next;
start = next;
current = next;
}
}


static IEnumerable<T> DecrementingIterator(T start, T endInclusive, T step)
static IEnumerable<T> DecrementingIterator(T current, T endInclusive, T step)
{
Debug.Assert(step < T.Zero);

yield return start;
yield return current;

while (true)
{
T next = start + step;
T next = current + step;

if (next <= endInclusive || next >= start)
if (next <= endInclusive || next >= current) // handle overflow and saturation
{
if (next == endInclusive && start != next)
if (next == endInclusive && current != next)
{
yield return next;
}
Expand All @@ -148,7 +166,7 @@ static IEnumerable<T> DecrementingIterator(T start, T endInclusive, T step)
}

yield return next;
start = next;
current = next;
}
}
}
Expand Down
Loading
Loading