// 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 System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http.Abstractions;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Routing
{
///
/// An type for route values.
///
public class RouteValueDictionary : IDictionary, IReadOnlyDictionary
{
// 4 is a good default capacity here because that leaves enough space for area/controller/action/id
private const int DefaultCapacity = 4;
internal KeyValuePair[] _arrayStorage;
internal PropertyStorage _propertyStorage;
private int _count;
///
/// Creates a new from the provided array.
/// The new instance will take ownership of the array, and may mutate it.
///
/// The items array.
/// A new .
public static RouteValueDictionary FromArray(KeyValuePair[] items)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
// We need to compress the array by removing non-contiguous items. We
// typically have a very small number of items to process. We don't need
// to preserve order.
var start = 0;
var end = items.Length - 1;
// We walk forwards from the beginning of the array and fill in 'null' slots.
// We walk backwards from the end of the array end move items in non-null' slots
// into whatever start is pointing to. O(n)
while (start <= end)
{
if (items[start].Key != null)
{
start++;
}
else if (items[end].Key != null)
{
// Swap this item into start and advance
items[start] = items[end];
items[end] = default;
start++;
end--;
}
else
{
// Both null, we need to hold on 'start' since we
// still need to fill it with something.
end--;
}
}
return new RouteValueDictionary()
{
_arrayStorage = items,
_count = start,
};
}
///
/// Creates an empty .
///
public RouteValueDictionary()
{
_arrayStorage = Array.Empty>();
}
///
/// Creates a initialized with the specified .
///
/// An object to initialize the dictionary. The value can be of type
/// or
/// or an object with public properties as key-value pairs.
///
///
/// If the value is a dictionary or other of ,
/// then its entries are copied. Otherwise the object is interpreted as a set of key-value pairs where the
/// property names are keys, and property values are the values, and copied into the dictionary.
/// Only public instance non-index properties are considered.
///
public RouteValueDictionary(object values)
: this()
{
if (values is RouteValueDictionary dictionary)
{
if (dictionary._propertyStorage != null)
{
// PropertyStorage is immutable so we can just copy it.
_propertyStorage = dictionary._propertyStorage;
_count = dictionary._count;
return;
}
var other = dictionary._arrayStorage;
var storage = new KeyValuePair[other.Length];
if (dictionary._count != 0)
{
Array.Copy(other, 0, storage, 0, dictionary._count);
}
_arrayStorage = storage;
_count = dictionary._count;
return;
}
if (values is IEnumerable> keyValueEnumerable)
{
foreach (var kvp in keyValueEnumerable)
{
Add(kvp.Key, kvp.Value);
}
return;
}
if (values is IEnumerable> stringValueEnumerable)
{
foreach (var kvp in stringValueEnumerable)
{
Add(kvp.Key, kvp.Value);
}
return;
}
if (values != null)
{
var storage = new PropertyStorage(values);
_propertyStorage = storage;
_count = storage.Properties.Length;
return;
}
}
///
public object this[string key]
{
get
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key));
}
object value;
TryGetValue(key, out value);
return value;
}
set
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key));
}
// We're calling this here for the side-effect of converting from properties
// to array. We need to create the array even if we just set an existing value since
// property storage is immutable.
EnsureCapacity(_count);
var index = FindInArray(key);
if (index < 0)
{
EnsureCapacity(_count + 1);
_arrayStorage[_count++] = new KeyValuePair(key, value);
}
else
{
_arrayStorage[index] = new KeyValuePair(key, value);
}
}
}
///
/// Gets the comparer for this dictionary.
///
///
/// This will always be a reference to
///
public IEqualityComparer Comparer => StringComparer.OrdinalIgnoreCase;
///
public int Count => _count;
///
bool ICollection>.IsReadOnly => false;
///
public ICollection Keys
{
get
{
EnsureCapacity(_count);
var array = _arrayStorage;
var keys = new string[_count];
for (var i = 0; i < keys.Length; i++)
{
keys[i] = array[i].Key;
}
return keys;
}
}
IEnumerable IReadOnlyDictionary.Keys => Keys;
///
public ICollection