Skip to content

Generic Enumerable.Range method #106925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
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 @@ -87,7 +87,7 @@ char GetNextAvailableDriveLetter()
List<char> existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList();

// A,B are reserved, C is usually reserved
IEnumerable<int> range = Enumerable.Range('D', 'Z' - 'D');
IEnumerable<int> range = Enumerable.Range<int>('D', 'Z' - 'D');
IEnumerable<char> castRange = range.Select(x => Convert.ToChar(x));
IEnumerable<char> allDrivesLetters = castRange.Except(existingDrives);

Expand Down Expand Up @@ -145,4 +145,4 @@ private static string SubstPath
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
<Compile Include="System.Linq.AsyncEnumerable.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<Compile Include="System.Linq.AsyncEnumerable.netcore.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
<ProjectReference Include="$(LibrariesProjectRoot)System.Linq/ref/System.Linq.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime/ref/System.Runtime.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// ------------------------------------------------------------------------------
// Changes to this file must follow the https://aka.ms/api-review process.
// ------------------------------------------------------------------------------

namespace System.Linq
{
public static partial class AsyncEnumerable
{
public static System.Collections.Generic.IAsyncEnumerable<T> Range<T>(T start, int count) where T : System.Numerics.IBinaryInteger<T> { throw null; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
<Compile Include="System\Linq\Zip.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<Compile Include="System\Linq\Range.netcore.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
<Reference Include="System.Collections" />
<Reference Include="System.Linq" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Numerics;

namespace System.Linq
{
public static partial class AsyncEnumerable
{
/// <summary>Generates a sequence of integral numbers within a specified range.</summary>
/// <typeparam name="T">The <see cref="IBinaryInteger{TSelf}"/> type of the elements in the sequence.</typeparam>
/// <param name="start">The value of the first <see cref="IBinaryInteger{TSelf}"/> in the sequence.</param>
/// <param name="count">The number of sequential <see cref="IBinaryInteger{TSelf}"/> to generate.</param>
/// <returns>An <see cref="IAsyncEnumerable{T}"/> that contains a range of sequential integral numbers.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is less than 0</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="start"/> + <paramref name="count"/> -1 is larger than <see cref="IMinMaxValue{TSelf}.MaxValue"/>.</exception>
public static IAsyncEnumerable<T> Range<T>(T start, int count) where T : IBinaryInteger<T>
{
if (count == 0)
{
return Empty<T>();
}

if (count < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count));
}

T max = start + T.CreateTruncating(count - 1);
if (start > max || StartMaxCount(start, max) + 1 != count)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count));
}

return Impl(start, count);

static async IAsyncEnumerable<T> Impl(T start, int count)
{
for (int i = 0; i < count; i++, start++)
{
yield return start;
}
}

static int StartMaxCount(T start, T max)
{
int count = int.CreateTruncating(max - start);
if (count < 0)
count = int.CreateTruncating(max) - int.CreateTruncating(start);
return count;
}
}
}
}
68 changes: 66 additions & 2 deletions src/libraries/System.Linq.AsyncEnumerable/tests/RangeTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -12,14 +13,30 @@ public class RangeTests : AsyncEnumerableTests
public void InvalidInputs_Throws()
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range(-1, -1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range(-1, -1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range(2, int.MaxValue));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range(int.MaxValue - 1, 3));

#if NET
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<int>(-1, -1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<int>(2, int.MaxValue));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<int>(int.MaxValue - 1, 3));

AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<byte>(255, -1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<byte>(2, byte.MaxValue));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<byte>(byte.MaxValue - 1, 3));

AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<long>(-1, -1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<long>(long.MaxValue - int.MaxValue + 2, int.MaxValue));
AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<long>(long.MaxValue - 1, 3));

AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => AsyncEnumerable.Range<BigInteger>(-1, -1));
#endif
}

[Fact]
public async Task VariousValues_MatchesEnumerable()
{
foreach (int start in new[] { int.MinValue, -1, 0, 1, 1_000_000 })
foreach (int start in new[] { int.MinValue, -1, 0, 1, int.MaxValue - 9 })
{
foreach (int count in new[] { 0, 1, 3, 10 })
{
Expand All @@ -28,6 +45,53 @@ await AssertEqual(
AsyncEnumerable.Range(start, count));
}
}

#if NET
foreach (int start in new[] { int.MinValue, -1, 0, 1, int.MaxValue - 9 })
{
foreach (int count in new[] { 0, 1, 3, 10 })
{
await AssertEqual(
Enumerable.Range<int>(start, count),
AsyncEnumerable.Range<int>(start, count));
}
}

foreach (byte start in new[] { byte.MinValue, 1, byte.MaxValue - 9 })
{
foreach (int count in new[] { 0, 1, 3, 10 })
{
await AssertEqual(
Enumerable.Range<byte>(start, count),
AsyncEnumerable.Range<byte>(start, count));
}
}

foreach (long start in new[] { long.MinValue, -1, 0, 1, long.MaxValue - 9 })
{
foreach (int count in new[] { 0, 1, 3, 10 })
{
await AssertEqual(
Enumerable.Range<long>(start, count),
AsyncEnumerable.Range<long>(start, count));
}
}

foreach (BigInteger start in new[] { -BigInteger.Pow(2, 1024), -1, 0, 1, BigInteger.Pow(2, 1024) })
{
foreach (int count in new[] { 0, 1, 3, 10 })
{
await AssertEqual(
Enumerable.Range<BigInteger>(start, count),
AsyncEnumerable.Range<BigInteger>(start, count));
}

await AssertEqual(
Enumerable.Range<BigInteger>(start, int.MaxValue).Skip(10).Take(10),
AsyncEnumerable.Range<BigInteger>(start, int.MaxValue).Skip(10).Take(10));
}

#endif
}
}
}
1 change: 1 addition & 0 deletions src/libraries/System.Linq/ref/System.Linq.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public static System.Collections.Generic.IEnumerable<
public static System.Linq.IOrderedEnumerable<T> Order<T>(this System.Collections.Generic.IEnumerable<T> source, System.Collections.Generic.IComparer<T>? comparer) { throw null; }
public static System.Collections.Generic.IEnumerable<TSource> Prepend<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, TSource element) { throw null; }
public static System.Collections.Generic.IEnumerable<int> Range(int start, int count) { throw null; }
public static System.Collections.Generic.IEnumerable<T> Range<T>(T start, int count) where T : System.Numerics.IBinaryInteger<T> { throw null; }
public static System.Collections.Generic.IEnumerable<TResult> Repeat<TResult>(TResult element, int count) { throw null; }
public static System.Collections.Generic.IEnumerable<TSource> Reverse<TSource>(this System.Collections.Generic.IEnumerable<TSource> source) { throw null; }
public static System.Collections.Generic.IEnumerable<TSource> Reverse<TSource>(this TSource[] source) { throw null; }
Expand Down
82 changes: 42 additions & 40 deletions src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,113 +2,115 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

namespace System.Linq
{
public static partial class Enumerable
{
private sealed partial class RangeIterator : IList<int>, IReadOnlyList<int>
private sealed partial class RangeIterator<T> : IList<T>, IReadOnlyList<T>
{
public override IEnumerable<TResult> Select<TResult>(Func<int, TResult> selector)
public override IEnumerable<TResult> Select<TResult>(Func<T, TResult> selector)
{
return new RangeSelectIterator<TResult>(_start, _end, selector);
return new RangeSelectIterator<T, TResult>(_start, _end, selector);
}

public override int[] ToArray()
public override T[] ToArray()
{
int start = _start;
int[] array = new int[_end - start];
T start = _start;
T[] array = new T[Count];
FillIncrementing(array, start);
return array;
}

public override List<int> ToList()
public override List<T> ToList()
{
(int start, int end) = (_start, _end);
List<int> list = new List<int>(end - start);
FillIncrementing(SetCountAndGetSpan(list, end - start), start);
int count = Count;
List<T> list = new List<T>(count);
FillIncrementing(SetCountAndGetSpan(list, count), _start);
return list;
}

public void CopyTo(int[] array, int arrayIndex) =>
FillIncrementing(array.AsSpan(arrayIndex, _end - _start), _start);
public void CopyTo(T[] array, int arrayIndex) =>
FillIncrementing(array.AsSpan(arrayIndex, Count), _start);

public override int GetCount(bool onlyIfCheap) => _end - _start;
public override int GetCount(bool onlyIfCheap) => Count;

public int Count => _end - _start;
public int Count => _count;

public override Iterator<int>? Skip(int count)
public override Iterator<T>? Skip(int count)
{
if (count >= _end - _start)
Debug.Assert(count > 0);
if (count >= Count)
{
return null;
}

return new RangeIterator(_start + count, _end - _start - count);
return new RangeIterator<T>(_start + T.CreateTruncating(count), Count - count);
}

public override Iterator<int> Take(int count)
public override Iterator<T>? Take(int count)
{
int curCount = _end - _start;
if (count >= curCount)
Debug.Assert(count > 0);
if (count >= Count)
{
return this;
}

return new RangeIterator(_start, count);
return new RangeIterator<T>(_start, count);
}

public override int TryGetElementAt(int index, out bool found)
public override T TryGetElementAt(int index, out bool found)
{
if ((uint)index < (uint)(_end - _start))
if ((uint)index < (uint)Count)
{
found = true;
return _start + index;
return _start + T.CreateTruncating(index);
}

found = false;
return 0;
return T.Zero;
}

public override int TryGetFirst(out bool found)
public override T TryGetFirst(out bool found)
{
found = true;
return _start;
}

public override int TryGetLast(out bool found)
public override T TryGetLast(out bool found)
{
found = true;
return _end - 1;
return _end - T.One;
}

public bool Contains(int item) =>
(uint)(item - _start) < (uint)(_end - _start);
public bool Contains(T item) =>
_start <= item && item <= _end - T.One; // _start can be equal to _end

public int IndexOf(int item) =>
Contains(item) ? item - _start : -1;
public int IndexOf(T item) =>
Contains(item) ? StartMaxCount(_start, item) : -1;

public int this[int index]
public T this[int index]
{
get
{
if ((uint)index >= (uint)(_end - _start))
if ((uint)index >= (uint)Count)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}

return _start + index;
return _start + T.CreateTruncating(index);
}
set => ThrowHelper.ThrowNotSupportedException();
}

public bool IsReadOnly => true;

void ICollection<int>.Add(int item) => ThrowHelper.ThrowNotSupportedException();
void ICollection<int>.Clear() => ThrowHelper.ThrowNotSupportedException();
void IList<int>.Insert(int index, int item) => ThrowHelper.ThrowNotSupportedException();
bool ICollection<int>.Remove(int item) => ThrowHelper.ThrowNotSupportedException_Boolean();
void IList<int>.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException();
void ICollection<T>.Add(T item) => ThrowHelper.ThrowNotSupportedException();
void ICollection<T>.Clear() => ThrowHelper.ThrowNotSupportedException();
void IList<T>.Insert(int index, T item) => ThrowHelper.ThrowNotSupportedException();
bool ICollection<T>.Remove(T item) => ThrowHelper.ThrowNotSupportedException_Boolean();
void IList<T>.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException();
}
}
}
Loading
Loading