diff --git a/src/Microsoft.AspNetCore.Routing/Internal/ArrayBuilder.cs b/src/Microsoft.AspNetCore.Routing/Internal/ArrayBuilder.cs new file mode 100644 index 0000000000..868b3cfc27 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/Internal/ArrayBuilder.cs @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// LICENSING NOTE: This file is from the dotnet corefx repository. +// +// See https://github.com/dotnet/corefx/blob/143df51926f2ad397fef9c9ca7ede88e2721e801/src/Common/src/System/Collections/Generic/ArrayBuilder.cs + + +using System; +using System.Diagnostics; + +namespace Microsoft.AspNetCore.Routing.Internal +{ + /// + /// Helper type for avoiding allocations while building arrays. + /// + /// The element type. + internal struct ArrayBuilder + { + private const int DefaultCapacity = 4; + private const int MaxCoreClrArrayLength = 0x7fefffff; // For byte arrays the limit is slightly larger + + private T[] _array; // Starts out null, initialized on first Add. + private int _count; // Number of items into _array we're using. + + /// + /// Initializes the with a specified capacity. + /// + /// The capacity of the array to allocate. + public ArrayBuilder(int capacity) : this() + { + Debug.Assert(capacity >= 0); + if (capacity > 0) + { + _array = new T[capacity]; + } + } + + /// + /// Gets the number of items this instance can store without re-allocating, + /// or 0 if the backing array is null. + /// + public int Capacity => _array?.Length ?? 0; + + /// Gets the current underlying array. + public T[] Buffer => _array; + + /// + /// Gets the number of items in the array currently in use. + /// + public int Count => _count; + + /// + /// Gets or sets the item at a certain index in the array. + /// + /// The index into the array. + public T this[int index] + { + get + { + Debug.Assert(index >= 0 && index < _count); + return _array[index]; + } + } + + /// + /// Adds an item to the backing array, resizing it if necessary. + /// + /// The item to add. + public void Add(T item) + { + if (_count == Capacity) + { + EnsureCapacity(_count + 1); + } + + UncheckedAdd(item); + } + + /// + /// Gets the first item in this builder. + /// + public T First() + { + Debug.Assert(_count > 0); + return _array[0]; + } + + /// + /// Gets the last item in this builder. + /// + public T Last() + { + Debug.Assert(_count > 0); + return _array[_count - 1]; + } + + /// + /// Creates an array from the contents of this builder. + /// + /// + /// Do not call this method twice on the same builder. + /// + public T[] ToArray() + { + if (_count == 0) + { + return Array.Empty(); + } + + Debug.Assert(_array != null); // Nonzero _count should imply this + + T[] result = _array; + if (_count < result.Length) + { + // Avoid a bit of overhead (method call, some branches, extra codegen) + // which would be incurred by using Array.Resize + result = new T[_count]; + Array.Copy(_array, 0, result, 0, _count); + } + +#if DEBUG + // Try to prevent callers from using the ArrayBuilder after ToArray, if _count != 0. + _count = -1; + _array = null; +#endif + + return result; + } + + /// + /// Adds an item to the backing array, without checking if there is room. + /// + /// The item to add. + /// + /// Use this method if you know there is enough space in the + /// for another item, and you are writing performance-sensitive code. + /// + public void UncheckedAdd(T item) + { + Debug.Assert(_count < Capacity); + + _array[_count++] = item; + } + + private void EnsureCapacity(int minimum) + { + Debug.Assert(minimum > Capacity); + + int capacity = Capacity; + int nextCapacity = capacity == 0 ? DefaultCapacity : 2 * capacity; + + if ((uint)nextCapacity > (uint)MaxCoreClrArrayLength) + { + nextCapacity = Math.Max(capacity + 1, MaxCoreClrArrayLength); + } + + nextCapacity = Math.Max(nextCapacity, minimum); + + T[] next = new T[nextCapacity]; + if (_count > 0) + { + Array.Copy(_array, 0, next, 0, _count); + } + _array = next; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs b/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs index 49860dcb0f..0cb7e48989 100644 --- a/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs +++ b/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs @@ -2,8 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Linq; +using Microsoft.AspNetCore.Routing.Internal; namespace Microsoft.AspNetCore.Routing.Patterns { @@ -77,7 +76,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns currentIndex++; } - var parseResults = ParseConstraints(parameter, parameterName, currentIndex, endIndex); + var parseResults = ParseConstraints(parameter, currentIndex, endIndex); currentIndex = parseResults.CurrentIndex; string defaultValue = null; @@ -91,17 +90,16 @@ namespace Microsoft.AspNetCore.Routing.Patterns parameterName, defaultValue, parameterKind, - parseResults.ParameterPolicies.ToArray(), + parseResults.ParameterPolicies, encodeSlashes); } private static ParameterPolicyParseResults ParseConstraints( string text, - string parameterName, int currentIndex, int endIndex) { - var constraints = new List(); + var constraints = new ArrayBuilder(0); var state = ParseState.Start; var startIndex = currentIndex; do @@ -234,7 +232,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns } while (state != ParseState.End); - return new ParameterPolicyParseResults(currentIndex, constraints); + return new ParameterPolicyParseResults(currentIndex, constraints.ToArray()); } private enum ParseState @@ -249,9 +247,9 @@ namespace Microsoft.AspNetCore.Routing.Patterns { public readonly int CurrentIndex; - public readonly IReadOnlyList ParameterPolicies; + public readonly RoutePatternParameterPolicyReference[] ParameterPolicies; - public ParameterPolicyParseResults(int currentIndex, IReadOnlyList parameterPolicies) + public ParameterPolicyParseResults(int currentIndex, RoutePatternParameterPolicyReference[] parameterPolicies) { CurrentIndex = currentIndex; ParameterPolicies = parameterPolicies;