Minor perf updates for RVD
Porting changes from perf work in https://github.com/aspnet/Routing/pull/788 Includes porting/adding the RVD benchmarks, as well as a new TryAdd method.
This commit is contained in:
parent
02f55e181b
commit
41c4a47680
|
|
@ -1,4 +1,4 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.10
|
||||
MinimumVisualStudioVersion = 15.0.26730.03
|
||||
|
|
@ -70,6 +70,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authen
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Core.Test", "test\Microsoft.AspNetCore.Authentication.Core.Test\Microsoft.AspNetCore.Authentication.Core.Test.csproj", "{A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{5C05BDE0-6339-40BF-8215-97AFA72DCFE1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Performance", "benchmarks\Microsoft.AspNetCore.Http.Performance\Microsoft.AspNetCore.Http.Performance.csproj", "{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -272,6 +276,18 @@ Global
|
|||
{A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -296,6 +312,7 @@ Global
|
|||
{3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852} = {A5A15F1C-885A-452A-A731-B0173DDBD913}
|
||||
{73CA3145-91BD-4DA5-BC74-40008DE7EA98} = {A5A15F1C-885A-452A-A731-B0173DDBD913}
|
||||
{A85950C5-2794-47E2-8EAA-05A1DC7C6DA7} = {F31FF137-390C-49BF-A3BD-7C6ED3597C21}
|
||||
{4633ADE6-7A61-44EB-B7CE-0141C009DBAA} = {5C05BDE0-6339-40BF-8215-97AFA72DCFE1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D9A9994D-F09F-4209-861B-4A9036485D1F}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Microsoft.AspNetCore.Http</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Http\Microsoft.AspNetCore.Http.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1 @@
|
|||
[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class RouteValueDictionaryBenchmark
|
||||
{
|
||||
private RouteValueDictionary _arrayValues;
|
||||
private RouteValueDictionary _propertyValues;
|
||||
|
||||
// We modify the route value dictionaries in many of these benchmarks.
|
||||
[IterationSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_arrayValues = new RouteValueDictionary()
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
{ "id", "17" },
|
||||
};
|
||||
_propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary AddSingleItem()
|
||||
{
|
||||
var dictionary = new RouteValueDictionary
|
||||
{
|
||||
{ "action", "Index" }
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary AddThreeItems()
|
||||
{
|
||||
var dictionary = new RouteValueDictionary
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
{ "id", "15" }
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary ConditionalAdd_ContainsKeyAdd()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
|
||||
if (!dictionary.ContainsKey("action"))
|
||||
{
|
||||
dictionary.Add("action", "Index");
|
||||
}
|
||||
|
||||
if (!dictionary.ContainsKey("controller"))
|
||||
{
|
||||
dictionary.Add("controller", "Home");
|
||||
}
|
||||
|
||||
if (!dictionary.ContainsKey("area"))
|
||||
{
|
||||
dictionary.Add("area", "Admin");
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary ConditionalAdd_TryAdd()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
|
||||
dictionary.TryAdd("action", "Index");
|
||||
dictionary.TryAdd("controller", "Home");
|
||||
dictionary.TryAdd("area", "Admin");
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary ForEachThreeItems_Array()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
foreach (var kvp in dictionary)
|
||||
{
|
||||
GC.KeepAlive(kvp.Value);
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary ForEachThreeItems_Properties()
|
||||
{
|
||||
var dictionary = _propertyValues;
|
||||
foreach (var kvp in dictionary)
|
||||
{
|
||||
GC.KeepAlive(kvp.Value);
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary GetThreeItems_Array()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
GC.KeepAlive(dictionary["action"]);
|
||||
GC.KeepAlive(dictionary["controller"]);
|
||||
GC.KeepAlive(dictionary["id"]);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary GetThreeItems_Properties()
|
||||
{
|
||||
var dictionary = _propertyValues;
|
||||
GC.KeepAlive(dictionary["action"]);
|
||||
GC.KeepAlive(dictionary["controller"]);
|
||||
GC.KeepAlive(dictionary["id"]);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary SetSingleItem()
|
||||
{
|
||||
var dictionary = new RouteValueDictionary
|
||||
{
|
||||
["action"] = "Index"
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary SetExistingItem()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
dictionary["action"] = "About";
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary SetThreeItems()
|
||||
{
|
||||
var dictionary = new RouteValueDictionary
|
||||
{
|
||||
["action"] = "Index",
|
||||
["controller"] = "Home",
|
||||
["id"] = "15"
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary TryGetValueThreeItems_Array()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
dictionary.TryGetValue("action", out var action);
|
||||
dictionary.TryGetValue("controller", out var controller);
|
||||
dictionary.TryGetValue("id", out var id);
|
||||
GC.KeepAlive(action);
|
||||
GC.KeepAlive(controller);
|
||||
GC.KeepAlive(id);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary TryGetValueThreeItems_Properties()
|
||||
{
|
||||
var dictionary = _propertyValues;
|
||||
dictionary.TryGetValue("action", out var action);
|
||||
dictionary.TryGetValue("controller", out var controller);
|
||||
dictionary.TryGetValue("id", out var id);
|
||||
GC.KeepAlive(action);
|
||||
GC.KeepAlive(controller);
|
||||
GC.KeepAlive(id);
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Package Versions">
|
||||
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
||||
<InternalAspNetCoreSdkPackageVersion>3.0.0-alpha1-20180907.9</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>3.0.0-alpha1-10419</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-alpha1-10419</MicrosoftAspNetCoreTestingPackageVersion>
|
||||
<MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>3.0.0-alpha1-10419</MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>
|
||||
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>3.0.0-alpha1-10419</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
|
||||
|
|
|
|||
|
|
@ -12,4 +12,8 @@
|
|||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp22PackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableBenchmarkValidation>true</EnableBenchmarkValidation>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -155,9 +155,9 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
ThrowArgumentNullExceptionForKey();
|
||||
}
|
||||
|
||||
object value;
|
||||
|
|
@ -167,9 +167,9 @@ namespace Microsoft.AspNetCore.Routing
|
|||
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
ThrowArgumentNullExceptionForKey();
|
||||
}
|
||||
|
||||
// We're calling this here for the side-effect of converting from properties
|
||||
|
|
@ -177,7 +177,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
// property storage is immutable.
|
||||
EnsureCapacity(_count);
|
||||
|
||||
var index = FindInArray(key);
|
||||
var index = FindIndex(key);
|
||||
if (index < 0)
|
||||
{
|
||||
EnsureCapacity(_count + 1);
|
||||
|
|
@ -255,12 +255,12 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
ThrowArgumentNullExceptionForKey();
|
||||
}
|
||||
|
||||
EnsureCapacity(_count + 1);
|
||||
|
||||
var index = FindInArray(key);
|
||||
var index = FindIndex(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary));
|
||||
|
|
@ -302,7 +302,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
ThrowArgumentNullExceptionForKey();
|
||||
}
|
||||
|
||||
return TryGetValue(key, out var _);
|
||||
|
|
@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
|
||||
EnsureCapacity(Count);
|
||||
|
||||
var index = FindInArray(item.Key);
|
||||
var index = FindIndex(item.Key);
|
||||
var array = _arrayStorage;
|
||||
if (index >= 0 && EqualityComparer<object>.Default.Equals(array[index].Value, item.Value))
|
||||
{
|
||||
|
|
@ -380,7 +380,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
ThrowArgumentNullExceptionForKey();
|
||||
}
|
||||
|
||||
if (Count == 0)
|
||||
|
|
@ -390,7 +390,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
|
||||
EnsureCapacity(Count);
|
||||
|
||||
var index = FindInArray(key);
|
||||
var index = FindIndex(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
_count--;
|
||||
|
|
@ -404,14 +404,54 @@ namespace Microsoft.AspNetCore.Routing
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to the add the provided <paramref name="key"/> and <paramref name="value"/> to the dictionary.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>Returns <c>true</c> if the value was added. Returns <c>false</c> if the key was already present.</returns>
|
||||
public bool TryAdd(string key, object value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
ThrowArgumentNullExceptionForKey();
|
||||
}
|
||||
|
||||
// Since this is an attempt to write to the dictionary, just make it an array if it isn't. If the code
|
||||
// path we're on event tries to write to the dictionary, it will likely get 'upgraded' at some point,
|
||||
// so we do it here to keep the code size and complexity down.
|
||||
EnsureCapacity(Count);
|
||||
|
||||
var index = FindIndex(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureCapacity(Count + 1);
|
||||
_arrayStorage[Count] = new KeyValuePair<string, object>(key, value);
|
||||
_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
ThrowArgumentNullExceptionForKey();
|
||||
}
|
||||
|
||||
if (_propertyStorage == null)
|
||||
{
|
||||
return TryFindItem(key, out value);
|
||||
}
|
||||
|
||||
return TryGetValueSlow(key, out value);
|
||||
}
|
||||
|
||||
private bool TryGetValueSlow(string key, out object value)
|
||||
{
|
||||
if (_propertyStorage != null)
|
||||
{
|
||||
var storage = _propertyStorage;
|
||||
|
|
@ -423,25 +463,17 @@ namespace Microsoft.AspNetCore.Routing
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var array = _arrayStorage;
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = array[i].Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ThrowArgumentNullExceptionForKey()
|
||||
{
|
||||
throw new ArgumentNullException("key");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureCapacity(int capacity)
|
||||
{
|
||||
|
|
@ -456,7 +488,10 @@ namespace Microsoft.AspNetCore.Routing
|
|||
if (_propertyStorage != null)
|
||||
{
|
||||
var storage = _propertyStorage;
|
||||
capacity = Math.Max(storage.Properties.Length, capacity);
|
||||
|
||||
// If we're converting from properties, it's likely due to an 'add' to make sure we have at least
|
||||
// the default amount of space.
|
||||
capacity = Math.Max(DefaultCapacity, Math.Max(storage.Properties.Length, capacity));
|
||||
var array = new KeyValuePair<string, object>[capacity];
|
||||
|
||||
for (var i = 0; i < storage.Properties.Length; i++)
|
||||
|
|
@ -484,10 +519,14 @@ namespace Microsoft.AspNetCore.Routing
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int FindInArray(string key)
|
||||
private int FindIndex(string key)
|
||||
{
|
||||
// Generally the bounds checking here will be elided by the JIT because this will be called
|
||||
// on the same code path as EnsureCapacity.
|
||||
var array = _arrayStorage;
|
||||
for (var i = 0; i < _count; i++)
|
||||
var count = _count;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
@ -498,9 +537,32 @@ namespace Microsoft.AspNetCore.Routing
|
|||
return -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool TryFindItem(string key, out object value)
|
||||
{
|
||||
var array = _arrayStorage;
|
||||
var count = _count;
|
||||
|
||||
// Elide bounds check for indexing.
|
||||
if ((uint)count <= (uint)array.Length)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = array[i].Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
|
||||
{
|
||||
private RouteValueDictionary _dictionary;
|
||||
private readonly RouteValueDictionary _dictionary;
|
||||
private int _index;
|
||||
|
||||
public Enumerator(RouteValueDictionary dictionary)
|
||||
|
|
@ -513,7 +575,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
_dictionary = dictionary;
|
||||
|
||||
Current = default;
|
||||
_index = -1;
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
public KeyValuePair<string, object> Current { get; private set; }
|
||||
|
|
@ -524,22 +586,36 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
}
|
||||
|
||||
// Similar to the design of List<T>.Enumerator - Split into fast path and slow path for inlining friendliness
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++_index < _dictionary.Count)
|
||||
{
|
||||
if (_dictionary._propertyStorage != null)
|
||||
{
|
||||
var storage = _dictionary._propertyStorage;
|
||||
var property = storage.Properties[_index];
|
||||
Current = new KeyValuePair<string, object>(property.Name, property.GetValue(storage.Value));
|
||||
return true;
|
||||
}
|
||||
var dictionary = _dictionary;
|
||||
|
||||
Current = _dictionary._arrayStorage[_index];
|
||||
// The uncommon case is that the propertyStorage is in use
|
||||
if (dictionary._propertyStorage == null && ((uint)_index < (uint)dictionary._count))
|
||||
{
|
||||
Current = dictionary._arrayStorage[_index];
|
||||
_index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return MoveNextRare();
|
||||
}
|
||||
|
||||
private bool MoveNextRare()
|
||||
{
|
||||
var dictionary = _dictionary;
|
||||
if (dictionary._propertyStorage != null && ((uint)_index < (uint)dictionary._count))
|
||||
{
|
||||
var storage = dictionary._propertyStorage;
|
||||
var property = storage.Properties[_index];
|
||||
Current = new KeyValuePair<string, object>(property.Name, property.GetValue(storage.Value));
|
||||
_index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
_index = dictionary._count;
|
||||
Current = default;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -547,7 +623,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
public void Reset()
|
||||
{
|
||||
Current = default;
|
||||
_index = -1;
|
||||
_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -595,4 +671,4 @@ namespace Microsoft.AspNetCore.Routing
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -380,6 +380,19 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexGet_EmptyStringIsAllowed()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
var value = dict[""];
|
||||
|
||||
// Assert
|
||||
Assert.Null(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexGet_EmptyStorage_ReturnsNull()
|
||||
{
|
||||
|
|
@ -486,6 +499,19 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexSet_EmptyStringIsAllowed()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
dict[""] = "foo";
|
||||
|
||||
// Assert
|
||||
Assert.Equal("foo", dict[""]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexSet_EmptyStorage_UpgradesToList()
|
||||
{
|
||||
|
|
@ -747,6 +773,19 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_EmptyStringIsAllowed()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
dict.Add("", "foo");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("foo", dict[""]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_PropertyStorage()
|
||||
{
|
||||
|
|
@ -762,6 +801,14 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
|
||||
kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
|
||||
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
||||
|
||||
// The upgrade from property -> array should make space for at least 4 entries
|
||||
Assert.Collection(
|
||||
dict._arrayStorage,
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("age", 30), kvp),
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -994,6 +1041,19 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsKey_EmptyStringIsAllowed()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
var result = dict.ContainsKey("");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsKey_PropertyStorage_False()
|
||||
{
|
||||
|
|
@ -1206,6 +1266,19 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_EmptyStringIsAllowed()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
var result = dict.Remove("");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_PropertyStorage_Empty()
|
||||
{
|
||||
|
|
@ -1320,6 +1393,132 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_EmptyStringIsAllowed()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
var result = dict.TryAdd("", "foo");
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
// We always 'upgrade' if you are trying to write to the dictionary.
|
||||
[Fact]
|
||||
public void TryAdd_ConvertsPropertyStorage_ToArrayStorage()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary(new { key = "value", });
|
||||
|
||||
// Act
|
||||
var result = dict.TryAdd("key", "value");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(dict._propertyStorage);
|
||||
Assert.Collection(
|
||||
dict._arrayStorage,
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_EmptyStorage_CanAdd()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
var result = dict.TryAdd("key", "value");
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Collection(
|
||||
dict._arrayStorage,
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ArrayStorage_CanAdd()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary()
|
||||
{
|
||||
{ "key0", "value0" },
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = dict.TryAdd("key1", "value1");
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Collection(
|
||||
dict._arrayStorage,
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key0", "value0"), kvp),
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key1", "value1"), kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ArrayStorage_CanAddWithResize()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary()
|
||||
{
|
||||
{ "key0", "value0" },
|
||||
{ "key1", "value1" },
|
||||
{ "key2", "value2" },
|
||||
{ "key3", "value3" },
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = dict.TryAdd("key4", "value4");
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Collection(
|
||||
dict._arrayStorage,
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key0", "value0"), kvp),
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key1", "value1"), kvp),
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key2", "value2"), kvp),
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key3", "value3"), kvp),
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key4", "value4"), kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary()
|
||||
{
|
||||
{ "key0", "value0" },
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = dict.TryAdd("key0", "value1");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Collection(
|
||||
dict._arrayStorage,
|
||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key0", "value0"), kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp),
|
||||
kvp => Assert.Equal(default, kvp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetValue_EmptyStorage()
|
||||
{
|
||||
|
|
@ -1335,6 +1534,20 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
Assert.Null(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetValue_EmptyStringIsAllowed()
|
||||
{
|
||||
// Arrange
|
||||
var dict = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
var result = dict.TryGetValue("", out var value);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetValue_PropertyStorage_False()
|
||||
{
|
||||
|
|
@ -1618,4 +1831,4 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
|||
public string State { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue