Add Remove(string key, out object value) overload to RouteValueDictionary (#858)

* Add Remove(string key, out object value) overload to RouteValueDictionary.

* Consistently use _count field instead of Count property in Remove overloads.
Added comment on EnsureCapacity call.
Added test for removing first/middle/last entry.
This commit is contained in:
Gert Driesen 2018-10-17 22:24:07 +02:00 committed by Ryan Nowak
parent e8b2c9337e
commit 5c31f1f455
3 changed files with 275 additions and 6 deletions

View File

@ -388,7 +388,9 @@ namespace Microsoft.AspNetCore.Routing
return false;
}
EnsureCapacity(Count);
// Ensure property storage is converted to array storage as we'll be
// applying the lookup and removal on the array
EnsureCapacity(_count);
var index = FindIndex(key);
if (index >= 0)
@ -404,6 +406,48 @@ namespace Microsoft.AspNetCore.Routing
return false;
}
/// <summary>
/// Attempts to remove and return the value that has the specified key from the <see cref="RouteValueDictionary"/>.
/// </summary>
/// <param name="key">The key of the element to remove and return.</param>
/// <param name="value">When this method returns, contains the object removed from the <see cref="RouteValueDictionary"/>, or <c>null</c> if key does not exist.</param>
/// <returns>
/// <c>true</c> if the object was removed successfully; otherwise, <c>false</c>.
/// </returns>
public bool Remove(string key, out object value)
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
if (_count == 0)
{
value = default;
return false;
}
// Ensure property storage is converted to array storage as we'll be
// applying the lookup and removal on the array
EnsureCapacity(_count);
var index = FindIndex(key);
if (index >= 0)
{
_count--;
var array = _arrayStorage;
value = array[index].Value;
Array.Copy(array, index + 1, array, index, _count - index);
array[_count] = default;
return true;
}
value = default;
return false;
}
/// <summary>
/// Attempts to the add the provided <paramref name="key"/> and <paramref name="value"/> to the dictionary.
/// </summary>

View File

@ -471,11 +471,7 @@ namespace Microsoft.AspNetCore.Routing.Template
else if (part is RoutePatternParameterPart parameterPart)
{
// If it's a parameter, get its value
var hasValue = acceptedValues.TryGetValue(parameterPart.Name, out var value);
if (hasValue)
{
acceptedValues.Remove(parameterPart.Name);
}
acceptedValues.Remove(parameterPart.Name, out var value);
var isSameAsDefault = false;
if (_defaults != null &&

View File

@ -1393,6 +1393,235 @@ namespace Microsoft.AspNetCore.Routing.Tests
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_EmptyStorage()
{
// Arrange
var dict = new RouteValueDictionary();
// Act
var result = dict.Remove("key", out var removedValue);
// Assert
Assert.False(result);
Assert.Null(removedValue);
}
[Fact]
public void Remove_KeyAndOutValue_EmptyStringIsAllowed()
{
// Arrange
var dict = new RouteValueDictionary();
// Act
var result = dict.Remove("", out var removedValue);
// Assert
Assert.False(result);
Assert.Null(removedValue);
}
[Fact]
public void Remove_KeyAndOutValue_PropertyStorage_Empty()
{
// Arrange
var dict = new RouteValueDictionary(new { });
// Act
var result = dict.Remove("other", out var removedValue);
// Assert
Assert.False(result);
Assert.Null(removedValue);
Assert.Empty(dict);
Assert.NotNull(dict._propertyStorage);
}
[Fact]
public void Remove_KeyAndOutValue_PropertyStorage_False()
{
// Arrange
var dict = new RouteValueDictionary(new { key = "value" });
// Act
var result = dict.Remove("other", out var removedValue);
// Assert
Assert.False(result);
Assert.Null(removedValue);
Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_PropertyStorage_True()
{
// Arrange
object value = "value";
var dict = new RouteValueDictionary(new { key = value });
// Act
var result = dict.Remove("key", out var removedValue);
// Assert
Assert.True(result);
Assert.Same(value, removedValue);
Assert.Empty(dict);
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_PropertyStorage_True_CaseInsensitive()
{
// Arrange
object value = "value";
var dict = new RouteValueDictionary(new { key = value });
// Act
var result = dict.Remove("kEy", out var removedValue);
// Assert
Assert.True(result);
Assert.Same(value, removedValue);
Assert.Empty(dict);
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_ListStorage_False()
{
// Arrange
var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
// Act
var result = dict.Remove("other", out var removedValue);
// Assert
Assert.False(result);
Assert.Null(removedValue);
Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_ListStorage_True()
{
// Arrange
object value = "value";
var dict = new RouteValueDictionary()
{
{ "key", value }
};
// Act
var result = dict.Remove("key", out var removedValue);
// Assert
Assert.True(result);
Assert.Same(value, removedValue);
Assert.Empty(dict);
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_ListStorage_True_CaseInsensitive()
{
// Arrange
object value = "value";
var dict = new RouteValueDictionary()
{
{ "key", value }
};
// Act
var result = dict.Remove("kEy", out var removedValue);
// Assert
Assert.True(result);
Assert.Same(value, removedValue);
Assert.Empty(dict);
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_ListStorage_KeyExists_First()
{
// Arrange
object value = "value";
var dict = new RouteValueDictionary()
{
{ "key", value },
{ "other", 5 },
{ "dotnet", "rocks" }
};
// Act
var result = dict.Remove("key", out var removedValue);
// Assert
Assert.True(result);
Assert.Same(value, removedValue);
Assert.Equal(2, dict.Count);
Assert.False(dict.ContainsKey("key"));
Assert.True(dict.ContainsKey("other"));
Assert.True(dict.ContainsKey("dotnet"));
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_ListStorage_KeyExists_Middle()
{
// Arrange
object value = "value";
var dict = new RouteValueDictionary()
{
{ "other", 5 },
{ "key", value },
{ "dotnet", "rocks" }
};
// Act
var result = dict.Remove("key", out var removedValue);
// Assert
Assert.True(result);
Assert.Same(value, removedValue);
Assert.Equal(2, dict.Count);
Assert.False(dict.ContainsKey("key"));
Assert.True(dict.ContainsKey("other"));
Assert.True(dict.ContainsKey("dotnet"));
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void Remove_KeyAndOutValue_ListStorage_KeyExists_Last()
{
// Arrange
object value = "value";
var dict = new RouteValueDictionary()
{
{ "other", 5 },
{ "dotnet", "rocks" },
{ "key", value }
};
// Act
var result = dict.Remove("key", out var removedValue);
// Assert
Assert.True(result);
Assert.Same(value, removedValue);
Assert.Equal(2, dict.Count);
Assert.False(dict.ContainsKey("key"));
Assert.True(dict.ContainsKey("other"));
Assert.True(dict.ContainsKey("dotnet"));
Assert.IsType<KeyValuePair<string, object>[]>(dict._arrayStorage);
}
[Fact]
public void TryAdd_EmptyStringIsAllowed()
{