├── RangeExtensions.Tests ├── Usings.cs ├── Properties.cs ├── RangeExtensions.Tests.csproj ├── RangeEnumerable.IEnumerable.cs ├── Specialized │ ├── WhereRange.cs │ ├── WhereRange.SpeedOpt.cs │ ├── SelectRange.SpeedOpt.cs │ └── SelectRange.cs ├── RangeEnumerable.IList.cs ├── RangeEnumerable.cs ├── Data.cs ├── Helpers.cs ├── RangeEnumerable.ICollection.cs ├── RangeExtensions.cs └── RangeEnumerable.SpeedOpt.cs ├── RangeExtensions ├── Usings.cs ├── RangeEnumerable.IEnumerable.cs ├── RangeEnumerable.IList.cs ├── RangeExtensions.csproj ├── ThrowHelpers.cs ├── RangeExtensions.cs ├── Specialized │ ├── WhereRange.cs │ ├── WhereRange.SpeedOpt.cs │ ├── SelectRange.SpeedOpt.cs │ └── SelectRange.cs ├── RangeEnumerable.cs ├── RangeEnumerable.ICollection.cs └── RangeEnumerable.SpeedOpt.cs ├── RangeExtensions.Benchmarks ├── Program.cs ├── RangeExtensions.Benchmarks.csproj ├── ToListArray.cs ├── SpeedOpt.cs ├── EnumerableExtras.cs └── ForEach.cs ├── dotnet-releaser.toml ├── .github ├── dependabot.yml └── workflows │ └── dotnet-releaser.yml ├── LICENSE ├── .vscode ├── launch.json └── tasks.json ├── RangeExtensions.sln ├── README.md └── .gitignore /RangeExtensions.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /RangeExtensions.Tests/Properties.cs: -------------------------------------------------------------------------------- 1 | #if !NET48 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | [assembly: ExcludeFromCodeCoverage] 5 | #endif -------------------------------------------------------------------------------- /RangeExtensions/Usings.cs: -------------------------------------------------------------------------------- 1 | global using System.Diagnostics.CodeAnalysis; 2 | global using System.Runtime.CompilerServices; 3 | global using RangeExtensions; -------------------------------------------------------------------------------- /RangeExtensions.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | BenchmarkSwitcher 4 | .FromAssembly(typeof(Program).Assembly) 5 | .Run(); 6 | -------------------------------------------------------------------------------- /dotnet-releaser.toml: -------------------------------------------------------------------------------- 1 | # configuration file for dotnet-releaser 2 | [msbuild] 3 | project = "RangeExtensions.sln" 4 | [github] 5 | user = "neon-sunset" 6 | repo = "RangeExtensions" -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /RangeExtensions/RangeEnumerable.IEnumerable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace System.Linq; 4 | 5 | public readonly partial record struct RangeEnumerable : IEnumerable 6 | { 7 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 8 | IEnumerator IEnumerable.GetEnumerator() 9 | { 10 | return GetEnumeratorUnchecked(); 11 | } 12 | 13 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 14 | IEnumerator IEnumerable.GetEnumerator() 15 | { 16 | return GetEnumeratorUnchecked(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RangeExtensions.Benchmarks/RangeExtensions.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0;net7.0 6 | enable 7 | enable 8 | nullable 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /RangeExtensions.Benchmarks/ToListArray.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Jobs; 3 | 4 | namespace RangeExtensions.Benchmarks; 5 | 6 | [SimpleJob] 7 | [MemoryDiagnoser] 8 | [DisassemblyDiagnoser(maxDepth: 2, exportCombinedDisassemblyReport: true)] 9 | public class ToListArray 10 | { 11 | [Params(10, 1000, 100000)] 12 | public int Length; 13 | 14 | [Benchmark(Baseline = true)] 15 | public int[] RangeToArray() => (0..Length).ToArray(); 16 | 17 | [Benchmark] 18 | public int[] EnumerableToArray() => Enumerable.Range(0, Length).ToArray(); 19 | 20 | [Benchmark] 21 | public List RangeToList() => (0..Length).ToList(); 22 | 23 | [Benchmark] 24 | public List EnumerableToList() => Enumerable.Range(0, Length).ToList(); 25 | 26 | [Benchmark] 27 | public int[] RangeSelectToArray() => (0..Length).Select(i => i).ToArray(); 28 | 29 | [Benchmark] 30 | public int[] EnumerableSelectToArray() => Enumerable.Range(0, Length).Select(i => i).ToArray(); 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 neon-sunset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/RangeExtensions.Benchmarks/bin/Debug/net6.0/RangeExtensions.Benchmarks.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/RangeExtensions.Benchmarks", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.github/workflows/dotnet-releaser.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '*.md' 7 | pull_request: 8 | 9 | jobs: 10 | all-in-one: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | with: 16 | submodules: true 17 | fetch-depth: 0 18 | - name: Install .NET SDK 19 | uses: actions/setup-dotnet@v2 20 | with: 21 | dotnet-version: | 22 | 3.1.x 23 | 6.0.x 24 | 7.0.x 25 | - name: Build, Test, Pack, Publish 26 | shell: bash 27 | run: | 28 | dotnet tool install -g dotnet-releaser 29 | dotnet-releaser run --nuget-token "${{secrets.NUGET_TOKEN}}" --github-token "${{secrets.GITHUB_TOKEN}}" dotnet-releaser.toml 30 | 31 | tests-extra-net48: 32 | runs-on: windows-latest 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v3 36 | with: 37 | submodules: true 38 | fetch-depth: 0 39 | - name: Install .NET SDK 40 | uses: actions/setup-dotnet@v2 41 | with: 42 | dotnet-version: | 43 | 3.1.x 44 | 6.0.x 45 | 7.0.x 46 | - name: Build, Test, Pack, Publish 47 | shell: pwsh 48 | run: | 49 | dotnet test -c release -r win-x64 50 | -------------------------------------------------------------------------------- /RangeExtensions.Benchmarks/SpeedOpt.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace RangeExtensions.Benchmarks; 4 | 5 | [ShortRunJob] 6 | [MemoryDiagnoser] 7 | [DisassemblyDiagnoser(maxDepth: 2, exportCombinedDisassemblyReport: true)] 8 | public class SpeedOpt 9 | { 10 | public int Length = 1000; 11 | 12 | [Benchmark] 13 | public long RangeAggregate() => (0..Length) 14 | .Select(i => (long)i) 15 | .Aggregate((acc, i) => acc + i); 16 | 17 | [Benchmark] 18 | public long EnumerableAggregate() => Enumerable.Range(0, Length) 19 | .Select(i => (long)i) 20 | .Aggregate((acc, i) => acc + i); 21 | 22 | [Benchmark] 23 | public int RangeWhereLast() => (0..Length) 24 | .Where(i => i % 64 is 0) 25 | .Last(); 26 | 27 | [Benchmark] 28 | public int EnumerableWhereLast() => Enumerable 29 | .Range(0, Length) 30 | .Last(i => i % 64 is 0); 31 | 32 | [Benchmark] 33 | public long RangeIndex() => (0..Length) 34 | .Select(i => (long)i)[Length - 16]; 35 | 36 | [Benchmark] 37 | public long RangeElementAt() => (0..Length) 38 | .Select(i => (long)i) 39 | .ElementAt(Length - 16); 40 | 41 | [Benchmark] 42 | public long EnumerableElementAt() => Enumerable 43 | .Range(0, Length) 44 | .Select(i => (long)i) 45 | .ElementAt(Length - 16); 46 | } 47 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/RangeExtensions.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1;net6.0;net7.0 5 | $(TargetFrameworks);net48 6 | 11 7 | enable 8 | enable 9 | nullable 10 | 11 | false 12 | RangeExtensions.Tests.AssertHelpers* 13 | 14 | 15 | 16 | 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | all 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/RangeExtensions.Benchmarks/RangeExtensions.Benchmarks.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/RangeExtensions.Benchmarks/RangeExtensions.Benchmarks.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/RangeExtensions.Benchmarks/RangeExtensions.Benchmarks.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /RangeExtensions/RangeEnumerable.IList.cs: -------------------------------------------------------------------------------- 1 | namespace System.Linq; 2 | 3 | public readonly partial record struct RangeEnumerable : IList 4 | { 5 | public int this[int index] 6 | { 7 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 8 | get 9 | { 10 | if (index < 0 || index >= Count || _start == _end) 11 | { 12 | IndexOutOfRange(); 13 | } 14 | 15 | return _start < _end 16 | ? _start + index 17 | : _start - index - 1; 18 | } 19 | set => throw new NotSupportedException(); 20 | } 21 | 22 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 23 | public int IndexOf(int item) 24 | { 25 | var count = Count; 26 | var index = _start < _end 27 | ? item - _start 28 | : _start - 1 - item; 29 | 30 | return index >= 0 && index < count 31 | ? index : -1; 32 | } 33 | 34 | public void Insert(int index, int item) 35 | { 36 | throw new NotSupportedException(); 37 | } 38 | 39 | public void RemoveAt(int index) 40 | { 41 | throw new NotSupportedException(); 42 | } 43 | 44 | #if NETSTANDARD2_0 45 | [MethodImpl(MethodImplOptions.NoInlining)] 46 | #else 47 | [DoesNotReturn] 48 | #endif 49 | private static void IndexOutOfRange() 50 | { 51 | throw new ArgumentOutOfRangeException( 52 | "Index was outside the bounds of the enumerable range."); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/RangeEnumerable.IEnumerable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace RangeExtensions.Tests; 4 | 5 | public partial class RangeEnumerableTests 6 | { 7 | [Theory, MemberData(nameof(ValidRangePairs))] 8 | public void RangeEnumerator_MatchesStandardEnumerableRange(Range range, IEnumerable enumerable) 9 | { 10 | IEnumerable Enumerate() 11 | { 12 | foreach (var i in range) 13 | { 14 | yield return i; 15 | } 16 | } 17 | 18 | Assert.Equal(enumerable, Enumerate()); 19 | } 20 | 21 | [Theory, MemberData(nameof(ValidRangePairs))] 22 | public void RangeEnumerableBoxed_MatchesStandardEnumerableRange(Range range, IEnumerable enumerable) 23 | { 24 | IEnumerable EnumerateBoxed() 25 | { 26 | foreach (var i in (IEnumerable)range.AsEnumerable()) 27 | { 28 | yield return (int)i!; 29 | } 30 | } 31 | 32 | Assert.Equal(enumerable, EnumerateBoxed()); 33 | } 34 | 35 | [Theory, MemberData(nameof(ValidRangePairs))] 36 | public void RangeEnumerableBoxedGeneric_MatchesStandardEnumerableRange(Range range, IEnumerable enumerable) 37 | { 38 | IEnumerable EnumerateBoxed() 39 | { 40 | foreach (var i in (IEnumerable)range.AsEnumerable()) 41 | { 42 | yield return i; 43 | } 44 | } 45 | 46 | Assert.Equal(enumerable, EnumerateBoxed()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RangeExtensions/RangeExtensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | neon-sunset 5 | https://github.com/neon-sunset/RangeExtensions 6 | https://github.com/neon-sunset/RangeExtensions 7 | MIT 8 | Range;System.Range;Extensions;LINQ;IEnumerable;foreach;RangeForEach 9 | README.md 10 | A set of optimized extensions which integrate System.Range with foreach and LINQ. 11 | 12 | 13 | 14 | netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0 15 | 11 16 | enable 17 | enable 18 | nullable 19 | true 20 | 21 | 22 | 23 | 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | all 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/Specialized/WhereRange.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions.Tests; 2 | 3 | public partial class WhereRangeTests 4 | { 5 | public static IEnumerable ValidRangePairs() => Data.ValidRangePairs(); 6 | 7 | [Theory, MemberData(nameof(ValidRangePairs))] 8 | public void Enumerator_MatchesStandardEnumerable(Range range, IEnumerable enumerable) 9 | { 10 | IEnumerable Enumerate() 11 | { 12 | foreach (var i in range.Where(i => i % 2 != 0)) 13 | { 14 | yield return i; 15 | } 16 | } 17 | 18 | var expected = enumerable.Where(i => i % 2 != 0); 19 | var actual = Enumerate(); 20 | 21 | Assert.Equal(expected, actual); 22 | } 23 | 24 | [Theory, MemberData(nameof(ValidRangePairs))] 25 | public void EnumeratorBoxed_MatchesStandardEnumerable(Range range, IEnumerable enumerable) 26 | { 27 | IEnumerable EnumerateBoxed() 28 | { 29 | foreach (var i in (IEnumerable)range.Where(i => i % 2 != 0)) 30 | { 31 | yield return i; 32 | } 33 | } 34 | 35 | var expected = enumerable.Where(i => i % 2 != 0); 36 | var actual = EnumerateBoxed(); 37 | 38 | Assert.Equal(expected, actual); 39 | } 40 | 41 | [Fact] 42 | public void EnumeratorReset_ThrowsNotSupportedException() 43 | { 44 | static void ResetEnumerator() 45 | { 46 | (0..100).Where(i => i % 2 != 0).GetEnumerator().Reset(); 47 | } 48 | 49 | Assert.Throws(ResetEnumerator); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /RangeExtensions.Benchmarks/EnumerableExtras.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace RangeExtensions.Benchmarks; 5 | 6 | [ShortRunJob] 7 | [MemoryDiagnoser] 8 | [DisassemblyDiagnoser(maxDepth: 5, exportCombinedDisassemblyReport: true)] 9 | public class EnumerableExtras 10 | { 11 | // [Params(1, 10, 100, 10000)] 12 | public const int Length = 1000; 13 | 14 | [Benchmark] public bool RangeAny() => Range(Length).Any(); 15 | 16 | [Benchmark] public bool EnumerableAny() => Enumerable(Length).Any(); 17 | 18 | [Benchmark] public bool RangeContains() => Range(Length).Contains(Length / 2); 19 | 20 | [Benchmark] public bool EnumerableContains() => Enumerable(Length).Contains(Length / 2); 21 | 22 | [Benchmark] public int RangeCount() => Range(Length).Count(); 23 | 24 | [Benchmark] public int EnumerableCount() => Enumerable(Length).Count(); 25 | 26 | [Benchmark] public int RangeMax() => Range(Length).Max(); 27 | 28 | [Benchmark] public int EnumerableMax() => Enumerable(Length).Max(); 29 | 30 | [Benchmark] public double RangeAverage() => Range(Length).Average(); 31 | 32 | [Benchmark] public double EnumerableAverage() => Enumerable(Length).Average(); 33 | 34 | [Benchmark] public int RangeSum() => Range(Length).Sum(); 35 | 36 | [Benchmark] public int EnumerableSum() => Enumerable(Length).Sum(); 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | private static RangeEnumerable Range(int length) => 0..length; 40 | 41 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 42 | private static IEnumerable Enumerable(int length) => System.Linq.Enumerable.Range(0, length); 43 | } 44 | -------------------------------------------------------------------------------- /RangeExtensions.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RangeExtensions", "RangeExtensions\RangeExtensions.csproj", "{C35D3E00-B23A-43E2-A491-091AA6EA5C62}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RangeExtensions.Benchmarks", "RangeExtensions.Benchmarks\RangeExtensions.Benchmarks.csproj", "{8C8AEBE2-07C9-4832-B520-753BB3AF2239}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RangeExtensions.Tests", "RangeExtensions.Tests\RangeExtensions.Tests.csproj", "{FC784CD4-4EF0-42AD-996E-F1777352C2ED}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {C35D3E00-B23A-43E2-A491-091AA6EA5C62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {C35D3E00-B23A-43E2-A491-091AA6EA5C62}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {C35D3E00-B23A-43E2-A491-091AA6EA5C62}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {C35D3E00-B23A-43E2-A491-091AA6EA5C62}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {8C8AEBE2-07C9-4832-B520-753BB3AF2239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {8C8AEBE2-07C9-4832-B520-753BB3AF2239}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {8C8AEBE2-07C9-4832-B520-753BB3AF2239}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {8C8AEBE2-07C9-4832-B520-753BB3AF2239}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {FC784CD4-4EF0-42AD-996E-F1777352C2ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {FC784CD4-4EF0-42AD-996E-F1777352C2ED}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {FC784CD4-4EF0-42AD-996E-F1777352C2ED}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {FC784CD4-4EF0-42AD-996E-F1777352C2ED}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/RangeEnumerable.IList.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions.Tests; 2 | 3 | public partial class RangeEnumerableTests 4 | { 5 | [Theory, MemberData(nameof(ValidRangePairs))] 6 | public void IndexOperator_MatchesIListIndexOperator(Range range, IEnumerable enumerable) 7 | { 8 | var expected = enumerable.ToList(); 9 | var actual = range.AsEnumerable(); 10 | 11 | for (var i = 0; i < expected.Count; i++) 12 | { 13 | Assert.Equal(expected[i], actual[i]); 14 | } 15 | } 16 | 17 | [Theory, MemberData(nameof(ValidRangePairs))] 18 | public void IndexOperator_ThrowsOnOutOfBoundsAccess(Range range, IEnumerable enumerable) 19 | { 20 | var expected = enumerable.ToList(); 21 | var actual = range.AsEnumerable(); 22 | 23 | foreach (var i in Data.InvalidIndexes(expected)) 24 | { 25 | Assert.Throws(() => expected[i]); 26 | Assert.Throws(() => actual[i]); 27 | } 28 | } 29 | 30 | [Fact] 31 | public void IndexOperator_SetThrowsNotSupportedException() 32 | { 33 | static void SetIndex() 34 | { 35 | (0..100).AsEnumerable()[10] = 10; 36 | } 37 | 38 | Assert.Throws(SetIndex); 39 | } 40 | 41 | [Theory, MemberData(nameof(ValidRangePairs))] 42 | public void IndexOf_MatchesIListIndexOf(Range range, IEnumerable enumerable) 43 | { 44 | var expected = enumerable.ToList(); 45 | var actual = range.AsEnumerable(); 46 | 47 | foreach (var value in Data.Indexes(expected)) 48 | { 49 | Assert.Equal(expected.IndexOf(value), actual.IndexOf(value)); 50 | } 51 | } 52 | 53 | [Fact] 54 | public void Insert_ThrowsNotSupportedException() 55 | { 56 | static void Insert() 57 | { 58 | (..100).AsEnumerable().Insert(10, 1); 59 | } 60 | 61 | Assert.Throws(Insert); 62 | } 63 | 64 | [Fact] 65 | public void RemoveAt_ThrowsNotSupportedException() 66 | { 67 | static void RemoveAt() 68 | { 69 | (..100).AsEnumerable().RemoveAt(10); 70 | } 71 | 72 | Assert.Throws(RemoveAt); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /RangeExtensions/ThrowHelpers.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions; 2 | 3 | internal static class ThrowHelpers 4 | { 5 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 6 | public static void CheckInvalid(int start, int end) 7 | { 8 | if (start < 0 || end < 0) 9 | { 10 | InvalidRange(start, end); 11 | } 12 | } 13 | 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static void CheckEmpty(RangeEnumerable enumerable) 16 | { 17 | var (start, end) = enumerable; 18 | if (start == end) 19 | { 20 | EmptyRange(); 21 | } 22 | } 23 | 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static void CheckEmpty(int start, int end) 26 | { 27 | if (start == end) 28 | { 29 | EmptyRange(); 30 | } 31 | } 32 | 33 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 34 | public static void CheckNull(T? value) where T : class 35 | { 36 | if (value is null) 37 | { 38 | ArgumentNull(); 39 | } 40 | } 41 | 42 | #if NETSTANDARD2_0 43 | [MethodImpl(MethodImplOptions.NoInlining)] 44 | #else 45 | [DoesNotReturn] 46 | #endif 47 | public static void ArgumentException() 48 | { 49 | throw new ArgumentException(); 50 | } 51 | 52 | #if NETSTANDARD2_0 53 | [MethodImpl(MethodImplOptions.NoInlining)] 54 | #else 55 | [DoesNotReturn] 56 | #endif 57 | public static void ArgumentNull() 58 | { 59 | throw new ArgumentNullException(); 60 | } 61 | 62 | #if NETSTANDARD2_0 63 | [MethodImpl(MethodImplOptions.NoInlining)] 64 | #else 65 | [DoesNotReturn] 66 | #endif 67 | public static void ArgumentOutOfRange() 68 | { 69 | throw new ArgumentOutOfRangeException(); 70 | } 71 | 72 | #if NETSTANDARD2_0 73 | [MethodImpl(MethodImplOptions.NoInlining)] 74 | #else 75 | [DoesNotReturn] 76 | #endif 77 | public static void InvalidRange(int start, int end) 78 | { 79 | throw new ArgumentOutOfRangeException(nameof(Range), start..end, "Cannot enumerate numbers in range with a head or tail indexed from end."); 80 | } 81 | 82 | #if NETSTANDARD2_0 83 | [MethodImpl(MethodImplOptions.NoInlining)] 84 | #else 85 | [DoesNotReturn] 86 | #endif 87 | public static void EmptyRange() 88 | { 89 | throw new InvalidOperationException("Sequence contains no elements"); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/RangeEnumerable.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions.Tests; 2 | 3 | public partial class RangeEnumerableTests 4 | { 5 | public static IEnumerable ValidRangePairs() => Data.ValidRangePairs(); 6 | public static IEnumerable InvalidRanges() => Data.InvalidRanges(); 7 | 8 | [Theory, MemberData(nameof(ValidRangePairs))] 9 | public void RangeEnumerable_MatchesStandardEnumerableRange(Range range, IEnumerable enumerable) 10 | { 11 | Assert.Equal(enumerable, range.AsEnumerable()); 12 | } 13 | 14 | [Theory, MemberData(nameof(InvalidRanges))] 15 | public void RangeEnumerable_ThrowsOnInvalidRange(Range range) 16 | { 17 | void AsEnumerable() 18 | { 19 | _ = range.AsEnumerable(); 20 | } 21 | 22 | Assert.Throws(AsEnumerable); 23 | } 24 | 25 | [Theory, MemberData(nameof(InvalidRanges))] 26 | public void RangeEnumerator_ThrowsOnInvalidRange(Range range) 27 | { 28 | void Enumerate() 29 | { 30 | foreach (var i in range) { } 31 | } 32 | 33 | Assert.Throws(Enumerate); 34 | } 35 | 36 | [Fact] 37 | public void RangeEnumerator_ResetThrows() 38 | { 39 | static void Reset() 40 | { 41 | (0..int.MaxValue).GetEnumerator().Reset(); 42 | } 43 | 44 | Assert.Throws(Reset); 45 | } 46 | 47 | [Theory, MemberData(nameof(ValidRangePairs))] 48 | public void RangeEnumerable_ImplicitConversionToRange_KeepsRangeIntact(Range range, IEnumerable enumerable) 49 | { 50 | _ = enumerable; 51 | var rangeEnumerable = range.AsEnumerable(); 52 | 53 | Assert.Equal(range, rangeEnumerable); 54 | } 55 | 56 | [Theory, MemberData(nameof(ValidRangePairs))] 57 | public void RangeEnumerable_ImplicitConversionFromRange_KeepsRangeIntact(Range range, IEnumerable enumerable) 58 | { 59 | _ = enumerable; 60 | var rangeEnumerable = range.AsEnumerable(); 61 | 62 | Assert.Equal(rangeEnumerable, range); 63 | } 64 | 65 | [Theory, MemberData(nameof(InvalidRanges))] 66 | public void RangeEnumerable_ImplicitConversionFromRange_ThrowsOnInvalidRange(Range range) 67 | { 68 | void ToRangeEnumerable() 69 | { 70 | RangeEnumerable _ = range; 71 | } 72 | 73 | Assert.Throws(ToRangeEnumerable); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /RangeExtensions.Benchmarks/ForEach.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace RangeExtensions.Benchmarks; 4 | 5 | [MemoryDiagnoser] 6 | [HideColumns("StdDev", "Gen0", "Alloc Ratio")] 7 | // [DisassemblyDiagnoser(maxDepth: 2, exportCombinedDisassemblyReport: true)] 8 | public class ForEach 9 | { 10 | [Params(1, 100, 100_000)] 11 | public int Length; 12 | 13 | [Benchmark(Baseline = true)] 14 | public int For() 15 | { 16 | var ret = 0; 17 | for (int i = 0; i < Length; i++) 18 | { 19 | ret += i; 20 | } 21 | 22 | return ret; 23 | } 24 | 25 | [Benchmark] 26 | public int Range() 27 | { 28 | var ret = 0; 29 | foreach (var i in 0..Length) 30 | { 31 | ret += i; 32 | } 33 | 34 | return ret; 35 | } 36 | 37 | [Benchmark] 38 | public int EnumerableRange() 39 | { 40 | var ret = 0; 41 | foreach (var i in Enumerable.Range(0, Length)) 42 | { 43 | ret += i; 44 | } 45 | 46 | return ret; 47 | } 48 | 49 | [Benchmark] 50 | public int RangeSelect() 51 | { 52 | var ret = 0; 53 | foreach (var i in (..Length).Select(i => i * 2)) 54 | { 55 | ret += i; 56 | } 57 | 58 | return ret; 59 | } 60 | 61 | [Benchmark] 62 | public int EnumerableSelect() 63 | { 64 | var ret = 0; 65 | foreach (var i in Enumerable.Range(0, Length).Select(i => i * 2)) 66 | { 67 | ret += i; 68 | } 69 | 70 | return ret; 71 | } 72 | 73 | [Benchmark] 74 | public int RangeSelectTwice() 75 | { 76 | var ret = 0; 77 | foreach (var i in (..Length) 78 | .Select(i => i * 2) 79 | .Select(i => i * 2)) 80 | { 81 | ret += i; 82 | } 83 | 84 | return ret; 85 | } 86 | 87 | [Benchmark] 88 | public int EnumerableSelectTwice() 89 | { 90 | var ret = 0; 91 | foreach (var i in Enumerable.Range(0, Length) 92 | .Select(i => i * 2) 93 | .Select(i => i * 2)) 94 | { 95 | ret += i; 96 | } 97 | 98 | return ret; 99 | } 100 | 101 | [Benchmark] 102 | public int RangeWhere() 103 | { 104 | var ret = 0; 105 | foreach (var i in (..Length).Where(i => i % 2 is 0)) 106 | { 107 | ret += i; 108 | } 109 | 110 | return ret; 111 | } 112 | 113 | [Benchmark] 114 | public int EnumerableWhere() 115 | { 116 | var ret = 0; 117 | foreach (var i in Enumerable.Range(0, Length).Where(i => i % 2 is 0)) 118 | { 119 | ret += i; 120 | } 121 | 122 | return ret; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /RangeExtensions/RangeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | public static class RangeExtensions 4 | { 5 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 6 | public static RangeEnumerable AsEnumerable(this Range range) 7 | { 8 | return range; 9 | } 10 | 11 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 12 | public static RangeEnumerable.Enumerator GetEnumerator(this Range range) 13 | { 14 | return range.AsEnumerable().GetEnumerator(); 15 | } 16 | 17 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 18 | public static int Aggregate(this Range range, Func func) 19 | { 20 | return range.AsEnumerable().Aggregate(func); 21 | } 22 | 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public static TAccumulate Aggregate( 25 | this Range range, TAccumulate seed, Func func) 26 | { 27 | return range.AsEnumerable().Aggregate(seed, func); 28 | } 29 | 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static SelectRange Select(this Range range, Func selector) 32 | { 33 | return range.AsEnumerable().Select(selector); 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public static WhereRange Where(this Range range, Func predicate) 38 | { 39 | return range.AsEnumerable().Where(predicate); 40 | } 41 | 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public static int[] ToArray(this Range range) 44 | { 45 | return range.AsEnumerable().ToArray(); 46 | } 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | public static List ToList(this Range range) 50 | { 51 | return new(range.AsEnumerable()); 52 | } 53 | 54 | #if !NETSTANDARD2_0 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | public static void CopyTo(this Range range, Span destination) 57 | { 58 | range.AsEnumerable().CopyTo(destination, 0); 59 | } 60 | 61 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 62 | public static bool TryCopyTo(this Range range, Span destination) 63 | { 64 | var result = false; 65 | var enumerable = range.AsEnumerable(); 66 | 67 | if (enumerable.Count <= destination.Length) 68 | { 69 | enumerable.CopyTo(destination, 0); 70 | result = true; 71 | } 72 | 73 | return result; 74 | } 75 | #endif 76 | 77 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 78 | internal static (int, int) UnwrapUnchecked(this Range range) 79 | { 80 | var (start, end) = (range.Start, range.End); 81 | 82 | #if NETCOREAPP3_1 || NET6_0_OR_GREATER 83 | return ( 84 | Unsafe.As(ref start), 85 | Unsafe.As(ref end)); 86 | #else 87 | return (start.Value, end.Value); 88 | #endif 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /RangeExtensions/Specialized/WhereRange.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RangeExtensions; 5 | 6 | [StructLayout(LayoutKind.Auto)] 7 | public readonly partial record struct WhereRange : IEnumerable 8 | { 9 | private readonly Func _predicate; 10 | private readonly int _start; 11 | private readonly int _end; 12 | 13 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 14 | internal WhereRange(Func predicate, int start, int end) 15 | { 16 | ThrowHelpers.CheckNull(predicate); 17 | 18 | _predicate = predicate; 19 | _start = start; 20 | _end = end; 21 | } 22 | 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public Enumerator GetEnumerator() 25 | { 26 | return new(_predicate, _start, _end); 27 | } 28 | 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 34 | 35 | [StructLayout(LayoutKind.Auto)] 36 | public record struct Enumerator : IEnumerator 37 | { 38 | private readonly Func _predicate; 39 | private readonly int _shift; 40 | private readonly int _end; 41 | 42 | private int _current; 43 | 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | internal Enumerator(Func predicate, int start, int end) 46 | { 47 | _predicate = predicate; 48 | 49 | if (start < end) 50 | { 51 | _shift = 1; 52 | _current = start - 1; 53 | _end = end; 54 | } 55 | else 56 | { 57 | _shift = -1; 58 | _current = start; 59 | _end = end - 1; 60 | } 61 | } 62 | 63 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 64 | public bool MoveNext() 65 | { 66 | var current = _current; 67 | while ((current += _shift) != _end) 68 | { 69 | if (_predicate(current)) 70 | { 71 | _current = current; 72 | return true; 73 | } 74 | } 75 | 76 | return false; 77 | } 78 | 79 | public int Current 80 | { 81 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 82 | get => _current; 83 | } 84 | 85 | object IEnumerator.Current => _current; 86 | 87 | #if NETSTANDARD2_0 88 | [MethodImpl(MethodImplOptions.NoInlining)] 89 | #else 90 | [DoesNotReturn] 91 | #endif 92 | public void Reset() 93 | { 94 | throw new NotSupportedException(); 95 | } 96 | 97 | public void Dispose() { } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /RangeExtensions/RangeEnumerable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace System.Linq; 4 | 5 | public readonly partial record struct RangeEnumerable 6 | { 7 | private readonly int _start; 8 | private readonly int _end; 9 | 10 | public static RangeEnumerable Empty => default; 11 | 12 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 13 | public RangeEnumerable(Range range) 14 | { 15 | var (start, end) = range.UnwrapUnchecked(); 16 | 17 | // Sadly we need this to both avoid codegen regressions pre-net8.0 and comply with tests on netstandard 18 | #if NETCOREAPP3_1 || NET6_0_OR_GREATER 19 | ThrowHelpers.CheckInvalid(start, end); 20 | #else 21 | if (range.Start.IsFromEnd || range.End.IsFromEnd) 22 | { 23 | ThrowHelpers.InvalidRange(start, end); 24 | } 25 | #endif 26 | 27 | _start = start; 28 | _end = end; 29 | } 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | internal RangeEnumerable(int start, int end) 33 | { 34 | _start = start; 35 | _end = end; 36 | } 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public Enumerator GetEnumerator() 40 | { 41 | return GetEnumeratorUnchecked(); 42 | } 43 | 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | private Enumerator GetEnumeratorUnchecked() 46 | { 47 | return new(_start, _end); 48 | } 49 | 50 | public record struct Enumerator : IEnumerator 51 | { 52 | private readonly int _shift; 53 | private readonly int _end; 54 | 55 | private int _current; 56 | 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | internal Enumerator(int start, int end) 59 | { 60 | if (start < end) 61 | { 62 | _shift = 1; 63 | _current = start - 1; 64 | _end = end; 65 | } 66 | else 67 | { 68 | _shift = -1; 69 | _current = start; 70 | _end = end - 1; 71 | } 72 | } 73 | 74 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 75 | public bool MoveNext() 76 | { 77 | return (_current += _shift) != _end; 78 | } 79 | 80 | public int Current 81 | { 82 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 | get => _current; 84 | } 85 | 86 | object IEnumerator.Current => _current; 87 | 88 | public void Reset() 89 | { 90 | throw new NotSupportedException(); 91 | } 92 | 93 | public void Dispose() { } 94 | } 95 | 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | public static implicit operator RangeEnumerable(Range range) => new(range); 98 | 99 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 100 | public static implicit operator Range(RangeEnumerable enumerable) => enumerable._start..enumerable._end; 101 | } 102 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/Data.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions.Tests; 2 | 3 | internal static class Data 4 | { 5 | public static IEnumerable ValidRangePairs() 6 | { 7 | yield return new object[] { 0..0, Enumerable.Range(0, 0) }; 8 | yield return new object[] { 100..100, Enumerable.Range(100, 0) }; 9 | yield return new object[] { 65537..65537, Enumerable.Range(65537, 0) }; 10 | yield return new object[] { 0..1, Enumerable.Range(0, 1) }; 11 | yield return new object[] { 0..10, Enumerable.Range(0, 10) }; 12 | yield return new object[] { 0..65536, Enumerable.Range(0, 65536) }; 13 | yield return new object[] { 141469..177013, Enumerable.Range(141469, 177013 - 141469) }; 14 | yield return new object[] { (int.MaxValue - 5)..int.MaxValue, Enumerable.Range(int.MaxValue - 5, 5) }; 15 | yield return new object[] { 1..0, Enumerable.Range(0, 1).Reverse() }; 16 | yield return new object[] { 100..0, Enumerable.Range(0, 100).Reverse() }; 17 | yield return new object[] { 65536..0, Enumerable.Range(0, 65536).Reverse() }; 18 | yield return new object[] { int.MaxValue..(int.MaxValue - 5), Enumerable.Range(int.MaxValue - 5, 5).Reverse() }; 19 | } 20 | 21 | public static IEnumerable InvalidRanges() 22 | { 23 | yield return new object[] { 0..^0 }; 24 | yield return new object[] { 0..^10 }; 25 | yield return new object[] { ^5..10 }; 26 | yield return new object[] { ^5..^10 }; 27 | yield return new object[] { ^5..^10 }; 28 | yield return new object[] { 0.. }; 29 | yield return new object[] { 10.. }; 30 | } 31 | 32 | public static IEnumerable EmptyRanges() 33 | { 34 | yield return new object[] { 0..0 }; 35 | yield return new object[] { 1337..1337 }; 36 | yield return new object[] { 100..100 }; 37 | yield return new object[] { 65537..65537 }; 38 | yield return new object[] { int.MaxValue..int.MaxValue }; 39 | } 40 | 41 | public static IEnumerable Numbers(Range range) => 42 | new[] 43 | { 44 | 0, 1, -1, 7, -7, 10, 1001, 178000, int.MinValue, int.MaxValue, 45 | range.Start.Value, range.Start.Value + 1, range.Start.Value - 1, 46 | range.End.Value, range.End.Value + 1, range.End.Value - 1 47 | }; 48 | 49 | public static IEnumerable Indexes(ICollection collection) 50 | { 51 | yield return 0; 52 | yield return 1; 53 | yield return 2; 54 | yield return 9; 55 | yield return 19; 56 | yield return -1; 57 | yield return -2; 58 | yield return collection.Count / 4; 59 | yield return collection.Count / 3; 60 | yield return collection.Count / 2; 61 | yield return collection.Count - 21; 62 | yield return collection.Count - 11; 63 | yield return collection.Count - 1; 64 | yield return collection.Count; 65 | yield return collection.Count + 1; 66 | yield return int.MinValue; 67 | yield return int.MaxValue; 68 | } 69 | 70 | public static IEnumerable ValidIndexes(ICollection collection) => 71 | Indexes(collection) 72 | .Where(index => index >= 0 && index < collection.Count); 73 | 74 | public static IEnumerable InvalidIndexes(ICollection collection) => 75 | Indexes(collection) 76 | .Where(index => index < 0 || index >= collection.Count); 77 | } 78 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/Helpers.cs: -------------------------------------------------------------------------------- 1 | #if !NET48 2 | using System.Buffers; 3 | #endif 4 | 5 | using Xunit.Sdk; 6 | 7 | namespace RangeExtensions.Tests; 8 | 9 | internal static class AssertHelpers 10 | { 11 | public static void EqualValueOrException( 12 | Func expectedValueSource, 13 | Func actualValueSource, 14 | bool allowInherited = false) 15 | { 16 | try 17 | { 18 | Assert.Equal(expectedValueSource(), actualValueSource()); 19 | } 20 | catch (Exception expectedException) when (expectedException is not EqualException) 21 | { 22 | var exceptionType = expectedException.GetType(); 23 | 24 | if (!allowInherited) 25 | { 26 | Assert.Throws(exceptionType, () => expectedValueSource()); 27 | Assert.Throws(exceptionType, () => actualValueSource()); 28 | return; 29 | } 30 | 31 | if (ThrowsSubclassOf(exceptionType, actualValueSource)) 32 | { 33 | return; 34 | } 35 | 36 | throw; 37 | } 38 | } 39 | 40 | public static void EqualSequenceOrException( 41 | Func> expectedValueSource, 42 | Func> actualValueSource, 43 | bool allowInherited = false) 44 | { 45 | try 46 | { 47 | Assert.Equal(expectedValueSource(), actualValueSource()); 48 | } 49 | catch (Exception expectedException) when (expectedException is not EqualException) 50 | { 51 | var exceptionType = expectedException.GetType(); 52 | 53 | if (!allowInherited) 54 | { 55 | Assert.Throws(exceptionType, () => expectedValueSource()); 56 | Assert.Throws(exceptionType, () => actualValueSource()); 57 | return; 58 | } 59 | 60 | if (ThrowsSubclassOf(exceptionType, actualValueSource)) 61 | { 62 | return; 63 | } 64 | 65 | throw; 66 | } 67 | } 68 | 69 | private static bool ThrowsSubclassOf(Type parentType, Func func) 70 | { 71 | try 72 | { 73 | func(); 74 | return false; 75 | } 76 | catch (Exception actualException) 77 | { 78 | return parentType.IsAssignableFrom(actualException.GetType()); 79 | } 80 | } 81 | 82 | public static void Ignore(T _) { } 83 | } 84 | 85 | #if !NET48 86 | internal ref struct Buffer 87 | { 88 | private readonly T[]? _pooled; 89 | 90 | public readonly Span Span; 91 | 92 | public Buffer(Span initialBuffer, int length) 93 | { 94 | if (length > initialBuffer.Length) 95 | { 96 | _pooled = ArrayPool.Shared.Rent(length); 97 | Span = _pooled.AsSpan(..length); 98 | } 99 | else 100 | { 101 | _pooled = default; 102 | Span = initialBuffer[..length]; 103 | } 104 | } 105 | 106 | public static implicit operator Span(Buffer buffer) => buffer.Span; 107 | 108 | public void Dispose() 109 | { 110 | if (_pooled != null) 111 | { 112 | ArrayPool.Shared.Return(_pooled); 113 | } 114 | } 115 | } 116 | 117 | internal static class Buffer 118 | { 119 | public static Buffer Create(Span initialBuffer, int length) => new(initialBuffer, length); 120 | } 121 | #endif 122 | -------------------------------------------------------------------------------- /RangeExtensions/Specialized/WhereRange.SpeedOpt.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions; 2 | 3 | public readonly partial record struct WhereRange 4 | { 5 | public int Aggregate(Func func) 6 | { 7 | ThrowHelpers.CheckNull(func); 8 | ThrowHelpers.CheckEmpty(_start, _end); 9 | 10 | var enumerator = GetEnumerator(); 11 | if (!enumerator.MoveNext()) 12 | { 13 | ThrowHelpers.EmptyRange(); 14 | } 15 | 16 | var result = enumerator.Current; 17 | while (enumerator.MoveNext()) 18 | { 19 | result = func(result, enumerator.Current); 20 | } 21 | 22 | return result; 23 | } 24 | 25 | public TAccumulate Aggregate( 26 | TAccumulate seed, Func func) 27 | { 28 | ThrowHelpers.CheckNull(func); 29 | 30 | var result = seed; 31 | foreach (var element in this) 32 | { 33 | result = func(result, element); 34 | } 35 | 36 | return result; 37 | } 38 | 39 | public int First() 40 | { 41 | ThrowHelpers.CheckEmpty(_start, _end); 42 | 43 | var predicate = _predicate; 44 | foreach (var num in new RangeEnumerable( 45 | start: _start, 46 | end: _end)) 47 | { 48 | if (predicate(num)) 49 | { 50 | return num; 51 | } 52 | } 53 | 54 | return ThrowEmpty(); 55 | } 56 | 57 | public int FirstOrDefault() 58 | { 59 | return FirstOrDefault(default); 60 | } 61 | 62 | public int FirstOrDefault(int defaultValue) 63 | { 64 | if (_start == _end) 65 | { 66 | return defaultValue; 67 | } 68 | 69 | var predicate = _predicate; 70 | foreach (var num in new RangeEnumerable( 71 | start: _start, 72 | end: _end)) 73 | { 74 | if (predicate(num)) 75 | { 76 | return num; 77 | } 78 | } 79 | 80 | return defaultValue; 81 | } 82 | 83 | public int Last() 84 | { 85 | ThrowHelpers.CheckEmpty(_start, _end); 86 | 87 | var predicate = _predicate; 88 | foreach (var num in new RangeEnumerable( 89 | start: _end, 90 | end: _start)) 91 | { 92 | if (predicate(num)) 93 | { 94 | return num; 95 | } 96 | } 97 | 98 | return ThrowEmpty(); 99 | } 100 | 101 | public int LastOrDefault() 102 | { 103 | return LastOrDefault(default); 104 | } 105 | 106 | public int LastOrDefault(int defaultValue) 107 | { 108 | if (_start == _end) 109 | { 110 | return defaultValue; 111 | } 112 | 113 | var predicate = _predicate; 114 | foreach (var num in new RangeEnumerable( 115 | start: _end, 116 | end: _start)) 117 | { 118 | if (predicate(num)) 119 | { 120 | return num; 121 | } 122 | } 123 | 124 | return defaultValue; 125 | } 126 | 127 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 128 | public WhereRange Reverse() 129 | { 130 | return new(_predicate, start: _end, end: _start); 131 | } 132 | 133 | public WhereRange Where(Func predicate) 134 | { 135 | ThrowHelpers.CheckNull(predicate); 136 | 137 | var inner = _predicate; 138 | bool Outer(int i) => inner(i) && predicate(i); 139 | 140 | return new(Outer, _start, _end); 141 | } 142 | 143 | #if NETSTANDARD2_0 144 | [MethodImpl(MethodImplOptions.NoInlining)] 145 | #else 146 | [DoesNotReturn] 147 | #endif 148 | private static int ThrowEmpty() 149 | { 150 | throw new InvalidOperationException("Sequence contains no elements"); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/RangeEnumerable.ICollection.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions.Tests; 2 | 3 | public partial class RangeEnumerableTests 4 | { 5 | [Theory, MemberData(nameof(ValidRangePairs))] 6 | public void Count_MatchesIEnumerableCount(Range range, IEnumerable enumerable) 7 | { 8 | var rangeCount = range.AsEnumerable().Count; 9 | var enumerableCount = enumerable.Count(); 10 | 11 | Assert.Equal(enumerableCount, rangeCount); 12 | } 13 | 14 | [Fact] 15 | public void IsReadOnly_ReturnTrue() 16 | { 17 | var rangeEnumerable = (..100).AsEnumerable(); 18 | 19 | Assert.True(rangeEnumerable.IsReadOnly); 20 | } 21 | 22 | [Fact] 23 | public void Add_ThrowsNotSupportedException() 24 | { 25 | static void Add() 26 | { 27 | (..100).AsEnumerable().Add(10); 28 | } 29 | 30 | Assert.Throws(Add); 31 | } 32 | 33 | [Fact] 34 | public void Clear_ThrowsNotSupportedException() 35 | { 36 | static void Clear() 37 | { 38 | (..100).AsEnumerable().Clear(); 39 | } 40 | 41 | Assert.Throws(Clear); 42 | } 43 | 44 | [Theory, MemberData(nameof(ValidRangePairs))] 45 | public void Contains_MatchesIEnumerableContains(Range range, IEnumerable enumerable) 46 | { 47 | var numbers = Data.Numbers(range); 48 | 49 | var rangeResults = numbers.Select(i => range.AsEnumerable().Contains(i)); 50 | var enumerableResults = numbers.Select(i => enumerable.Contains(i)); 51 | 52 | Assert.Equal(enumerableResults, rangeResults); 53 | } 54 | 55 | [Theory, MemberData(nameof(ValidRangePairs))] 56 | public void CopyTo_MatchesICollectionCopyTo(Range range, IEnumerable enumerable) 57 | { 58 | var rangeEnumerable = range.AsEnumerable(); 59 | var numbersArray = enumerable.ToArray(); 60 | 61 | static int[] CopyCollection(int index, TCollection collection) 62 | where TCollection : ICollection 63 | { 64 | var numbers = new int[collection.Count]; 65 | 66 | collection.CopyTo(numbers, index); 67 | 68 | return numbers; 69 | } 70 | 71 | foreach (var index in Data.Indexes(numbersArray)) 72 | { 73 | AssertHelpers.EqualSequenceOrException( 74 | () => CopyCollection(index, numbersArray), 75 | () => CopyCollection(index, rangeEnumerable), 76 | allowInherited: true); 77 | } 78 | } 79 | 80 | #if !NET48 81 | [Theory, MemberData(nameof(ValidRangePairs))] 82 | public void CopyToSpan_MatchesICollectionCopyTo(Range range, IEnumerable enumerable) 83 | { 84 | var numbersArray = enumerable.ToArray(); 85 | 86 | static int[] CopyCollection(int index, TCollection collection) 87 | where TCollection : ICollection 88 | { 89 | var numbers = new int[collection.Count]; 90 | 91 | collection.CopyTo(numbers, index); 92 | 93 | return numbers; 94 | } 95 | 96 | static int[] CopySpan(int index, RangeEnumerable rangeCollection) 97 | { 98 | var numbers = new int[rangeCollection.Count]; 99 | 100 | rangeCollection.CopyTo(numbers.AsSpan(), index); 101 | 102 | return numbers; 103 | } 104 | 105 | foreach (var index in Data.Indexes(numbersArray)) 106 | { 107 | AssertHelpers.EqualSequenceOrException( 108 | () => CopyCollection(index, numbersArray), 109 | () => CopySpan(index, range), 110 | allowInherited: true); 111 | } 112 | } 113 | #endif 114 | 115 | [Fact] 116 | public void Remove_ThrowsNotSupportedException() 117 | { 118 | static void Remove() 119 | { 120 | _ = (..100).AsEnumerable().Remove(10); 121 | } 122 | 123 | Assert.Throws(Remove); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /RangeExtensions/Specialized/SelectRange.SpeedOpt.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions; 2 | 3 | public readonly partial record struct SelectRange 4 | { 5 | public T Aggregate(Func func) 6 | { 7 | ThrowHelpers.CheckNull(func); 8 | ThrowHelpers.CheckEmpty(_start, _end); 9 | 10 | var enumerator = GetEnumerator(); 11 | enumerator.MoveNext(); 12 | 13 | var result = enumerator.Current; 14 | while (enumerator.MoveNext()) 15 | { 16 | result = func(result, enumerator.Current); 17 | } 18 | 19 | return result; 20 | } 21 | 22 | public TAccumulate Aggregate( 23 | TAccumulate seed, Func func) 24 | { 25 | ThrowHelpers.CheckNull(func); 26 | 27 | var result = seed; 28 | foreach (var element in this) 29 | { 30 | result = func(result, element); 31 | } 32 | 33 | return result; 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public bool Any() 38 | { 39 | return Count > 0; 40 | } 41 | 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public T First() 44 | { 45 | ThrowHelpers.CheckEmpty(_start, _end); 46 | 47 | var num = _start < _end 48 | ? _start 49 | : _start - 1; 50 | 51 | return _selector(num); 52 | } 53 | 54 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 55 | public T? FirstOrDefault() 56 | { 57 | return FirstOrDefault(default!); 58 | } 59 | 60 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 61 | public T FirstOrDefault(T defaultValue) 62 | { 63 | if (_start == _end) 64 | { 65 | return defaultValue; 66 | } 67 | 68 | var num = _start < _end 69 | ? _start 70 | : _start - 1; 71 | 72 | return _selector(num); 73 | } 74 | 75 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 76 | public T Last() 77 | { 78 | ThrowHelpers.CheckEmpty(_start, _end); 79 | 80 | var num = _start < _end 81 | ? _end - 1 82 | : _end; 83 | 84 | return _selector(num); 85 | } 86 | 87 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 88 | public T? LastOrDefault() 89 | { 90 | return LastOrDefault(default!); 91 | } 92 | 93 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 94 | public T LastOrDefault(T defaultValue) 95 | { 96 | if (_start == _end) 97 | { 98 | return defaultValue; 99 | } 100 | 101 | var num = _start < _end 102 | ? _end - 1 103 | : _end; 104 | 105 | return _selector(num); 106 | } 107 | 108 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 109 | public SelectRange Reverse() 110 | { 111 | return new(_selector, start: _end, end: _start); 112 | } 113 | 114 | public SelectRange Select(Func selector) 115 | { 116 | ThrowHelpers.CheckNull(selector); 117 | 118 | var inner = _selector; 119 | TResult Outer(int i) => selector(inner(i)); 120 | 121 | return new(Outer, _start, _end); 122 | } 123 | 124 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 125 | public SelectRange Skip(int count) 126 | { 127 | if (count >= Count) 128 | { 129 | return new(_selector, 0, 0); 130 | } 131 | 132 | if (count <= 0) 133 | { 134 | return this; 135 | } 136 | 137 | var start = _start <= _end 138 | ? _start + count 139 | : _start - count; 140 | 141 | return new(_selector, start, _end); 142 | } 143 | 144 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 145 | public SelectRange Take(int count) 146 | { 147 | if (count >= Count) 148 | { 149 | return this; 150 | } 151 | 152 | if (count <= 0) 153 | { 154 | return new(_selector, 0, 0); 155 | } 156 | 157 | var end = _start <= _end 158 | ? _start + count 159 | : _start - count; 160 | 161 | return new(_selector, _start, end); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/RangeExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NET48 2 | using System.Buffers; 3 | #endif 4 | 5 | namespace RangeExtensions.Tests; 6 | 7 | public class RangeExtensionsTests 8 | { 9 | public static IEnumerable ValidRangePairs() => Data.ValidRangePairs(); 10 | 11 | [Theory, MemberData(nameof(ValidRangePairs))] 12 | public static void Aggregate_MatchesIEnumerableAggregate(Range range, IEnumerable enumerable) 13 | { 14 | AssertHelpers.EqualValueOrException( 15 | () => enumerable.Aggregate((acc, i) => acc + i), 16 | () => range.Aggregate((acc, i) => acc + i)); 17 | } 18 | 19 | [Theory, MemberData(nameof(ValidRangePairs))] 20 | public static void AggregateWithSeed_MatchesIEnumerableAggregateWithSeed(Range range, IEnumerable enumerable) 21 | { 22 | const int seed = 34; 23 | 24 | var expected = enumerable.Aggregate(seed, (acc, i) => acc + i); 25 | var actual = range.Aggregate(seed, (acc, i) => acc + i); 26 | 27 | Assert.Equal(expected, actual); 28 | } 29 | 30 | [Theory, MemberData(nameof(ValidRangePairs))] 31 | public static void Select_MatchesIEnumerableSelect(Range range, IEnumerable enumerable) 32 | { 33 | var expected = enumerable.Select(i => $"{i * 2}"); 34 | var actual = range.Select(i => $"{i * 2}"); 35 | 36 | Assert.Equal(expected, actual); 37 | } 38 | 39 | [Fact] 40 | public static void Select_ThrowsOnNullSelector() 41 | { 42 | static void SelectWithNull() 43 | { 44 | _ = (0..100).Select((Func)null!); 45 | } 46 | 47 | Assert.Throws(SelectWithNull); 48 | } 49 | 50 | [Theory, MemberData(nameof(ValidRangePairs))] 51 | public static void Where_MatchesIEnumerableWhere(Range range, IEnumerable enumerable) 52 | { 53 | var expected = enumerable.Where(i => i % 7 is 0); 54 | var actual = range.Where(i => i % 7 is 0); 55 | 56 | Assert.Equal(expected, actual); 57 | } 58 | 59 | [Fact] 60 | public static void Where_ThrowsOnNullPredicate() 61 | { 62 | static void WhereWithNull() 63 | { 64 | _ = (0..100).Where(null!); 65 | } 66 | 67 | Assert.Throws(WhereWithNull); 68 | } 69 | 70 | [Theory, MemberData(nameof(ValidRangePairs))] 71 | public void ToArray_MatchesIEnumerableToArray(Range range, IEnumerable enumerable) 72 | { 73 | var rangeToArray = range.ToArray(); 74 | var enumerableToArray = enumerable.ToArray(); 75 | 76 | Assert.Equal(enumerableToArray, rangeToArray); 77 | } 78 | 79 | [Theory, MemberData(nameof(ValidRangePairs))] 80 | public void ToList_MatchesIEnumerableToList(Range range, IEnumerable enumerable) 81 | { 82 | var rangeToList = range.ToList(); 83 | var enumerableToList = enumerable.ToList(); 84 | 85 | Assert.Equal(enumerableToList, rangeToList); 86 | } 87 | 88 | #if !NET48 89 | [Theory, MemberData(nameof(ValidRangePairs))] 90 | public void CopyToSpan_MatchesIEnumerableToArray(Range range, IEnumerable enumerable) 91 | { 92 | var enumerableArray = enumerable.ToArray(); 93 | using var rangeBuffer = Buffer.Create(stackalloc int[1024], enumerableArray.Length); 94 | 95 | range.CopyTo(rangeBuffer); 96 | 97 | Assert.True(rangeBuffer.Span.SequenceEqual(enumerableArray)); 98 | } 99 | 100 | [Theory, MemberData(nameof(ValidRangePairs))] 101 | public void CopyToSpan_DoesNotRunPastRangeCount(Range range, IEnumerable enumerable) 102 | { 103 | var enumerableArray = enumerable.Concat(Enumerable.Repeat(0, 10)).ToArray(); 104 | using var rangeBuffer = Buffer.Create(stackalloc int[1024], enumerableArray.Length); 105 | 106 | rangeBuffer.Span.Clear(); 107 | range.CopyTo(rangeBuffer); 108 | 109 | Assert.True(rangeBuffer.Span.SequenceEqual(enumerableArray)); 110 | } 111 | 112 | [Fact] 113 | public void CopyToSpan_ThrowsOnRangeLongerThanSpan() 114 | { 115 | static void CopyToSpan() 116 | { 117 | var span = (stackalloc int[128]); 118 | 119 | (..129).CopyTo(span); 120 | } 121 | 122 | Assert.Throws(CopyToSpan); 123 | } 124 | 125 | [Theory, MemberData(nameof(ValidRangePairs))] 126 | public void TryCopyToSpan_MatchesIEnumerableToArray(Range range, IEnumerable enumerable) 127 | { 128 | var enumerableArray = enumerable.ToArray(); 129 | if (enumerableArray.Length > 0) 130 | { 131 | using var insufficientBuffer = Buffer.Create(stackalloc int[1024], enumerableArray.Length - 1); 132 | 133 | Assert.False(range.TryCopyTo(insufficientBuffer)); 134 | } 135 | 136 | using var rangeBuffer = Buffer.Create(stackalloc int[1024], enumerableArray.Length); 137 | 138 | Assert.True(range.TryCopyTo(rangeBuffer)); 139 | Assert.True(rangeBuffer.Span.SequenceEqual(enumerableArray)); 140 | } 141 | #endif 142 | } 143 | -------------------------------------------------------------------------------- /RangeExtensions/Specialized/SelectRange.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RangeExtensions; 5 | 6 | [StructLayout(LayoutKind.Auto)] 7 | public readonly partial record struct SelectRange : IList 8 | { 9 | private readonly Func _selector; 10 | private readonly int _start; 11 | private readonly int _end; 12 | 13 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 14 | internal SelectRange(Func selector, int start, int end) 15 | { 16 | ThrowHelpers.CheckNull(selector); 17 | 18 | _selector = selector; 19 | _start = start; 20 | _end = end; 21 | } 22 | 23 | public int Count 24 | { 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | get => _start < _end 27 | ? _end - _start 28 | : _start - _end; 29 | } 30 | 31 | public bool IsReadOnly 32 | { 33 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 34 | get => true; 35 | } 36 | 37 | public T this[int index] 38 | { 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | get => _selector(new RangeEnumerable(_start, _end)[index]); 41 | set => throw new NotSupportedException(); 42 | } 43 | 44 | public bool Contains(T item) 45 | { 46 | foreach (var i in new RangeEnumerable(_start, _end)) 47 | { 48 | if (EqualityComparer.Default.Equals(_selector(i), item)) 49 | { 50 | return true; 51 | } 52 | } 53 | 54 | return false; 55 | } 56 | 57 | public void CopyTo(T[] array, int index) 58 | { 59 | if (index < 0 || index > array.Length) 60 | { 61 | ThrowHelpers.ArgumentOutOfRange(); 62 | } 63 | 64 | var count = Count; 65 | if (count > (array.Length - index)) 66 | { 67 | ThrowHelpers.ArgumentException(); 68 | } 69 | 70 | var enumerator = new RangeEnumerable.Enumerator(_start, _end); 71 | for (var i = index; i < count; i++) 72 | { 73 | enumerator.MoveNext(); 74 | array[i] = _selector(enumerator.Current); 75 | } 76 | } 77 | 78 | public int IndexOf(T item) 79 | { 80 | var index = 0; 81 | foreach (var i in new RangeEnumerable(_start, _end)) 82 | { 83 | if (EqualityComparer.Default.Equals(_selector(i), item)) 84 | { 85 | return index; 86 | } 87 | 88 | index++; 89 | } 90 | 91 | return -1; 92 | } 93 | 94 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 95 | public Enumerator GetEnumerator() 96 | { 97 | return new(_selector, _start, _end); 98 | } 99 | 100 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 101 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 102 | 103 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 104 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 105 | 106 | [StructLayout(LayoutKind.Auto)] 107 | public record struct Enumerator : IEnumerator 108 | { 109 | private readonly Func _selector; 110 | private readonly int _shift; 111 | private readonly int _end; 112 | 113 | private int _current; 114 | 115 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 116 | internal Enumerator(Func selector, int start, int end) 117 | { 118 | _selector = selector; 119 | 120 | if (start < end) 121 | { 122 | _shift = 1; 123 | _current = start - 1; 124 | _end = end; 125 | } 126 | else 127 | { 128 | _shift = -1; 129 | _current = start; 130 | _end = end - 1; 131 | } 132 | } 133 | 134 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 135 | public bool MoveNext() 136 | { 137 | return (_current += _shift) != _end; 138 | } 139 | 140 | public T Current 141 | { 142 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 143 | get => _selector(_current); 144 | } 145 | 146 | object? IEnumerator.Current => _selector(_current); 147 | 148 | public void Dispose() { } 149 | 150 | public void Reset() 151 | { 152 | throw new NotSupportedException(); 153 | } 154 | } 155 | 156 | public void Add(T item) 157 | { 158 | throw new NotSupportedException(); 159 | } 160 | 161 | public void Clear() 162 | { 163 | throw new NotSupportedException(); 164 | } 165 | 166 | public void Insert(int index, T item) 167 | { 168 | throw new NotSupportedException(); 169 | } 170 | 171 | public bool Remove(T item) 172 | { 173 | throw new NotSupportedException(); 174 | } 175 | 176 | public void RemoveAt(int index) 177 | { 178 | throw new NotSupportedException(); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /RangeExtensions/RangeEnumerable.ICollection.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Numerics; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace System.Linq; 6 | 7 | public readonly partial record struct RangeEnumerable : ICollection 8 | { 9 | public int Count 10 | { 11 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 12 | get => _start < _end 13 | ? _end - _start 14 | : _start - _end; 15 | } 16 | 17 | public bool IsReadOnly 18 | { 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | get => true; 21 | } 22 | 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public bool Contains(int item) 25 | { 26 | if (Count is 0) 27 | { 28 | return false; 29 | } 30 | 31 | var (min, max) = MinAndMaxUnchecked(); 32 | 33 | return item >= min && item <= max; 34 | } 35 | 36 | public void CopyTo(int[] array, int index) 37 | { 38 | #if !NETSTANDARD2_0 39 | CopyTo(array.AsSpan(), index); 40 | #else 41 | if (index < 0 || index > array.Length) 42 | { 43 | ThrowHelpers.ArgumentOutOfRange(); 44 | } 45 | 46 | var count = Count; 47 | if (count > (array.Length - index)) 48 | { 49 | ThrowHelpers.ArgumentException(); 50 | } 51 | 52 | if (array.Length is 0 || count is 0) 53 | { 54 | return; 55 | } 56 | 57 | var i = index; 58 | foreach (var num in this) 59 | { 60 | array[i] = num; 61 | i++; 62 | } 63 | #endif 64 | } 65 | 66 | #if !NETSTANDARD2_0 67 | public void CopyTo(Span span, int index) 68 | { 69 | if (index < 0 || index > span.Length) 70 | { 71 | ThrowHelpers.ArgumentOutOfRange(); 72 | } 73 | 74 | var count = Count; 75 | if (count > (span.Length - index)) 76 | { 77 | ThrowHelpers.ArgumentException(); 78 | } 79 | 80 | if ((span = span.Slice(index, count)).Length is 0) 81 | { 82 | return; 83 | } 84 | 85 | #if NETCOREAPP3_1 || NET 86 | InitializeSpan(span); 87 | #else 88 | var enumerator = GetEnumerator(); 89 | for (var i = 0; i < span.Length; i++) 90 | { 91 | enumerator.MoveNext(); 92 | span[i] = enumerator.Current; 93 | } 94 | #endif 95 | } 96 | #endif 97 | 98 | public void Add(int item) 99 | { 100 | throw new NotSupportedException(); 101 | } 102 | 103 | public void Clear() 104 | { 105 | throw new NotSupportedException(); 106 | } 107 | 108 | public bool Remove(int item) 109 | { 110 | throw new NotSupportedException(); 111 | } 112 | 113 | #if NETCOREAPP3_1 || NET 114 | /// 115 | /// Contract: destination length must be greater than 0 and equal to .Count 116 | /// 117 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 118 | private void InitializeSpan(Span destination) 119 | { 120 | Debug.Assert(_start != _end); 121 | Debug.Assert(destination.Length != 0 && destination.Length == Count); 122 | 123 | if (destination.Length < Vector.Count * 2) 124 | { 125 | // The caller *must* guarantee that destination length can fit the range 126 | ref var pos = ref MemoryMarshal.GetReference(destination); 127 | foreach (var num in this) 128 | { 129 | pos = num; 130 | pos = ref Unsafe.Add(ref pos, 1); 131 | } 132 | } 133 | else 134 | { 135 | InitializeSpanCore(destination); 136 | } 137 | } 138 | 139 | private void InitializeSpanCore(Span destination) 140 | { 141 | var (direction, start) = _start < _end 142 | ? (1, _start) 143 | : (-1, _start - 1); 144 | 145 | var width = Vector.Count; 146 | var stride = Vector.Count * 2; 147 | var remainder = destination.Length % stride; 148 | 149 | var initMask = Unsafe.ReadUnaligned>( 150 | ref Unsafe.As(ref MemoryMarshal.GetReference( 151 | (ReadOnlySpan)new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }))); 152 | 153 | var mask = new Vector(stride) * direction; 154 | var value = new Vector(start) + (initMask * direction); 155 | var value2 = value + (new Vector(width) * direction); 156 | 157 | ref var pos = ref MemoryMarshal.GetReference(destination); 158 | ref var limit = ref Unsafe.Add(ref pos, destination.Length - remainder); 159 | while (!Unsafe.AreSame(ref pos, ref limit)) 160 | { 161 | Unsafe.WriteUnaligned(ref ByteRef(ref pos), value); 162 | Unsafe.WriteUnaligned(ref ByteRef(ref Unsafe.Add(ref pos, width)), value2); 163 | 164 | value += mask; 165 | value2 += mask; 166 | pos = ref Unsafe.Add(ref pos, stride); 167 | } 168 | 169 | var num = start + ((destination.Length - remainder) * direction); 170 | limit = ref Unsafe.Add(ref limit, remainder); 171 | while (!Unsafe.AreSame(ref pos, ref limit)) 172 | { 173 | pos = num; 174 | pos = ref Unsafe.Add(ref pos, 1); 175 | num += direction; 176 | } 177 | } 178 | 179 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 180 | private static ref byte ByteRef(ref T source) => ref Unsafe.As(ref source); 181 | #endif 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RangeExtensions 2 | [![CI/CD](https://github.com/neon-sunset/RangeExtensions/actions/workflows/dotnet-releaser.yml/badge.svg)](https://github.com/neon-sunset/RangeExtensions/actions/workflows/dotnet-releaser.yml) [![nuget](https://badgen.net/nuget/v/RangeExtensions/latest)](https://www.nuget.org/packages/RangeExtensions/) [![Coverage Status](https://coveralls.io/repos/github/neon-sunset/RangeExtensions/badge.svg)](https://coveralls.io/github/neon-sunset/RangeExtensions) 3 | 4 | This package enables the usage of `System.Range` in `foreach` expressions and provides extensions to integrate it with LINQ as a faster replacement to `Enumerable.Range`. 5 | 6 | - Correctness is verified against `IEnumerable` and `Enumerable.Range` behavior; 7 | - Implementation tries its best to make abstractions either zero-cost or reasonably close to that. For critical paths, performance is tuned to be allocation-free and on par with regular `for` loops 8 | 9 | ## Features 10 | ### Range enumeration 11 | ```cs 12 | foreach (var i in ..100) // you can write 0..Length as just ..Length 13 | { 14 | Console.WriteLine(i); 15 | } 16 | ``` 17 | 18 | ### Reverse range enumeration 19 | ```cs 20 | for (var i = 100 - 1; i >= 0; i--) 21 | { 22 | Console.WriteLine(i); 23 | } 24 | 25 | // Can be written as 26 | foreach (var i in 100..0) 27 | { 28 | Console.WriteLine(i); 29 | } 30 | ``` 31 | 32 | ### Select and Where 33 | ```cs 34 | var floats = (0..100).Select(i => (float)i); 35 | var odd = (0..100).Where(i => i % 2 != 0); 36 | 37 | var randomNumbers = (0..1000) 38 | .Select(_ => Random.Shared.Next()) 39 | .ToArray(); 40 | ``` 41 | 42 | ### Collecting to array or list 43 | ```cs 44 | var numbers = (0..100).ToArray(); 45 | ``` 46 | 47 | ### Aggregate 48 | ```cs 49 | var digits = (0..10) 50 | .Aggregate(new StringBuilder(), (sb, i) => sb.Append(i)) 51 | .ToString(); 52 | 53 | Assert.Equal("0123456789", digits); 54 | ``` 55 | 56 | ### Other LINQ specializations 57 | ```cs 58 | var enumerable = (..100).AsEnumerable(); 59 | 60 | var sum = enumerable.Sum(); 61 | var count = enumerable.Count; 62 | var average = enumerable.Average(); 63 | var firstTen = enumerable.Take(10); 64 | var reversed = enumerable.Reverse(); 65 | // and others 66 | ``` 67 | 68 | ## Performance 69 | In .NET 7, `foreach (var i in 0..Length)` has the same performance as `for` loop. Otherwise, `RangeExtensions` is 2 to >10 times faster than `Enumerable.Range`. Using DynamicPGO significantly improves the performance of both. 70 | ``` ini 71 | BenchmarkDotNet=v0.13.2, OS=macOS 13.1 (22C65) [Darwin 22.2.0] 72 | Apple M1 Pro, 1 CPU, 8 logical and 8 physical cores 73 | .NET SDK=8.0.100-alpha.1.22620.11 74 | [Host] : .NET 7.0.1 (7.0.122.56804), Arm64 RyuJIT AdvSIMD 75 | DefaultJob : .NET 7.0.1 (7.0.122.56804), Arm64 RyuJIT AdvSIMD 76 | 77 | Job=ShortRun IterationCount=3 LaunchCount=1 78 | WarmupCount=3 79 | 80 | DOTNET_TieredPGO=1 81 | DOTNET_ReadyToRun=0 82 | ``` 83 | | Method | Length | Mean | Error | Ratio | Allocated | 84 | |---------------------- |------- |----------------:|--------------:|------:|----------:| 85 | | For | 1 | 0.0000 ns | 0.0000 ns | ? | - | 86 | | **Range** | 1 | 0.6531 ns | 0.0051 ns | ? | - | 87 | | EnumerableRange | 1 | 8.1135 ns | 0.0198 ns | ? | 40 B | 88 | | **RangeSelect** | 1 | 1.1588 ns | 0.0048 ns | ? | - | 89 | | EnumerableSelect | 1 | 40.7948 ns | 0.7697 ns | ? | 88 B | 90 | | **RangeSelectTwice** | 1 | 19.4165 ns | 0.0480 ns | ? | 96 B | 91 | | EnumerableSelectTwice | 1 | 43.6399 ns | 0.0908 ns | ? | 232 B | 92 | | **RangeWhere** | 1 | 1.3954 ns | 0.0036 ns | ? | - | 93 | | EnumerableWhere | 1 | 26.1945 ns | 0.0534 ns | ? | 96 B | 94 | | | | | | | | 95 | | For | 100 | 36.1897 ns | 0.0618 ns | 1.00 | - | 96 | | **Range** | 100 | 36.9244 ns | 2.0789 ns | 1.00 | - | 97 | | EnumerableRange | 100 | 211.4774 ns | 0.6430 ns | 5.85 | 40 B | 98 | | **RangeSelect** | 100 | 42.6852 ns | 0.1689 ns | 1.18 | - | 99 | | EnumerableSelect | 100 | 235.7174 ns | 0.5161 ns | 6.51 | 88 B | 100 | | **RangeSelectTwice** | 100 | 110.1340 ns | 0.1667 ns | 3.04 | 96 B | 101 | | EnumerableSelectTwice | 100 | 298.2976 ns | 2.7831 ns | 8.22 | 232 B | 102 | | **RangeWhere** | 100 | 56.1455 ns | 0.1701 ns | 1.55 | - | 103 | | EnumerableWhere | 100 | 249.1264 ns | 1.5890 ns | 6.89 | 96 B | 104 | | | | | | | | 105 | | For | 100000 | 31,167.5682 ns | 37.3266 ns | 1.00 | - | 106 | | **Range** | 100000 | 31,173.8688 ns | 13.0666 ns | 1.00 | - | 107 | | EnumerableRange | 100000 | 212,925.2827 ns | 156.8182 ns | 6.83 | 40 B | 108 | | **RangeSelect** | 100000 | 50,086.3342 ns | 39.0657 ns | 1.61 | - | 109 | | EnumerableSelect | 100000 | 204,113.5813 ns | 100.0221 ns | 6.55 | 88 B | 110 | | **RangeSelectTwice** | 100000 | 94,302.1444 ns | 230.6254 ns | 3.02 | 96 B | 111 | | EnumerableSelectTwice | 100000 | 203,946.9247 ns | 908.5243 ns | 6.56 | 232 B | 112 | | **RangeWhere** | 100000 | 47,165.0569 ns | 36.7208 ns | 1.51 | - | 113 | | EnumerableWhere | 100000 | 209,918.1519 ns | 3,298.4418 ns | 6.76 | 96 B | 114 | 115 | More details on performance: [Releases](https://github.com/neon-sunset/RangeExtensions/releases) tab contains notes on improvements introduced with each version. 116 | -------------------------------------------------------------------------------- /RangeExtensions/RangeEnumerable.SpeedOpt.cs: -------------------------------------------------------------------------------- 1 | namespace System.Linq; 2 | 3 | public readonly partial record struct RangeEnumerable 4 | { 5 | public int Aggregate(Func func) 6 | { 7 | ThrowHelpers.CheckNull(func); 8 | ThrowHelpers.CheckEmpty(this); 9 | 10 | var enumerator = GetEnumeratorUnchecked(); 11 | enumerator.MoveNext(); 12 | 13 | var result = enumerator.Current; 14 | while (enumerator.MoveNext()) 15 | { 16 | result = func(result, enumerator.Current); 17 | } 18 | 19 | return result; 20 | } 21 | 22 | public TAccumulate Aggregate( 23 | TAccumulate seed, Func func) 24 | { 25 | ThrowHelpers.CheckNull(func); 26 | 27 | var result = seed; 28 | foreach (var element in this) 29 | { 30 | result = func(result, element); 31 | } 32 | 33 | return result; 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public bool Any() 38 | { 39 | return Count > 0; 40 | } 41 | 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public double Average() 44 | { 45 | ThrowHelpers.CheckEmpty(this); 46 | 47 | var (first, last) = FirstAndLastUnchecked(); 48 | 49 | return ((double)first + last) / 2; 50 | } 51 | 52 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 53 | public RangeEnumerable Distinct() 54 | { 55 | return this; 56 | } 57 | 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 59 | public int First() 60 | { 61 | ThrowHelpers.CheckEmpty(this); 62 | 63 | return _start < _end 64 | ? _start 65 | : _start - 1; 66 | } 67 | 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 69 | public int Last() 70 | { 71 | ThrowHelpers.CheckEmpty(this); 72 | 73 | return _start < _end 74 | ? _end - 1 75 | : _end; 76 | } 77 | 78 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 79 | public int Max() 80 | { 81 | ThrowHelpers.CheckEmpty(this); 82 | 83 | var (first, last) = FirstAndLastUnchecked(); 84 | 85 | return Math.Max(first, last); 86 | } 87 | 88 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 89 | public int Min() 90 | { 91 | ThrowHelpers.CheckEmpty(this); 92 | 93 | var (first, last) = FirstAndLastUnchecked(); 94 | 95 | return Math.Min(first, last); 96 | } 97 | 98 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 99 | public RangeEnumerable Reverse() 100 | { 101 | return new(start: _end, end: _start); 102 | } 103 | 104 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 105 | public SelectRange Select(Func selector) 106 | { 107 | return new(selector, _start, _end); 108 | } 109 | 110 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 111 | public RangeEnumerable Skip(int count) 112 | { 113 | if (count >= Count) 114 | { 115 | return Empty; 116 | } 117 | 118 | if (count <= 0) 119 | { 120 | return this; 121 | } 122 | 123 | var start = IsAscending() 124 | ? _start + count 125 | : _start - count; 126 | 127 | return new(start, _end); 128 | } 129 | 130 | public int Sum() 131 | { 132 | var (min, max) = MinAndMaxUnchecked(); 133 | if (min <= 1) 134 | { 135 | var longMax = (long)max; 136 | var longSum = longMax * (longMax + 1) / 2; 137 | 138 | return checked((int)longSum); 139 | } 140 | 141 | var sum = 0; 142 | for (var i = min; i <= max; i++) 143 | { 144 | sum = checked(sum + i); 145 | } 146 | 147 | return sum; 148 | } 149 | 150 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 151 | public RangeEnumerable Take(int count) 152 | { 153 | if (count >= Count) 154 | { 155 | return this; 156 | } 157 | 158 | if (count <= 0) 159 | { 160 | return Empty; 161 | } 162 | 163 | var end = IsAscending() 164 | ? _start + count 165 | : _start - count; 166 | 167 | return new(_start, end); 168 | } 169 | 170 | public int[] ToArray() 171 | { 172 | var length = Count; 173 | if (length is 0) 174 | { 175 | return Array.Empty(); 176 | } 177 | 178 | #if NET6_0_OR_GREATER 179 | var array = GC.AllocateUninitializedArray(length); 180 | #else 181 | var array = new int[length]; 182 | #endif 183 | 184 | #if NETCOREAPP3_1 || NET 185 | InitializeSpan(array); 186 | #else 187 | var enumerator = GetEnumeratorUnchecked(); 188 | for (var i = 0; i < array.Length; i++) 189 | { 190 | enumerator.MoveNext(); 191 | array[i] = enumerator.Current; 192 | } 193 | #endif 194 | return array; 195 | } 196 | 197 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 198 | public WhereRange Where(Func predicate) 199 | { 200 | return new(predicate, _start, _end); 201 | } 202 | 203 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 204 | internal void Deconstruct(out int start, out int end) 205 | { 206 | start = _start; 207 | end = _end; 208 | } 209 | 210 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 211 | internal (int, int) MinAndMaxUnchecked() 212 | { 213 | return _start <= _end 214 | ? (_start, _end - 1) 215 | : (_end, _start - 1); 216 | } 217 | 218 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 219 | private (int, int) FirstAndLastUnchecked() 220 | { 221 | return _start <= _end 222 | ? (_start, _end - 1) 223 | : (_start - 1, _end); 224 | } 225 | 226 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 227 | private bool IsAscending() 228 | { 229 | return _start <= _end; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/Specialized/WhereRange.SpeedOpt.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions.Tests; 2 | 3 | public partial class WhereRangeTests 4 | { 5 | public static IEnumerable EmptyRanges() => Data.EmptyRanges(); 6 | 7 | [Theory, MemberData(nameof(ValidRangePairs))] 8 | public static void Aggregate_MatchesIEnumerableAggregate(Range range, IEnumerable enumerable) 9 | { 10 | AssertHelpers.EqualValueOrException( 11 | () => enumerable.Where(i => i % 2 != 0).Aggregate((acc, i) => acc + i), 12 | () => range.Where(i => i % 2 != 0).Aggregate((acc, i) => acc + i)); 13 | } 14 | 15 | [Fact] 16 | public static void Aggregate_ThrowsOnNullDelegate() 17 | { 18 | static void AggregateNull() 19 | { 20 | _ = (0..100).Where(i => i % 2 != 0).Aggregate(null!); 21 | } 22 | 23 | Assert.Throws(AggregateNull); 24 | } 25 | 26 | [Theory, MemberData(nameof(ValidRangePairs))] 27 | public static void AggregateWithSeed_MatchesIEnumerableAggregateWithSeed(Range range, IEnumerable enumerable) 28 | { 29 | const int seed = 34; 30 | 31 | var expected = enumerable.Where(i => i % 2 != 0).Aggregate(seed, (acc, i) => acc + i); 32 | var actual = range.Where(i => i % 2 != 0).Aggregate(seed, (acc, i) => acc + i); 33 | 34 | Assert.Equal(expected, actual); 35 | } 36 | 37 | [Fact] 38 | public static void AggregateWithSeed_ThrowsOnNullDelegate() 39 | { 40 | static void AggregateNull() 41 | { 42 | _ = (0..100).Where(i => i % 2 != 0).Aggregate(34, (Func)null!); 43 | } 44 | 45 | Assert.Throws(AggregateNull); 46 | } 47 | 48 | [Theory, MemberData(nameof(ValidRangePairs))] 49 | public void First_MatchesIEnumerableFirst(Range range, IEnumerable enumerable) 50 | { 51 | var whereRange = range.Where(i => i % 2 != 0); 52 | if (whereRange.Count() is 0) 53 | { 54 | return; 55 | } 56 | 57 | var expected = enumerable.First(i => i % 2 != 0); 58 | var actual = whereRange.First(); 59 | 60 | Assert.Equal(expected, actual); 61 | } 62 | 63 | [Theory, MemberData(nameof(EmptyRanges))] 64 | public void First_ThrowsOnEmptyRange(Range range) 65 | { 66 | void First() 67 | { 68 | _ = range.Where(i => i % 2 != 0).First(); 69 | } 70 | 71 | Assert.Throws(First); 72 | } 73 | 74 | [Theory, MemberData(nameof(ValidRangePairs))] 75 | public void First_ThrowsOnNoResults(Range range, IEnumerable enumerable) 76 | { 77 | AssertHelpers.Ignore(enumerable); 78 | 79 | void Never() 80 | { 81 | range.Where(_ => false).First(); 82 | } 83 | 84 | Assert.Throws(Never); 85 | } 86 | 87 | [Theory, MemberData(nameof(ValidRangePairs))] 88 | public void FirstOrDefault_MatchesIEnumerableFirstOrDefault(Range range, IEnumerable enumerable) 89 | { 90 | var expected = enumerable.Where(i => i % 2 != 0); 91 | var actual = range.Where(i => i % 2 != 0); 92 | 93 | Assert.Equal(expected.FirstOrDefault(), actual.FirstOrDefault()); 94 | #if NET6_0_OR_GREATER 95 | Assert.Equal(expected.FirstOrDefault(42), actual.FirstOrDefault(42)); 96 | #endif 97 | } 98 | 99 | [Theory, MemberData(nameof(ValidRangePairs))] 100 | public void Last_MatchesIEnumerableLast(Range range, IEnumerable enumerable) 101 | { 102 | var whereRange = range.Where(i => i % 2 != 0); 103 | if (whereRange.Count() is 0) 104 | { 105 | return; 106 | } 107 | 108 | var expected = enumerable.Last(i => i % 2 != 0); 109 | var actual = whereRange.Last(); 110 | 111 | Assert.Equal(expected, actual); 112 | } 113 | 114 | [Theory, MemberData(nameof(EmptyRanges))] 115 | public void Last_ThrowsOnEmptyRange(Range range) 116 | { 117 | void Last() 118 | { 119 | _ = range.Where(i => i % 2 != 0).Last(); 120 | } 121 | 122 | Assert.Throws(Last); 123 | } 124 | 125 | [Theory, MemberData(nameof(ValidRangePairs))] 126 | public void Last_ThrowsOnNoResults(Range range, IEnumerable enumerable) 127 | { 128 | AssertHelpers.Ignore(enumerable); 129 | 130 | void Never() 131 | { 132 | range.Where(_ => false).Last(); 133 | } 134 | 135 | Assert.Throws(Never); 136 | } 137 | 138 | [Theory, MemberData(nameof(ValidRangePairs))] 139 | public void LastOrDefault_MatchesIEnumerableLastOrDefault(Range range, IEnumerable enumerable) 140 | { 141 | var expected = enumerable.Where(i => i % 2 != 0); 142 | var actual = range.Where(i => i % 2 != 0); 143 | 144 | Assert.Equal(expected.LastOrDefault(), actual.LastOrDefault()); 145 | #if NET6_0_OR_GREATER 146 | Assert.Equal(expected.LastOrDefault(42), actual.LastOrDefault(42)); 147 | #endif 148 | } 149 | 150 | [Theory, MemberData(nameof(ValidRangePairs))] 151 | public void Reverse_MatchesIEnumerableReverse(Range range, IEnumerable enumerable) 152 | { 153 | var expected = enumerable.Where(i => i % 2 != 0).Reverse(); 154 | var actual = range.Where(i => i % 2 != 0).Reverse(); 155 | 156 | Assert.Equal(expected, actual); 157 | } 158 | 159 | [Theory, MemberData(nameof(ValidRangePairs))] 160 | public void WhereTwice_MatchesIEnumerableWhereTwice(Range range, IEnumerable enumerable) 161 | { 162 | var expected = enumerable 163 | .Where(i => 164 | i % 2 is 0 && 165 | i % 3 is 0); 166 | 167 | var actual = range 168 | .Where(i => i % 2 is 0) 169 | .Where(i => i % 3 is 0); 170 | 171 | Assert.Equal(expected, actual); 172 | } 173 | 174 | [Fact] 175 | public void WhereTwice_ThrowsOnNullPredicate() 176 | { 177 | static void WhereNull() 178 | { 179 | _ = (0..100) 180 | .Where(i => i % 2 is 0) 181 | .Where(null!); 182 | } 183 | 184 | Assert.Throws(WhereNull); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/Specialized/SelectRange.SpeedOpt.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions.Tests; 2 | 3 | public partial class SelectRangeTests 4 | { 5 | public static IEnumerable EmptyRanges() => Data.EmptyRanges(); 6 | 7 | [Theory, MemberData(nameof(ValidRangePairs))] 8 | public static void Aggregate_MatchesIEnumerableAggregate(Range range, IEnumerable enumerable) 9 | { 10 | AssertHelpers.EqualValueOrException( 11 | () => enumerable.Aggregate((acc, i) => acc + i), 12 | () => range.Select(i => i).Aggregate((acc, i) => acc + i)); 13 | } 14 | 15 | [Fact] 16 | public static void Aggregate_ThrowsOnNullDelegate() 17 | { 18 | static void AggregateNull() 19 | { 20 | _ = (0..100).Select(i => i).Aggregate(null!); 21 | } 22 | 23 | Assert.Throws(AggregateNull); 24 | } 25 | 26 | [Theory, MemberData(nameof(ValidRangePairs))] 27 | public static void AggregateWithSeed_MatchesIEnumerableAggregateWithSeed(Range range, IEnumerable enumerable) 28 | { 29 | const int seed = 34; 30 | 31 | var expected = enumerable.Aggregate(seed, (acc, i) => acc + i); 32 | var actual = range.Select(i => i).Aggregate(seed, (acc, i) => acc + i); 33 | 34 | Assert.Equal(expected, actual); 35 | } 36 | 37 | [Fact] 38 | public static void AggregateWithSeed_ThrowsOnNullDelegate() 39 | { 40 | static void AggregateNull() 41 | { 42 | _ = (0..100).Select(i => i).Aggregate(34, (Func)null!); 43 | } 44 | 45 | Assert.Throws(AggregateNull); 46 | } 47 | 48 | [Theory, MemberData(nameof(ValidRangePairs))] 49 | public void Any_MatchesIEnumerableAny(Range range, IEnumerable enumerable) 50 | { 51 | var expected = enumerable.Any(); 52 | var actual = range.Select(i => i).Any(); 53 | 54 | Assert.Equal(expected, actual); 55 | } 56 | 57 | [Theory, MemberData(nameof(ValidRangePairs))] 58 | public void First_MatchesIEnumerableFirst(Range range, IEnumerable enumerable) 59 | { 60 | var selectRange = range.Select(i => i); 61 | if (selectRange.Count is 0) 62 | { 63 | return; 64 | } 65 | 66 | var expected = enumerable.First(); 67 | var actual = selectRange.First(); 68 | 69 | Assert.Equal(expected, actual); 70 | } 71 | 72 | [Theory, MemberData(nameof(EmptyRanges))] 73 | public void First_ThrowsOnEmptyRange(Range range) 74 | { 75 | void First() 76 | { 77 | _ = range.Select(i => i).First(); 78 | } 79 | 80 | Assert.Throws(First); 81 | } 82 | 83 | [Theory, MemberData(nameof(ValidRangePairs))] 84 | public void FirstOrDefault_MatchesIEnumerableFirstOrDefault(Range range, IEnumerable enumerable) 85 | { 86 | var select = range.Select(i => i); 87 | 88 | Assert.Equal(enumerable.FirstOrDefault(), select.FirstOrDefault()); 89 | #if NET6_0_OR_GREATER 90 | Assert.Equal(enumerable.FirstOrDefault(42), select.FirstOrDefault(42)); 91 | #endif 92 | } 93 | 94 | [Theory, MemberData(nameof(ValidRangePairs))] 95 | public void Last_MatchesIEnumerableLast(Range range, IEnumerable enumerable) 96 | { 97 | var selectRange = range.Select(i => i); 98 | if (selectRange.Count is 0) 99 | { 100 | return; 101 | } 102 | 103 | var expected = enumerable.Last(); 104 | var actual = selectRange.Last(); 105 | 106 | Assert.Equal(expected, actual); 107 | } 108 | 109 | [Theory, MemberData(nameof(EmptyRanges))] 110 | public void Last_ThrowsOnEmptyRange(Range range) 111 | { 112 | void Last() 113 | { 114 | _ = range.Select(i => i).Last(); 115 | } 116 | 117 | Assert.Throws(Last); 118 | } 119 | 120 | [Theory, MemberData(nameof(ValidRangePairs))] 121 | public void LastOrDefault_MatchesIEnumerableLastOrDefault(Range range, IEnumerable enumerable) 122 | { 123 | var select = range.Select(i => i); 124 | 125 | Assert.Equal(enumerable.LastOrDefault(), select.LastOrDefault()); 126 | #if NET6_0_OR_GREATER 127 | Assert.Equal(enumerable.LastOrDefault(42), select.LastOrDefault(42)); 128 | #endif 129 | } 130 | 131 | [Theory, MemberData(nameof(ValidRangePairs))] 132 | public void Reverse_MatchesIEnumerableReverse(Range range, IEnumerable enumerable) 133 | { 134 | var expected = enumerable.Reverse(); 135 | var actual = range.Select(i => i).Reverse(); 136 | 137 | Assert.Equal(expected, actual); 138 | } 139 | 140 | [Theory, MemberData(nameof(ValidRangePairs))] 141 | public void SelectTwice_MatchesIEnumerableSelectTwice(Range range, IEnumerable enumerable) 142 | { 143 | var expected = enumerable 144 | .Select(i => (long)i) 145 | .Select(i => i * 2); 146 | 147 | var actual = range 148 | .Select(i => (long)i) 149 | .Select(i => i * 2); 150 | 151 | Assert.Equal(expected, actual); 152 | } 153 | 154 | [Fact] 155 | public void SelectTwice_ThrowsOnNullSelector() 156 | { 157 | static void SelectNull() 158 | { 159 | _ = (0..100) 160 | .Select(i => i) 161 | .Select((Func)null!); 162 | } 163 | 164 | Assert.Throws(SelectNull); 165 | } 166 | 167 | [Theory, MemberData(nameof(ValidRangePairs))] 168 | public void Skip_MatchesIEnumerableSkip(Range range, IEnumerable enumerable) 169 | { 170 | var numbers = Data.Numbers(range); 171 | 172 | var expected = numbers.Select(i => enumerable.Skip(i)); 173 | var actual = numbers.Select(i => (IEnumerable)range.Select(i => i).Skip(i)); 174 | 175 | Assert.Equal(expected, actual); 176 | } 177 | 178 | [Theory, MemberData(nameof(ValidRangePairs))] 179 | public void Take_MatchesIEnumerableTake(Range range, IEnumerable enumerable) 180 | { 181 | var numbers = Data.Numbers(range); 182 | 183 | var expected = numbers.Select(i => enumerable.Take(i)); 184 | var actual = numbers.Select(i => (IEnumerable)range.Select(i => i).Take(i)); 185 | 186 | Assert.Equal(expected, actual); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/Specialized/SelectRange.cs: -------------------------------------------------------------------------------- 1 | namespace RangeExtensions.Tests; 2 | 3 | public partial class SelectRangeTests 4 | { 5 | public static IEnumerable ValidRangePairs() => Data.ValidRangePairs(); 6 | 7 | [Theory, MemberData(nameof(Data.ValidRangePairs))] 8 | public void Count_MatchesIListCount(Range range, IEnumerable enumerable) 9 | { 10 | var expected = enumerable.Select(i => i).ToList().Count; 11 | var actual = range.Select(i => i).ToList().Count; 12 | 13 | Assert.Equal(expected, actual); 14 | } 15 | 16 | [Fact] 17 | public void IsReadOnly_ReturnsTrue() 18 | { 19 | var selectRange = (0..100).Select(i => i); 20 | 21 | Assert.True(selectRange.IsReadOnly); 22 | } 23 | 24 | [Theory, MemberData(nameof(ValidRangePairs))] 25 | public void IndexOperator_MatchesIListIndexOperator(Range range, IEnumerable enumerable) 26 | { 27 | var expected = enumerable.ToList(); 28 | var actual = range.Select(i => i); 29 | 30 | for (var i = 0; i < expected.Count; i++) 31 | { 32 | Assert.Equal(expected[i], actual[i]); 33 | } 34 | } 35 | 36 | [Theory, MemberData(nameof(ValidRangePairs))] 37 | public void IndexOperator_ThrowsOnOutOfBoundsAccess(Range range, IEnumerable enumerable) 38 | { 39 | var expected = enumerable.ToList(); 40 | var actual = range.Select(i => i); 41 | 42 | foreach (var i in Data.InvalidIndexes(expected)) 43 | { 44 | Assert.Throws(() => expected[i]); 45 | Assert.Throws(() => actual[i]); 46 | } 47 | } 48 | 49 | [Fact] 50 | public void IndexOperator_SetThrowsNotSupportedException() 51 | { 52 | static void SetIndex() 53 | { 54 | (0..100).Select(i => i)[10] = 10; 55 | } 56 | 57 | Assert.Throws(SetIndex); 58 | } 59 | 60 | [Theory, MemberData(nameof(ValidRangePairs))] 61 | public void Contains_MatchesIEnumerableContains(Range range, IEnumerable enumerable) 62 | { 63 | var numbers = Data.Numbers(range); 64 | 65 | var selectRangeResults = numbers.Select(i => range.Select(j => j).Contains(i)); 66 | var enumerableResults = numbers.Select(i => enumerable.Contains(i)); 67 | 68 | Assert.Equal(enumerableResults, selectRangeResults); 69 | } 70 | 71 | [Theory, MemberData(nameof(ValidRangePairs))] 72 | public void CopyTo_MatchesICollectionCopyTo(Range range, IEnumerable enumerable) 73 | { 74 | var selectRangeEnumerable = range.Select(i => i); 75 | var numbersArray = enumerable.ToArray(); 76 | 77 | static int[] CopyCollection(int index, TCollection collection) 78 | where TCollection : ICollection 79 | { 80 | var numbers = new int[collection.Count]; 81 | 82 | collection.CopyTo(numbers, index); 83 | 84 | return numbers; 85 | } 86 | 87 | foreach (var index in Data.Indexes(numbersArray)) 88 | { 89 | AssertHelpers.EqualSequenceOrException( 90 | () => CopyCollection(index, numbersArray), 91 | () => CopyCollection(index, selectRangeEnumerable), 92 | allowInherited: true); 93 | } 94 | } 95 | 96 | [Theory, MemberData(nameof(ValidRangePairs))] 97 | public void IndexOf_MatchesIListIndexOf(Range range, IEnumerable enumerable) 98 | { 99 | var expected = enumerable.ToList(); 100 | var actual = range.Select(i => i); 101 | 102 | foreach (var value in Data.Indexes(expected)) 103 | { 104 | Assert.Equal(expected.IndexOf(value), actual.IndexOf(value)); 105 | } 106 | } 107 | 108 | [Theory, MemberData(nameof(ValidRangePairs))] 109 | public void Enumerator_MatchesStandardEnumerable(Range range, IEnumerable enumerable) 110 | { 111 | IEnumerable Enumerate() 112 | { 113 | foreach (var i in range.Select(i => i)) 114 | { 115 | yield return i; 116 | } 117 | } 118 | 119 | Assert.Equal(enumerable, Enumerate()); 120 | } 121 | 122 | [Theory, MemberData(nameof(ValidRangePairs))] 123 | public void EnumeratorBoxed_MatchesStandardEnumerable(Range range, IEnumerable enumerable) 124 | { 125 | IEnumerable EnumerateBoxed() 126 | { 127 | foreach (var i in (IEnumerable)range.Select(i => i)) 128 | { 129 | yield return i; 130 | } 131 | } 132 | 133 | Assert.Equal(enumerable, EnumerateBoxed()); 134 | } 135 | 136 | [Fact] 137 | public void EnumeratorReset_ThrowsNotSupportedException() 138 | { 139 | static void ResetEnumerator() 140 | { 141 | (0..100).Select(i => i).GetEnumerator().Reset(); 142 | } 143 | 144 | Assert.Throws(ResetEnumerator); 145 | } 146 | 147 | [Fact] 148 | public void Add_ThrowsNotSupportedException() 149 | { 150 | static void Add() 151 | { 152 | (0..100).Select(i => i).Add(10); 153 | } 154 | 155 | Assert.Throws(Add); 156 | } 157 | 158 | [Fact] 159 | public void Clear_ThrowsNotSupportedException() 160 | { 161 | static void Clear() 162 | { 163 | (0..100).Select(i => i).Clear(); 164 | } 165 | 166 | Assert.Throws(Clear); 167 | } 168 | 169 | [Fact] 170 | public void Insert_ThrowsNotSupportedException() 171 | { 172 | static void Insert() 173 | { 174 | (0..100).Select(i => i).Insert(10, 10); 175 | } 176 | 177 | Assert.Throws(Insert); 178 | } 179 | 180 | [Fact] 181 | public void Remove_ThrowsNotSupportedException() 182 | { 183 | static void Remove() 184 | { 185 | (0..100).Select(i => i).Remove(10); 186 | } 187 | 188 | Assert.Throws(Remove); 189 | } 190 | 191 | [Fact] 192 | public void RemoveAt_ThrowsNotSupportedException() 193 | { 194 | static void RemoveAt() 195 | { 196 | (0..100).Select(i => i).RemoveAt(10); 197 | } 198 | 199 | Assert.Throws(RemoveAt); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /RangeExtensions.Tests/RangeEnumerable.SpeedOpt.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace RangeExtensions.Tests; 4 | 5 | public partial class RangeEnumerableTests 6 | { 7 | public static IEnumerable EmptyRanges() => Data.EmptyRanges(); 8 | 9 | [Theory] 10 | [MemberData(nameof(ValidRangePairs))] 11 | public void Any_MatchesIEnumerableAny(Range range, IEnumerable enumerable) 12 | { 13 | var anyRange = range.AsEnumerable().Any(); 14 | var anyEnumerable = enumerable.Any(); 15 | 16 | Assert.Equal(anyEnumerable, anyRange); 17 | } 18 | 19 | [Theory] 20 | [MemberData(nameof(ValidRangePairs))] 21 | public void Average_MatchesIEnumerableAverage(Range range, IEnumerable enumerable) 22 | { 23 | var rangeEnumerable = range.AsEnumerable(); 24 | if (rangeEnumerable.Count is 0) 25 | { 26 | return; 27 | } 28 | 29 | var rangeAverage = rangeEnumerable.Average(); 30 | var enumerableAverage = enumerable.Average(); 31 | 32 | Assert.Equal(enumerableAverage, rangeAverage); 33 | } 34 | 35 | [Theory] 36 | [MemberData(nameof(EmptyRanges))] 37 | public void Average_ThrowsOnEmptyRange(Range range) 38 | { 39 | void Average() 40 | { 41 | _ = range.AsEnumerable().Average(); 42 | } 43 | 44 | Assert.Throws(Average); 45 | } 46 | 47 | [Theory] 48 | [MemberData(nameof(ValidRangePairs))] 49 | public void Distinct_MatchesIEnumerableDistinct(Range range, IEnumerable enumerable) 50 | { 51 | var rangeDistinct = range.AsEnumerable().Distinct(); 52 | var enumerableDistinct = enumerable.Distinct(); 53 | 54 | Assert.Equal(range.AsEnumerable(), rangeDistinct); 55 | Assert.Equal(enumerableDistinct, rangeDistinct); 56 | } 57 | 58 | [Theory] 59 | [MemberData(nameof(ValidRangePairs))] 60 | public void First_MatchesIEnumerableFirst(Range range, IEnumerable enumerable) 61 | { 62 | var rangeEnumerable = range.AsEnumerable(); 63 | if (rangeEnumerable.Count is 0) 64 | { 65 | return; 66 | } 67 | 68 | var rangeFirst = rangeEnumerable.First(); 69 | var enumerableFirst = enumerable.First(); 70 | 71 | Assert.Equal(enumerableFirst, rangeFirst); 72 | } 73 | 74 | [Theory] 75 | [MemberData(nameof(EmptyRanges))] 76 | public void First_ThrowsOnEmptyRange(Range range) 77 | { 78 | void First() 79 | { 80 | _ = range.AsEnumerable().First(); 81 | } 82 | 83 | Assert.Throws(First); 84 | } 85 | 86 | [Theory] 87 | [MemberData(nameof(ValidRangePairs))] 88 | public void Last_MatchesIEnumerableLast(Range range, IEnumerable enumerable) 89 | { 90 | var rangeEnumerable = range.AsEnumerable(); 91 | if (rangeEnumerable.Count is 0) 92 | { 93 | return; 94 | } 95 | 96 | var rangeLast = rangeEnumerable.Last(); 97 | var enumerableLast = enumerable.Last(); 98 | 99 | Assert.Equal(enumerableLast, rangeLast); 100 | } 101 | 102 | [Theory] 103 | [MemberData(nameof(EmptyRanges))] 104 | public void Last_ThrowsOnEmptyRange(Range range) 105 | { 106 | void Last() 107 | { 108 | _ = range.AsEnumerable().Last(); 109 | } 110 | 111 | Assert.Throws(Last); 112 | } 113 | 114 | [Theory] 115 | [MemberData(nameof(ValidRangePairs))] 116 | public void Max_MatchesIEnumerableMax(Range range, IEnumerable enumerable) 117 | { 118 | var rangeEnumerable = range.AsEnumerable(); 119 | if (rangeEnumerable.Count is 0) 120 | { 121 | return; 122 | } 123 | 124 | var rangeMax = rangeEnumerable.Max(); 125 | var enumerableMax = enumerable.Max(); 126 | 127 | Assert.Equal(enumerableMax, rangeMax); 128 | } 129 | 130 | [Theory] 131 | [MemberData(nameof(EmptyRanges))] 132 | public void Max_ThrowsOnEmptyRange(Range range) 133 | { 134 | void Max() 135 | { 136 | _ = range.AsEnumerable().Max(); 137 | } 138 | 139 | Assert.Throws(Max); 140 | } 141 | 142 | [Theory] 143 | [MemberData(nameof(ValidRangePairs))] 144 | public void Min_MatchesIEnumerableMin(Range range, IEnumerable enumerable) 145 | { 146 | var rangeEnumerable = range.AsEnumerable(); 147 | if (rangeEnumerable.Count is 0) 148 | { 149 | return; 150 | } 151 | 152 | var rangeMin = rangeEnumerable.Min(); 153 | var enumerableMin = enumerable.Min(); 154 | 155 | Assert.Equal(enumerableMin, rangeMin); 156 | } 157 | 158 | [Theory] 159 | [MemberData(nameof(EmptyRanges))] 160 | public void Min_ThrowsOnEmptyRange(Range range) 161 | { 162 | void Min() 163 | { 164 | _ = range.AsEnumerable().Min(); 165 | } 166 | 167 | Assert.Throws(Min); 168 | } 169 | 170 | [Theory] 171 | [MemberData(nameof(ValidRangePairs))] 172 | public void Reverse_MatchesIEnumerableReverse(Range range, IEnumerable enumerable) 173 | { 174 | var rangeReverse = range.AsEnumerable().Reverse(); 175 | var enumerableReverse = enumerable.Reverse(); 176 | 177 | Assert.Equal(enumerableReverse, rangeReverse); 178 | } 179 | 180 | [Theory] 181 | [MemberData(nameof(ValidRangePairs))] 182 | public void Skip_MatchesIEnumerableSkip(Range range, IEnumerable enumerable) 183 | { 184 | var numbers = Data.Numbers(range); 185 | 186 | var rangeResults = numbers.Select(i => (IEnumerable)range.AsEnumerable().Skip(i)); 187 | var enumerableResults = numbers.Select(i => enumerable.Skip(i)); 188 | 189 | Assert.Equal(enumerableResults, rangeResults); 190 | } 191 | 192 | [Theory] 193 | [MemberData(nameof(ValidRangePairs))] 194 | public void Sum_MatchesIEnumerableSum(Range range, IEnumerable enumerable) 195 | { 196 | int Sum() => range.AsEnumerable().Sum(); 197 | int SumEnumerable() => enumerable.Sum(); 198 | 199 | AssertHelpers.EqualValueOrException(Sum, SumEnumerable); 200 | } 201 | 202 | [Fact] 203 | public void Sum_ThrowsOnOverflow() 204 | { 205 | static void Sum() 206 | { 207 | _ = (0..65537).AsEnumerable().Sum(); 208 | } 209 | 210 | Assert.Throws(Sum); 211 | } 212 | 213 | [Theory] 214 | [MemberData(nameof(ValidRangePairs))] 215 | public void Take_MatchesIEnumerableTake(Range range, IEnumerable enumerable) 216 | { 217 | var numbers = Data.Numbers(range); 218 | 219 | var rangeResults = numbers.Select(i => (IEnumerable)range.AsEnumerable().Take(i)); 220 | var enumerableResults = numbers.Select(i => enumerable.Take(i)); 221 | 222 | Assert.Equal(enumerableResults, rangeResults); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # Tye 66 | .tye/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.tlog 97 | *.vspscc 98 | *.vssscc 99 | .builds 100 | *.pidb 101 | *.svclog 102 | *.scc 103 | 104 | # Chutzpah Test files 105 | _Chutzpah* 106 | 107 | # Visual C++ cache files 108 | ipch/ 109 | *.aps 110 | *.ncb 111 | *.opendb 112 | *.opensdf 113 | *.sdf 114 | *.cachefile 115 | *.VC.db 116 | *.VC.VC.opendb 117 | 118 | # Visual Studio profiler 119 | *.psess 120 | *.vsp 121 | *.vspx 122 | *.sap 123 | 124 | # Visual Studio Trace Files 125 | *.e2e 126 | 127 | # TFS 2012 Local Workspace 128 | $tf/ 129 | 130 | # Guidance Automation Toolkit 131 | *.gpState 132 | 133 | # ReSharper is a .NET coding add-in 134 | _ReSharper*/ 135 | *.[Rr]e[Ss]harper 136 | *.DotSettings.user 137 | 138 | # TeamCity is a build add-in 139 | _TeamCity* 140 | 141 | # DotCover is a Code Coverage Tool 142 | *.dotCover 143 | 144 | # AxoCover is a Code Coverage Tool 145 | .axoCover/* 146 | !.axoCover/settings.json 147 | 148 | # Coverlet is a free, cross platform Code Coverage Tool 149 | coverage*.json 150 | coverage*.xml 151 | coverage*.info 152 | 153 | # Visual Studio code coverage results 154 | *.coverage 155 | *.coveragexml 156 | 157 | # NCrunch 158 | _NCrunch_* 159 | .*crunch*.local.xml 160 | nCrunchTemp_* 161 | 162 | # MightyMoose 163 | *.mm.* 164 | AutoTest.Net/ 165 | 166 | # Web workbench (sass) 167 | .sass-cache/ 168 | 169 | # Installshield output folder 170 | [Ee]xpress/ 171 | 172 | # DocProject is a documentation generator add-in 173 | DocProject/buildhelp/ 174 | DocProject/Help/*.HxT 175 | DocProject/Help/*.HxC 176 | DocProject/Help/*.hhc 177 | DocProject/Help/*.hhk 178 | DocProject/Help/*.hhp 179 | DocProject/Help/Html2 180 | DocProject/Help/html 181 | 182 | # Click-Once directory 183 | publish/ 184 | 185 | # Publish Web Output 186 | *.[Pp]ublish.xml 187 | *.azurePubxml 188 | # Note: Comment the next line if you want to checkin your web deploy settings, 189 | # but database connection strings (with potential passwords) will be unencrypted 190 | *.pubxml 191 | *.publishproj 192 | 193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 194 | # checkin your Azure Web App publish settings, but sensitive information contained 195 | # in these scripts will be unencrypted 196 | PublishScripts/ 197 | 198 | # NuGet Packages 199 | *.nupkg 200 | # NuGet Symbol Packages 201 | *.snupkg 202 | # The packages folder can be ignored because of Package Restore 203 | **/[Pp]ackages/* 204 | # except build/, which is used as an MSBuild target. 205 | !**/[Pp]ackages/build/ 206 | # Uncomment if necessary however generally it will be regenerated when needed 207 | #!**/[Pp]ackages/repositories.config 208 | # NuGet v3's project.json files produces more ignorable files 209 | *.nuget.props 210 | *.nuget.targets 211 | 212 | # Microsoft Azure Build Output 213 | csx/ 214 | *.build.csdef 215 | 216 | # Microsoft Azure Emulator 217 | ecf/ 218 | rcf/ 219 | 220 | # Windows Store app package directories and files 221 | AppPackages/ 222 | BundleArtifacts/ 223 | Package.StoreAssociation.xml 224 | _pkginfo.txt 225 | *.appx 226 | *.appxbundle 227 | *.appxupload 228 | 229 | # Visual Studio cache files 230 | # files ending in .cache can be ignored 231 | *.[Cc]ache 232 | # but keep track of directories ending in .cache 233 | !?*.[Cc]ache/ 234 | 235 | # Others 236 | ClientBin/ 237 | ~$* 238 | *~ 239 | *.dbmdl 240 | *.dbproj.schemaview 241 | *.jfm 242 | *.pfx 243 | *.publishsettings 244 | orleans.codegen.cs 245 | 246 | # Including strong name files can present a security risk 247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 248 | #*.snk 249 | 250 | # Since there are multiple workflows, uncomment next line to ignore bower_components 251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 252 | #bower_components/ 253 | 254 | # RIA/Silverlight projects 255 | Generated_Code/ 256 | 257 | # Backup & report files from converting an old project file 258 | # to a newer Visual Studio version. Backup files are not needed, 259 | # because we have git ;-) 260 | _UpgradeReport_Files/ 261 | Backup*/ 262 | UpgradeLog*.XML 263 | UpgradeLog*.htm 264 | ServiceFabricBackup/ 265 | *.rptproj.bak 266 | 267 | # SQL Server files 268 | *.mdf 269 | *.ldf 270 | *.ndf 271 | 272 | # Business Intelligence projects 273 | *.rdl.data 274 | *.bim.layout 275 | *.bim_*.settings 276 | *.rptproj.rsuser 277 | *- [Bb]ackup.rdl 278 | *- [Bb]ackup ([0-9]).rdl 279 | *- [Bb]ackup ([0-9][0-9]).rdl 280 | 281 | # Microsoft Fakes 282 | FakesAssemblies/ 283 | 284 | # GhostDoc plugin setting file 285 | *.GhostDoc.xml 286 | 287 | # Node.js Tools for Visual Studio 288 | .ntvs_analysis.dat 289 | node_modules/ 290 | 291 | # Visual Studio 6 build log 292 | *.plg 293 | 294 | # Visual Studio 6 workspace options file 295 | *.opt 296 | 297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 298 | *.vbw 299 | 300 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 301 | *.vbp 302 | 303 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 304 | *.dsw 305 | *.dsp 306 | 307 | # Visual Studio 6 technical files 308 | *.ncb 309 | *.aps 310 | 311 | # Visual Studio LightSwitch build output 312 | **/*.HTMLClient/GeneratedArtifacts 313 | **/*.DesktopClient/GeneratedArtifacts 314 | **/*.DesktopClient/ModelManifest.xml 315 | **/*.Server/GeneratedArtifacts 316 | **/*.Server/ModelManifest.xml 317 | _Pvt_Extensions 318 | 319 | # Paket dependency manager 320 | .paket/paket.exe 321 | paket-files/ 322 | 323 | # FAKE - F# Make 324 | .fake/ 325 | 326 | # CodeRush personal settings 327 | .cr/personal 328 | 329 | # Python Tools for Visual Studio (PTVS) 330 | __pycache__/ 331 | *.pyc 332 | 333 | # Cake - Uncomment if you are using it 334 | # tools/** 335 | # !tools/packages.config 336 | 337 | # Tabs Studio 338 | *.tss 339 | 340 | # Telerik's JustMock configuration file 341 | *.jmconfig 342 | 343 | # BizTalk build output 344 | *.btp.cs 345 | *.btm.cs 346 | *.odx.cs 347 | *.xsd.cs 348 | 349 | # OpenCover UI analysis results 350 | OpenCover/ 351 | 352 | # Azure Stream Analytics local run output 353 | ASALocalRun/ 354 | 355 | # MSBuild Binary and Structured Log 356 | *.binlog 357 | 358 | # NVidia Nsight GPU debugger configuration file 359 | *.nvuser 360 | 361 | # MFractors (Xamarin productivity tool) working folder 362 | .mfractor/ 363 | 364 | # Local History for Visual Studio 365 | .localhistory/ 366 | 367 | # Visual Studio History (VSHistory) files 368 | .vshistory/ 369 | 370 | # BeatPulse healthcheck temp database 371 | healthchecksdb 372 | 373 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 374 | MigrationBackup/ 375 | 376 | # Ionide (cross platform F# VS Code tools) working folder 377 | .ionide/ 378 | 379 | # Fody - auto-generated XML schema 380 | FodyWeavers.xsd 381 | 382 | # VS Code files for those working on multiple tools 383 | .vscode/* 384 | !.vscode/settings.json 385 | !.vscode/tasks.json 386 | !.vscode/launch.json 387 | !.vscode/extensions.json 388 | *.code-workspace 389 | 390 | # Local History for Visual Studio Code 391 | .history/ 392 | 393 | # Windows Installer files from build outputs 394 | *.cab 395 | *.msi 396 | *.msix 397 | *.msm 398 | *.msp 399 | 400 | # JetBrains Rider 401 | *.sln.iml 402 | /.idea 403 | /shelf/ 404 | /workspace.xml 405 | /contentModel.xml 406 | /modules.xml 407 | /projectSettingsUpdater.xml 408 | /httpRequests/ 409 | /dataSources/ 410 | /dataSources.local.xml 411 | 412 | 413 | ## 414 | ## Visual studio for Mac 415 | ## 416 | 417 | 418 | # globs 419 | Makefile.in 420 | *.userprefs 421 | *.usertasks 422 | config.make 423 | config.status 424 | aclocal.m4 425 | install-sh 426 | autom4te.cache/ 427 | *.tar.gz 428 | tarballs/ 429 | test-results/ 430 | 431 | # Mac bundle stuff 432 | *.dmg 433 | *.app 434 | 435 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 436 | # General 437 | .DS_Store 438 | .AppleDouble 439 | .LSOverride 440 | 441 | # Icon must end with two \r 442 | Icon 443 | 444 | 445 | # Thumbnails 446 | ._* 447 | 448 | # Files that might appear in the root of a volume 449 | .DocumentRevisions-V100 450 | .fseventsd 451 | .Spotlight-V100 452 | .TemporaryItems 453 | .Trashes 454 | .VolumeIcon.icns 455 | .com.apple.timemachine.donotpresent 456 | 457 | # Directories potentially created on remote AFP share 458 | .AppleDB 459 | .AppleDesktop 460 | Network Trash Folder 461 | Temporary Items 462 | .apdisk 463 | 464 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 465 | # Windows thumbnail cache files 466 | Thumbs.db 467 | ehthumbs.db 468 | ehthumbs_vista.db 469 | 470 | # Dump file 471 | *.stackdump 472 | 473 | # Folder config file 474 | [Dd]esktop.ini 475 | 476 | # Recycle Bin used on file shares 477 | $RECYCLE.BIN/ 478 | 479 | # Windows Installer files 480 | *.cab 481 | *.msi 482 | *.msix 483 | *.msm 484 | *.msp 485 | 486 | # Windows shortcuts 487 | *.lnk 488 | --------------------------------------------------------------------------------