Optimize PrefixContainer

There's really no need for us to sanitize null. This code handles null
correctly. Additionally, CopyTo should be able to do better than our
hand-written foreach in the worst case, and avoids an enumerator
allocation.

Removed a custom implementation of IndexOfAny.
This commit is contained in:
Ryan Nowak 2016-02-08 15:26:48 -08:00
parent 698502df8c
commit dacebacb90
1 changed files with 21 additions and 56 deletions

View File

@ -14,6 +14,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
/// </summary> /// </summary>
public class PrefixContainer public class PrefixContainer
{ {
private static readonly string[] EmptyArray = new string[0];
private static readonly char[] Delimiters = new char[] { '[', '.' };
private readonly ICollection<string> _originalValues; private readonly ICollection<string> _originalValues;
private readonly string[] _sortedValues; private readonly string[] _sortedValues;
@ -25,8 +28,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
_originalValues = values; _originalValues = values;
_sortedValues = ToArrayWithoutNulls(_originalValues);
Array.Sort(_sortedValues, StringComparer.OrdinalIgnoreCase); if (_originalValues.Count == 0)
{
_sortedValues = EmptyArray;
}
else
{
_sortedValues = new string[_originalValues.Count];
_originalValues.CopyTo(_sortedValues, 0);
Array.Sort(_sortedValues, StringComparer.OrdinalIgnoreCase);
}
} }
public bool ContainsPrefix(string prefix) public bool ContainsPrefix(string prefix)
@ -41,6 +53,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return _sortedValues.Length > 0; // only match empty string when we have some value return _sortedValues.Length > 0; // only match empty string when we have some value
} }
if (_sortedValues.Length == 0)
{
return false;
}
return BinarySearch(prefix) > -1; return BinarySearch(prefix) > -1;
} }
@ -80,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
string key; string key;
string fullName; string fullName;
var delimiterPosition = IndexOfDelimiter(entry, 0); var delimiterPosition = entry.IndexOfAny(Delimiters, 0);
if (delimiterPosition == 0 && entry[0] == '[') if (delimiterPosition == 0 && entry[0] == '[')
{ {
@ -118,7 +135,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
case '.': case '.':
// Handle an entry such as "prefix.key", "prefix.key.property" and "prefix.key[index]". // Handle an entry such as "prefix.key", "prefix.key.property" and "prefix.key[index]".
var delimiterPosition = IndexOfDelimiter(entry, keyPosition); var delimiterPosition = entry.IndexOfAny(Delimiters, keyPosition);
if (delimiterPosition == -1) if (delimiterPosition == -1)
{ {
// Neither '.' nor '[' found later in the name. Use rest of the string. // Neither '.' nor '[' found later in the name. Use rest of the string.
@ -195,58 +212,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
} }
private static int IndexOfDelimiter(string entry, int startIndex)
{
int delimiterPosition;
var bracketPosition = entry.IndexOf('[', startIndex);
var dotPosition = entry.IndexOf('.', startIndex);
if (dotPosition == -1)
{
delimiterPosition = bracketPosition;
}
else if (bracketPosition == -1)
{
delimiterPosition = dotPosition;
}
else
{
delimiterPosition = Math.Min(dotPosition, bracketPosition);
}
return delimiterPosition;
}
/// <summary>
/// Convert an ICollection to an array, removing null values. Fast path for case where
/// there are no null values.
/// </summary>
private static TElement[] ToArrayWithoutNulls<TElement>(ICollection<TElement> collection) where TElement : class
{
Debug.Assert(collection != null);
var result = new TElement[collection.Count];
var count = 0;
foreach (TElement value in collection)
{
if (value != null)
{
result[count] = value;
count++;
}
}
if (count == collection.Count)
{
return result;
}
else
{
var trimmedResult = new TElement[count];
Array.Copy(result, trimmedResult, count);
return trimmedResult;
}
}
private int BinarySearch(string prefix) private int BinarySearch(string prefix)
{ {
var start = 0; var start = 0;