Improve performance and reduce allocations in RouteValueDictionary. (#1062)
This commit is contained in:
parent
1d3521113a
commit
5da68a2bb7
|
|
@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
private RouteValueDictionary _arrayValues;
|
private RouteValueDictionary _arrayValues;
|
||||||
private RouteValueDictionary _propertyValues;
|
private RouteValueDictionary _propertyValues;
|
||||||
|
private RouteValueDictionary _arrayValuesEmpty;
|
||||||
|
|
||||||
// We modify the route value dictionaries in many of these benchmarks.
|
// We modify the route value dictionaries in many of these benchmarks.
|
||||||
[IterationSetup]
|
[IterationSetup]
|
||||||
|
|
@ -21,9 +22,22 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{ "controller", "Home" },
|
{ "controller", "Home" },
|
||||||
{ "id", "17" },
|
{ "id", "17" },
|
||||||
};
|
};
|
||||||
|
_arrayValuesEmpty = new RouteValueDictionary();
|
||||||
_propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
|
_propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Ctor_Values_RouteValueDictionary_EmptyArray()
|
||||||
|
{
|
||||||
|
new RouteValueDictionary(_arrayValuesEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Ctor_Values_RouteValueDictionary_Array()
|
||||||
|
{
|
||||||
|
new RouteValueDictionary(_arrayValues);
|
||||||
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public RouteValueDictionary AddSingleItem()
|
public RouteValueDictionary AddSingleItem()
|
||||||
{
|
{
|
||||||
|
|
@ -47,7 +61,136 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public RouteValueDictionary ConditionalAdd_ContainsKeyAdd()
|
public void ContainsKey_Array_Found()
|
||||||
|
{
|
||||||
|
_arrayValues.ContainsKey("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void ContainsKey_Array_NotFound()
|
||||||
|
{
|
||||||
|
_arrayValues.ContainsKey("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void ContainsKey_Properties_Found()
|
||||||
|
{
|
||||||
|
_propertyValues.ContainsKey("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void ContainsKey_Properties_NotFound()
|
||||||
|
{
|
||||||
|
_propertyValues.ContainsKey("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void TryAdd_Properties_AtCapacity_KeyExists()
|
||||||
|
{
|
||||||
|
var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17", area = "root" });
|
||||||
|
propertyValues.TryAdd("id", "15");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void TryAdd_Properties_AtCapacity_KeyDoesNotExist()
|
||||||
|
{
|
||||||
|
var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17", area = "root" });
|
||||||
|
_propertyValues.TryAdd("name", "Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void TryAdd_Properties_NotAtCapacity_KeyExists()
|
||||||
|
{
|
||||||
|
var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
|
||||||
|
propertyValues.TryAdd("id", "15");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void TryAdd_Properties_NotAtCapacity_KeyDoesNotExist()
|
||||||
|
{
|
||||||
|
var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
|
||||||
|
_propertyValues.TryAdd("name", "Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void TryAdd_Array_AtCapacity_KeyExists()
|
||||||
|
{
|
||||||
|
var arrayValues = new RouteValueDictionary
|
||||||
|
{
|
||||||
|
{ "action", "Index" },
|
||||||
|
{ "controller", "Home" },
|
||||||
|
{ "id", "17" },
|
||||||
|
{ "area", "root" }
|
||||||
|
};
|
||||||
|
arrayValues.TryAdd("id", "15");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void TryAdd_Array_AtCapacity_KeyDoesNotExist()
|
||||||
|
{
|
||||||
|
var arrayValues = new RouteValueDictionary
|
||||||
|
{
|
||||||
|
{ "action", "Index" },
|
||||||
|
{ "controller", "Home" },
|
||||||
|
{ "id", "17" },
|
||||||
|
{ "area", "root" }
|
||||||
|
};
|
||||||
|
arrayValues.TryAdd("name", "Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void TryAdd_Array_NotAtCapacity_KeyExists()
|
||||||
|
{
|
||||||
|
var arrayValues = new RouteValueDictionary
|
||||||
|
{
|
||||||
|
{ "action", "Index" },
|
||||||
|
{ "controller", "Home" },
|
||||||
|
{ "id", "17" }
|
||||||
|
};
|
||||||
|
arrayValues.TryAdd("id", "15");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void TryAdd_Array_NotAtCapacity_KeyDoesNotExist()
|
||||||
|
{
|
||||||
|
var arrayValues = new RouteValueDictionary
|
||||||
|
{
|
||||||
|
{ "action", "Index" },
|
||||||
|
{ "controller", "Home" },
|
||||||
|
{ "id", "17" },
|
||||||
|
};
|
||||||
|
arrayValues.TryAdd("name", "Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void ConditionalAdd_Array()
|
||||||
|
{
|
||||||
|
var arrayValues = new RouteValueDictionary()
|
||||||
|
{
|
||||||
|
{ "action", "Index" },
|
||||||
|
{ "controller", "Home" },
|
||||||
|
{ "id", "17" },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!arrayValues.ContainsKey("name"))
|
||||||
|
{
|
||||||
|
arrayValues.Add("name", "Service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void ConditionalAdd_Properties()
|
||||||
|
{
|
||||||
|
var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
|
||||||
|
|
||||||
|
if (!propertyValues.ContainsKey("name"))
|
||||||
|
{
|
||||||
|
propertyValues.Add("name", "Service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public RouteValueDictionary ConditionalAdd_ContainsKey_Array()
|
||||||
{
|
{
|
||||||
var dictionary = _arrayValues;
|
var dictionary = _arrayValues;
|
||||||
|
|
||||||
|
|
@ -68,7 +211,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
return dictionary;
|
return dictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public RouteValueDictionary ConditionalAdd_TryAdd()
|
public RouteValueDictionary ConditionalAdd_TryAdd()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
/// Only public instance non-index properties are considered.
|
/// Only public instance non-index properties are considered.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public RouteValueDictionary(object values)
|
public RouteValueDictionary(object values)
|
||||||
: this()
|
|
||||||
{
|
{
|
||||||
if (values is RouteValueDictionary dictionary)
|
if (values is RouteValueDictionary dictionary)
|
||||||
{
|
{
|
||||||
|
|
@ -109,20 +108,27 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var other = dictionary._arrayStorage;
|
var count = dictionary._count;
|
||||||
var storage = new KeyValuePair<string, object>[other.Length];
|
if (count > 0)
|
||||||
if (dictionary._count != 0)
|
|
||||||
{
|
{
|
||||||
Array.Copy(other, 0, storage, 0, dictionary._count);
|
var other = dictionary._arrayStorage;
|
||||||
|
var storage = new KeyValuePair<string, object>[count];
|
||||||
|
Array.Copy(other, 0, storage, 0, count);
|
||||||
|
_arrayStorage = storage;
|
||||||
|
_count = count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_arrayStorage = Array.Empty<KeyValuePair<string, object>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
_arrayStorage = storage;
|
|
||||||
_count = dictionary._count;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values is IEnumerable<KeyValuePair<string, object>> keyValueEnumerable)
|
if (values is IEnumerable<KeyValuePair<string, object>> keyValueEnumerable)
|
||||||
{
|
{
|
||||||
|
_arrayStorage = Array.Empty<KeyValuePair<string, object>>();
|
||||||
|
|
||||||
foreach (var kvp in keyValueEnumerable)
|
foreach (var kvp in keyValueEnumerable)
|
||||||
{
|
{
|
||||||
Add(kvp.Key, kvp.Value);
|
Add(kvp.Key, kvp.Value);
|
||||||
|
|
@ -133,6 +139,8 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
if (values is IEnumerable<KeyValuePair<string, string>> stringValueEnumerable)
|
if (values is IEnumerable<KeyValuePair<string, string>> stringValueEnumerable)
|
||||||
{
|
{
|
||||||
|
_arrayStorage = Array.Empty<KeyValuePair<string, object>>();
|
||||||
|
|
||||||
foreach (var kvp in stringValueEnumerable)
|
foreach (var kvp in stringValueEnumerable)
|
||||||
{
|
{
|
||||||
Add(kvp.Key, kvp.Value);
|
Add(kvp.Key, kvp.Value);
|
||||||
|
|
@ -146,7 +154,10 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var storage = new PropertyStorage(values);
|
var storage = new PropertyStorage(values);
|
||||||
_propertyStorage = storage;
|
_propertyStorage = storage;
|
||||||
_count = storage.Properties.Length;
|
_count = storage.Properties.Length;
|
||||||
return;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_arrayStorage = Array.Empty<KeyValuePair<string, object>>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,8 +271,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
EnsureCapacity(_count + 1);
|
EnsureCapacity(_count + 1);
|
||||||
|
|
||||||
var index = FindIndex(key);
|
if (ContainsKeyArray(key))
|
||||||
if (index >= 0)
|
|
||||||
{
|
{
|
||||||
var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary));
|
var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary));
|
||||||
throw new ArgumentException(message, nameof(key));
|
throw new ArgumentException(message, nameof(key));
|
||||||
|
|
@ -305,7 +315,18 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
ThrowArgumentNullExceptionForKey();
|
ThrowArgumentNullExceptionForKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
return TryGetValue(key, out var _);
|
return ContainsKeyCore(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool ContainsKeyCore(string key)
|
||||||
|
{
|
||||||
|
if (_propertyStorage == null)
|
||||||
|
{
|
||||||
|
return ContainsKeyArray(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContainsKeyProperties(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -460,13 +481,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
ThrowArgumentNullExceptionForKey();
|
ThrowArgumentNullExceptionForKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since this is an attempt to write to the dictionary, just make it an array if it isn't. If the code
|
if (ContainsKeyCore(key))
|
||||||
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -603,6 +618,42 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool ContainsKeyArray(string key)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool ContainsKeyProperties(string key)
|
||||||
|
{
|
||||||
|
var properties = _propertyStorage.Properties;
|
||||||
|
for (var i = 0; i < properties.Length; i++)
|
||||||
|
{
|
||||||
|
if (string.Equals(properties[i].Name, key, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
|
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
|
||||||
{
|
{
|
||||||
private readonly RouteValueDictionary _dictionary;
|
private readonly RouteValueDictionary _dictionary;
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(other, dict);
|
Assert.Equal(other, dict);
|
||||||
|
Assert.Single(dict._arrayStorage);
|
||||||
|
Assert.Null(dict._propertyStorage);
|
||||||
|
|
||||||
var storage = Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
var storage = Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
||||||
var otherStorage = Assert.IsType<KeyValuePair<string, object>[]>(other._arrayStorage);
|
var otherStorage = Assert.IsType<KeyValuePair<string, object>[]>(other._arrayStorage);
|
||||||
|
|
@ -68,6 +70,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(other, dict);
|
Assert.Equal(other, dict);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
|
|
||||||
var storage = dict._propertyStorage;
|
var storage = dict._propertyStorage;
|
||||||
var otherStorage = other._propertyStorage;
|
var otherStorage = other._propertyStorage;
|
||||||
|
|
@ -259,6 +262,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
Assert.Empty(dict);
|
Assert.Empty(dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +277,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
Assert.Empty(dict);
|
Assert.Empty(dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,6 +292,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
dict.OrderBy(kvp => kvp.Key),
|
dict.OrderBy(kvp => kvp.Key),
|
||||||
kvp =>
|
kvp =>
|
||||||
|
|
@ -314,6 +320,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
dict.OrderBy(kvp => kvp.Key),
|
dict.OrderBy(kvp => kvp.Key),
|
||||||
kvp => { Assert.Equal("DerivedProperty", kvp.Key); Assert.Equal(5, kvp.Value); });
|
kvp => { Assert.Equal("DerivedProperty", kvp.Key); Assert.Equal(5, kvp.Value); });
|
||||||
|
|
@ -330,6 +337,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
Assert.Empty(dict);
|
Assert.Empty(dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -918,6 +926,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(dict);
|
Assert.Empty(dict);
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -932,6 +941,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(dict);
|
Assert.Empty(dict);
|
||||||
Assert.Null(dict._propertyStorage);
|
Assert.Null(dict._propertyStorage);
|
||||||
|
Assert.Empty(dict._arrayStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -949,10 +959,11 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(dict);
|
Assert.Empty(dict);
|
||||||
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
||||||
|
Assert.Null(dict._propertyStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Contains_KeyValuePair_True()
|
public void Contains_ListStorage_KeyValuePair_True()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var dict = new RouteValueDictionary()
|
var dict = new RouteValueDictionary()
|
||||||
|
|
@ -971,7 +982,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Contains_KeyValuePair_True_CaseInsensitive()
|
public void Contains_ListStory_KeyValuePair_True_CaseInsensitive()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var dict = new RouteValueDictionary()
|
var dict = new RouteValueDictionary()
|
||||||
|
|
@ -990,7 +1001,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Contains_KeyValuePair_False()
|
public void Contains_ListStorage_KeyValuePair_False()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var dict = new RouteValueDictionary()
|
var dict = new RouteValueDictionary()
|
||||||
|
|
@ -1010,7 +1021,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Value comparisons use the default equality comparer.
|
// Value comparisons use the default equality comparer.
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Contains_KeyValuePair_False_ValueComparisonIsDefault()
|
public void Contains_ListStorage_KeyValuePair_False_ValueComparisonIsDefault()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var dict = new RouteValueDictionary()
|
var dict = new RouteValueDictionary()
|
||||||
|
|
@ -1028,6 +1039,87 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_PropertyStorage_KeyValuePair_True()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new RouteValueDictionary(new { key = "value" });
|
||||||
|
|
||||||
|
var input = new KeyValuePair<string, object>("key", "value");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ((ICollection<KeyValuePair<string, object>>)dict).Contains(input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
|
Assert.Collection(
|
||||||
|
dict,
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_PropertyStory_KeyValuePair_True_CaseInsensitive()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new RouteValueDictionary(new { key = "value" });
|
||||||
|
|
||||||
|
var input = new KeyValuePair<string, object>("KEY", "value");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ((ICollection<KeyValuePair<string, object>>)dict).Contains(input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
|
Assert.Collection(
|
||||||
|
dict,
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_PropertyStorage_KeyValuePair_False()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new RouteValueDictionary(new { key = "value" });
|
||||||
|
|
||||||
|
var input = new KeyValuePair<string, object>("other", "value");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ((ICollection<KeyValuePair<string, object>>)dict).Contains(input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
|
Assert.Collection(
|
||||||
|
dict,
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value comparisons use the default equality comparer.
|
||||||
|
[Fact]
|
||||||
|
public void Contains_PropertyStorage_KeyValuePair_False_ValueComparisonIsDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new RouteValueDictionary(new { key = "value" });
|
||||||
|
|
||||||
|
var input = new KeyValuePair<string, object>("key", "valUE");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ((ICollection<KeyValuePair<string, object>>)dict).Contains(input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
|
Assert.Collection(
|
||||||
|
dict,
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ContainsKey_EmptyStorage()
|
public void ContainsKey_EmptyStorage()
|
||||||
{
|
{
|
||||||
|
|
@ -1066,6 +1158,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1080,6 +1173,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1094,6 +1188,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.NotNull(dict._propertyStorage);
|
Assert.NotNull(dict._propertyStorage);
|
||||||
|
Assert.Null(dict._arrayStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1393,6 +1488,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Remove_KeyAndOutValue_EmptyStorage()
|
public void Remove_KeyAndOutValue_EmptyStorage()
|
||||||
{
|
{
|
||||||
|
|
@ -1634,9 +1730,28 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always 'upgrade' if you are trying to write to the dictionary.
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryAdd_ConvertsPropertyStorage_ToArrayStorage()
|
public void TryAdd_PropertyStorage_KeyDoesNotExist_ConvertsPropertyStorageToArrayStorage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new RouteValueDictionary(new { key = "value", });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.TryAdd("otherKey", "value");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.Null(dict._propertyStorage);
|
||||||
|
Assert.Collection(
|
||||||
|
dict._arrayStorage,
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("otherKey", "value"), kvp),
|
||||||
|
kvp => Assert.Equal(default, kvp),
|
||||||
|
kvp => Assert.Equal(default, kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryAdd_PropertyStory_KeyExist_DoesNotConvertPropertyStorageToArrayStorage()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var dict = new RouteValueDictionary(new { key = "value", });
|
var dict = new RouteValueDictionary(new { key = "value", });
|
||||||
|
|
@ -1646,13 +1761,11 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
Assert.Null(dict._propertyStorage);
|
Assert.Null(dict._arrayStorage);
|
||||||
|
Assert.NotNull(dict._propertyStorage);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
dict._arrayStorage,
|
dict,
|
||||||
kvp => Assert.Equal(new KeyValuePair<string, object>("key", "value"), kvp),
|
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]
|
[Fact]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue