Split RVD in twain

This commit is contained in:
Ryan Nowak 2017-09-21 09:19:10 -07:00
parent 134096d9cb
commit d652b86852
11 changed files with 1184 additions and 980 deletions

View File

@ -0,0 +1,790 @@
// 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 Microsoft.AspNetCore.Dispatcher.Abstractions;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Dispatcher
{
/// <summary>
/// An <see cref="IDictionary{String, Object}"/> type for dispatcher values.
/// </summary>
public class DispatcherValueCollection : IDictionary<string, object>, IReadOnlyDictionary<string, object>
{
internal Storage _storage;
/// <summary>
/// Creates an empty <see cref="DispatcherValueCollection"/>.
/// </summary>
public DispatcherValueCollection()
{
_storage = EmptyStorage.Instance;
}
/// <summary>
/// Creates a <see cref="DispatcherValueCollection"/> initialized with the specified <paramref name="values"/>.
/// </summary>
/// <param name="values">An object to initialize the dictionary. The value can be of type
/// <see cref="IDictionary{TKey, TValue}"/> or <see cref="IReadOnlyDictionary{TKey, TValue}"/>
/// or an object with public properties as key-value pairs.
/// </param>
/// <remarks>
/// If the value is a dictionary or other <see cref="IEnumerable{T}"/> of <see cref="KeyValuePair{String, Object}"/>,
/// 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.
/// </remarks>
public DispatcherValueCollection(object values)
{
var dictionary = values as DispatcherValueCollection;
if (dictionary != null)
{
var listStorage = dictionary._storage as ListStorage;
if (listStorage != null)
{
_storage = new ListStorage(listStorage);
return;
}
var propertyStorage = dictionary._storage as PropertyStorage;
if (propertyStorage != null)
{
// PropertyStorage is immutable so we can just copy it.
_storage = dictionary._storage;
return;
}
// If we get here, it's an EmptyStorage.
_storage = EmptyStorage.Instance;
return;
}
var keyValueEnumerable = values as IEnumerable<KeyValuePair<string, object>>;
if (keyValueEnumerable != null)
{
var listStorage = new ListStorage();
_storage = listStorage;
foreach (var kvp in keyValueEnumerable)
{
if (listStorage.ContainsKey(kvp.Key))
{
var message = Resources.FormatDispatcherValueCollection_DuplicateKey(kvp.Key, nameof(DispatcherValueCollection));
throw new ArgumentException(message, nameof(values));
}
listStorage.Add(kvp);
}
return;
}
var stringValueEnumerable = values as IEnumerable<KeyValuePair<string, string>>;
if (stringValueEnumerable != null)
{
var listStorage = new ListStorage();
_storage = listStorage;
foreach (var kvp in stringValueEnumerable)
{
if (listStorage.ContainsKey(kvp.Key))
{
var message = Resources.FormatDispatcherValueCollection_DuplicateKey(kvp.Key, nameof(DispatcherValueCollection));
throw new ArgumentException(message, nameof(values));
}
listStorage.Add(new KeyValuePair<string, object>(kvp.Key, kvp.Value));
}
return;
}
if (values != null)
{
_storage = new PropertyStorage(values);
return;
}
_storage = EmptyStorage.Instance;
}
/// <inheritdoc />
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));
}
if (!_storage.TrySetValue(key, value))
{
Upgrade();
_storage.TrySetValue(key, value);
}
}
}
/// <summary>
/// Gets the comparer for this dictionary.
/// </summary>
/// <remarks>
/// This will always be a reference to <see cref="StringComparer.OrdinalIgnoreCase"/>
/// </remarks>
public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase;
/// <inheritdoc />
public int Count => _storage.Count;
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
/// <inheritdoc />
public ICollection<string> Keys
{
get
{
Upgrade();
var list = (ListStorage)_storage;
var keys = new string[list.Count];
for (var i = 0; i < keys.Length; i++)
{
keys[i] = list[i].Key;
}
return keys;
}
}
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys
{
get
{
return Keys;
}
}
/// <inheritdoc />
public ICollection<object> Values
{
get
{
Upgrade();
var list = (ListStorage)_storage;
var values = new object[list.Count];
for (var i = 0; i < values.Length; i++)
{
values[i] = list[i].Value;
}
return values;
}
}
IEnumerable<object> IReadOnlyDictionary<string, object>.Values
{
get
{
return Values;
}
}
/// <inheritdoc />
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
Add(item.Key, item.Value);
}
/// <inheritdoc />
public void Add(string key, object value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
Upgrade();
var list = (ListStorage)_storage;
for (var i = 0; i < list.Count; i++)
{
if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase))
{
var message = Resources.FormatDispatcherValueCollection_DuplicateKey(key, nameof(DispatcherValueCollection));
throw new ArgumentException(message, nameof(key));
}
}
list.Add(new KeyValuePair<string, object>(key, value));
}
/// <inheritdoc />
public void Clear()
{
if (_storage.Count == 0)
{
return;
}
Upgrade();
var list = (ListStorage)_storage;
list.Clear();
}
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
if (_storage.Count == 0)
{
return false;
}
Upgrade();
var list = (ListStorage)_storage;
for (var i = 0; i < list.Count; i++)
{
if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase))
{
return EqualityComparer<object>.Default.Equals(list[i].Value, item.Value);
}
}
return false;
}
/// <inheritdoc />
public bool ContainsKey(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
return _storage.ContainsKey(key);
}
/// <inheritdoc />
void ICollection<KeyValuePair<string, object>>.CopyTo(
KeyValuePair<string, object>[] array,
int arrayIndex)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (_storage.Count == 0)
{
return;
}
Upgrade();
var list = (ListStorage)_storage;
list.CopyTo(array, arrayIndex);
}
/// <inheritdoc />
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
/// <inheritdoc />
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
if (_storage.Count == 0)
{
return false;
}
Upgrade();
var list = (ListStorage)_storage;
for (var i = 0; i < list.Count; i++)
{
if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase) &&
EqualityComparer<object>.Default.Equals(list[i].Value, item.Value))
{
list.RemoveAt(i);
return true;
}
}
return false;
}
/// <inheritdoc />
public bool Remove(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (_storage.Count == 0)
{
return false;
}
Upgrade();
var list = (ListStorage)_storage;
for (var i = 0; i < list.Count; i++)
{
if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase))
{
list.RemoveAt(i);
return true;
}
}
return false;
}
/// <inheritdoc />
public bool TryGetValue(string key, out object value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
return _storage.TryGetValue(key, out value);
}
private void Upgrade()
{
_storage.Upgrade(ref _storage);
}
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
{
private readonly Storage _storage;
private int _index;
public Enumerator(DispatcherValueCollection collection)
{
if (collection == null)
{
throw new ArgumentNullException();
}
_storage = collection._storage;
Current = default(KeyValuePair<string, object>);
_index = -1;
}
public KeyValuePair<string, object> Current { get; private set; }
object IEnumerator.Current => Current;
public void Dispose()
{
}
public bool MoveNext()
{
if (++_index < _storage.Count)
{
Current = _storage[_index];
return true;
}
Current = default(KeyValuePair<string, object>);
return false;
}
public void Reset()
{
Current = default(KeyValuePair<string, object>);
_index = -1;
}
}
// Storage and its subclasses are internal for testing.
internal abstract class Storage
{
public abstract int Count { get; }
public abstract KeyValuePair<string, object> this[int index] { get; set; }
public abstract void Upgrade(ref Storage storage);
public abstract bool TryGetValue(string key, out object value);
public abstract bool ContainsKey(string key);
public abstract bool TrySetValue(string key, object value);
}
internal class ListStorage : Storage
{
private KeyValuePair<string, object>[] _items;
private int _count;
private static readonly KeyValuePair<string, object>[] _emptyArray = new KeyValuePair<string, object>[0];
public ListStorage()
{
_items = _emptyArray;
}
public ListStorage(int capacity)
{
if (capacity == 0)
{
_items = _emptyArray;
}
else
{
_items = new KeyValuePair<string, object>[capacity];
}
}
public ListStorage(ListStorage other)
{
if (other.Count == 0)
{
_items = _emptyArray;
}
else
{
_items = new KeyValuePair<string, object>[other.Count];
for (var i = 0; i < other.Count; i++)
{
this.Add(other[i]);
}
}
}
public int Capacity => _items.Length;
public override int Count => _count;
public override KeyValuePair<string, object> this[int index]
{
get
{
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _items[index];
}
set
{
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
_items[index] = value;
}
}
public void Add(KeyValuePair<string, object> item)
{
if (_count == _items.Length)
{
EnsureCapacity(_count + 1);
}
_items[_count++] = item;
}
public void RemoveAt(int index)
{
_count--;
for (var i = index; i < _count; i++)
{
_items[i] = _items[i + 1];
}
_items[_count] = default(KeyValuePair<string, object>);
}
public void Clear()
{
for (var i = 0; i < _count; i++)
{
_items[i] = default(KeyValuePair<string, object>);
}
_count = 0;
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
for (var i = 0; i < _count; i++)
{
array[arrayIndex++] = _items[i];
}
}
public override bool ContainsKey(string key)
{
for (var i = 0; i < Count; i++)
{
var kvp = _items[i];
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
public override bool TrySetValue(string key, object value)
{
for (var i = 0; i < Count; i++)
{
var kvp = _items[i];
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
{
_items[i] = new KeyValuePair<string, object>(key, value);
return true;
}
}
Add(new KeyValuePair<string, object>(key, value));
return true;
}
public override bool TryGetValue(string key, out object value)
{
for (var i = 0; i < Count; i++)
{
var kvp = _items[i];
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
{
value = kvp.Value;
return true;
}
}
value = null;
return false;
}
public override void Upgrade(ref Storage storage)
{
// Do nothing.
}
private void EnsureCapacity(int min)
{
var newLength = _items.Length == 0 ? 4 : _items.Length * 2;
var newItems = new KeyValuePair<string, object>[newLength];
for (var i = 0; i < _count; i++)
{
newItems[i] = _items[i];
}
_items = newItems;
}
}
internal class PropertyStorage : Storage
{
private static readonly PropertyCache _propertyCache = new PropertyCache();
internal readonly object _value;
internal readonly PropertyHelper[] _properties;
public PropertyStorage(object value)
{
Debug.Assert(value != null);
_value = value;
// Cache the properties so we can know if we've already validated them for duplicates.
var type = _value.GetType();
if (!_propertyCache.TryGetValue(type, out _properties))
{
_properties = PropertyHelper.GetVisibleProperties(type);
ValidatePropertyNames(type, _properties);
_propertyCache.TryAdd(type, _properties);
}
}
public PropertyStorage(PropertyStorage propertyStorage)
{
_value = propertyStorage._value;
_properties = propertyStorage._properties;
}
public override int Count => _properties.Length;
public override KeyValuePair<string, object> this[int index]
{
get
{
var property = _properties[index];
return new KeyValuePair<string, object>(property.Name, property.GetValue(_value));
}
set
{
// PropertyStorage never sets a value.
throw new NotImplementedException();
}
}
public override bool TryGetValue(string key, out object value)
{
for (var i = 0; i < _properties.Length; i++)
{
var property = _properties[i];
if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase))
{
value = property.GetValue(_value);
return true;
}
}
value = null;
return false;
}
public override bool ContainsKey(string key)
{
for (var i = 0; i < _properties.Length; i++)
{
var property = _properties[i];
if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
public override bool TrySetValue(string key, object value)
{
// PropertyStorage never sets a value.
return false;
}
public override void Upgrade(ref Storage storage)
{
storage = new ListStorage(Count);
for (var i = 0; i < _properties.Length; i++)
{
var property = _properties[i];
storage.TrySetValue(property.Name, property.GetValue(_value));
}
}
private static void ValidatePropertyNames(Type type, PropertyHelper[] properties)
{
var names = new Dictionary<string, PropertyHelper>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < properties.Length; i++)
{
var property = properties[i];
PropertyHelper duplicate;
if (names.TryGetValue(property.Name, out duplicate))
{
var message = Resources.FormatDispatcherValueCollection_DuplicatePropertyName(
type.FullName,
property.Name,
duplicate.Name,
nameof(DispatcherValueCollection));
throw new InvalidOperationException(message);
}
names.Add(property.Name, property);
}
}
}
internal class EmptyStorage : Storage
{
public static readonly EmptyStorage Instance = new EmptyStorage();
private EmptyStorage()
{
}
public override int Count => 0;
public override KeyValuePair<string, object> this[int index]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override bool ContainsKey(string key)
{
return false;
}
public override bool TryGetValue(string key, out object value)
{
value = null;
return false;
}
public override bool TrySetValue(string key, object value)
{
return false;
}
public override void Upgrade(ref Storage storage)
{
storage = new ListStorage();
}
}
private class PropertyCache : ConcurrentDictionary<Type, PropertyHelper[]>
{
}
}
}

View File

@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Dispatcher.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,58 @@
// <auto-generated />
namespace Microsoft.AspNetCore.Dispatcher.Abstractions
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Dispatcher.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// An element with the key '{0}' already exists in the {1}.
/// </summary>
internal static string DispatcherValueCollection_DuplicateKey
{
get => GetString("DispatcherValueCollection_DuplicateKey");
}
/// <summary>
/// An element with the key '{0}' already exists in the {1}.
/// </summary>
internal static string FormatDispatcherValueCollection_DuplicateKey(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherValueCollection_DuplicateKey"), p0, p1);
/// <summary>
/// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.
/// </summary>
internal static string DispatcherValueCollection_DuplicatePropertyName
{
get => GetString("DispatcherValueCollection_DuplicatePropertyName");
}
/// <summary>
/// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.
/// </summary>
internal static string FormatDispatcherValueCollection_DuplicatePropertyName(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherValueCollection_DuplicatePropertyName"), p0, p1, p2, p3);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DispatcherValueCollection_DuplicateKey" xml:space="preserve">
<value>An element with the key '{0}' already exists in the {1}.</value>
</data>
<data name="DispatcherValueCollection_DuplicatePropertyName" xml:space="preserve">
<value>The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.</value>
</data>
</root>

View File

@ -13,6 +13,9 @@ Microsoft.AspNetCore.Routing.RouteData</Description>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@ -10,34 +10,6 @@ namespace Microsoft.AspNetCore.Routing.Abstractions
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Routing.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// An element with the key '{0}' already exists in the {1}.
/// </summary>
internal static string RouteValueDictionary_DuplicateKey
{
get => GetString("RouteValueDictionary_DuplicateKey");
}
/// <summary>
/// An element with the key '{0}' already exists in the {1}.
/// </summary>
internal static string FormatRouteValueDictionary_DuplicateKey(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("RouteValueDictionary_DuplicateKey"), p0, p1);
/// <summary>
/// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.
/// </summary>
internal static string RouteValueDictionary_DuplicatePropertyName
{
get => GetString("RouteValueDictionary_DuplicatePropertyName");
}
/// <summary>
/// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.
/// </summary>
internal static string FormatRouteValueDictionary_DuplicatePropertyName(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("RouteValueDictionary_DuplicatePropertyName"), p0, p1, p2, p3);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -117,10 +117,4 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="RouteValueDictionary_DuplicateKey" xml:space="preserve">
<value>An element with the key '{0}' already exists in the {1}.</value>
</data>
<data name="RouteValueDictionary_DuplicatePropertyName" xml:space="preserve">
<value>The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.</value>
</data>
</root>

View File

@ -3,402 +3,30 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Routing.Abstractions;
using Microsoft.Extensions.Internal;
using Microsoft.AspNetCore.Dispatcher;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// An <see cref="IDictionary{String, Object}"/> type for route values.
/// </summary>
public class RouteValueDictionary : IDictionary<string, object>, IReadOnlyDictionary<string, object>
public class RouteValueDictionary : DispatcherValueCollection
{
internal Storage _storage;
/// <summary>
/// Creates an empty <see cref="RouteValueDictionary"/>.
/// </summary>
public RouteValueDictionary()
: base()
{
_storage = EmptyStorage.Instance;
}
/// <summary>
/// Creates a <see cref="RouteValueDictionary"/> initialized with the specified <paramref name="values"/>.
/// </summary>
/// <param name="values">An object to initialize the dictionary. The value can be of type
/// <see cref="IDictionary{TKey, TValue}"/> or <see cref="IReadOnlyDictionary{TKey, TValue}"/>
/// or an object with public properties as key-value pairs.
/// </param>
/// <remarks>
/// If the value is a dictionary or other <see cref="IEnumerable{T}"/> of <see cref="KeyValuePair{String, Object}"/>,
/// 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.
/// </remarks>
public RouteValueDictionary(object values)
public RouteValueDictionary(object obj)
: base(obj)
{
var dictionary = values as RouteValueDictionary;
if (dictionary != null)
{
var listStorage = dictionary._storage as ListStorage;
if (listStorage != null)
{
_storage = new ListStorage(listStorage);
return;
}
var propertyStorage = dictionary._storage as PropertyStorage;
if (propertyStorage != null)
{
// PropertyStorage is immutable so we can just copy it.
_storage = dictionary._storage;
return;
}
// If we get here, it's an EmptyStorage.
_storage = EmptyStorage.Instance;
return;
}
var keyValueEnumerable = values as IEnumerable<KeyValuePair<string, object>>;
if (keyValueEnumerable != null)
{
var listStorage = new ListStorage();
_storage = listStorage;
foreach (var kvp in keyValueEnumerable)
{
if (listStorage.ContainsKey(kvp.Key))
{
var message = Resources.FormatRouteValueDictionary_DuplicateKey(kvp.Key, nameof(RouteValueDictionary));
throw new ArgumentException(message, nameof(values));
}
listStorage.Add(kvp);
}
return;
}
var stringValueEnumerable = values as IEnumerable<KeyValuePair<string, string>>;
if (stringValueEnumerable != null)
{
var listStorage = new ListStorage();
_storage = listStorage;
foreach (var kvp in stringValueEnumerable)
{
if (listStorage.ContainsKey(kvp.Key))
{
var message = Resources.FormatRouteValueDictionary_DuplicateKey(kvp.Key, nameof(RouteValueDictionary));
throw new ArgumentException(message, nameof(values));
}
listStorage.Add(new KeyValuePair<string, object>(kvp.Key, kvp.Value));
}
return;
}
if (values != null)
{
_storage = new PropertyStorage(values);
return;
}
_storage = EmptyStorage.Instance;
}
/// <inheritdoc />
public object this[string key]
// Required to avoid a breaking change in the split of RVD/DVC
public new Enumerator GetEnumerator() => new Enumerator(this);
// Required to avoid a breaking change in the split of RVD/DVC
public new struct Enumerator : IEnumerator<KeyValuePair<string, object>>
{
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));
}
if (!_storage.TrySetValue(key, value))
{
Upgrade();
_storage.TrySetValue(key, value);
}
}
}
/// <summary>
/// Gets the comparer for this dictionary.
/// </summary>
/// <remarks>
/// This will always be a reference to <see cref="StringComparer.OrdinalIgnoreCase"/>
/// </remarks>
public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase;
/// <inheritdoc />
public int Count => _storage.Count;
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
/// <inheritdoc />
public ICollection<string> Keys
{
get
{
Upgrade();
var list = (ListStorage)_storage;
var keys = new string[list.Count];
for (var i = 0; i < keys.Length; i++)
{
keys[i] = list[i].Key;
}
return keys;
}
}
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys
{
get
{
return Keys;
}
}
/// <inheritdoc />
public ICollection<object> Values
{
get
{
Upgrade();
var list = (ListStorage)_storage;
var values = new object[list.Count];
for (var i = 0; i < values.Length; i++)
{
values[i] = list[i].Value;
}
return values;
}
}
IEnumerable<object> IReadOnlyDictionary<string, object>.Values
{
get
{
return Values;
}
}
/// <inheritdoc />
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
Add(item.Key, item.Value);
}
/// <inheritdoc />
public void Add(string key, object value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
Upgrade();
var list = (ListStorage)_storage;
for (var i = 0; i < list.Count; i++)
{
if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase))
{
var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary));
throw new ArgumentException(message, nameof(key));
}
}
list.Add(new KeyValuePair<string, object>(key, value));
}
/// <inheritdoc />
public void Clear()
{
if (_storage.Count == 0)
{
return;
}
Upgrade();
var list = (ListStorage)_storage;
list.Clear();
}
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
if (_storage.Count == 0)
{
return false;
}
Upgrade();
var list = (ListStorage)_storage;
for (var i = 0; i < list.Count; i++)
{
if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase))
{
return EqualityComparer<object>.Default.Equals(list[i].Value, item.Value);
}
}
return false;
}
/// <inheritdoc />
public bool ContainsKey(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
return _storage.ContainsKey(key);
}
/// <inheritdoc />
void ICollection<KeyValuePair<string, object>>.CopyTo(
KeyValuePair<string, object>[] array,
int arrayIndex)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (_storage.Count == 0)
{
return;
}
Upgrade();
var list = (ListStorage)_storage;
list.CopyTo(array, arrayIndex);
}
/// <inheritdoc />
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
/// <inheritdoc />
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
if (_storage.Count == 0)
{
return false;
}
Upgrade();
var list = (ListStorage)_storage;
for (var i = 0; i < list.Count; i++)
{
if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase) &&
EqualityComparer<object>.Default.Equals(list[i].Value, item.Value))
{
list.RemoveAt(i);
return true;
}
}
return false;
}
/// <inheritdoc />
public bool Remove(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (_storage.Count == 0)
{
return false;
}
Upgrade();
var list = (ListStorage)_storage;
for (var i = 0; i < list.Count; i++)
{
if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase))
{
list.RemoveAt(i);
return true;
}
}
return false;
}
/// <inheritdoc />
public bool TryGetValue(string key, out object value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
return _storage.TryGetValue(key, out value);
}
private void Upgrade()
{
_storage.Upgrade(ref _storage);
}
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
{
private readonly Storage _storage;
private int _index;
private DispatcherValueCollection.Enumerator _inner;
public Enumerator(RouteValueDictionary dictionary)
{
@ -407,15 +35,12 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException();
}
_storage = dictionary._storage;
Current = default(KeyValuePair<string, object>);
_index = -1;
_inner = ((DispatcherValueCollection)dictionary).GetEnumerator();
}
public KeyValuePair<string, object> Current { get; private set; }
public KeyValuePair<string, object> Current => _inner.Current;
object IEnumerator.Current => Current;
object IEnumerator.Current => _inner.Current;
public void Dispose()
{
@ -423,368 +48,13 @@ namespace Microsoft.AspNetCore.Routing
public bool MoveNext()
{
if (++_index < _storage.Count)
{
Current = _storage[_index];
return true;
}
Current = default(KeyValuePair<string, object>);
return false;
return _inner.MoveNext();
}
public void Reset()
{
Current = default(KeyValuePair<string, object>);
_index = -1;
_inner.Reset();
}
}
// Storage and its subclasses are internal for testing.
internal abstract class Storage
{
public abstract int Count { get; }
public abstract KeyValuePair<string, object> this[int index] { get; set; }
public abstract void Upgrade(ref Storage storage);
public abstract bool TryGetValue(string key, out object value);
public abstract bool ContainsKey(string key);
public abstract bool TrySetValue(string key, object value);
}
internal class ListStorage : Storage
{
private KeyValuePair<string, object>[] _items;
private int _count;
private static readonly KeyValuePair<string, object>[] _emptyArray = new KeyValuePair<string, object>[0];
public ListStorage()
{
_items = _emptyArray;
}
public ListStorage(int capacity)
{
if (capacity == 0)
{
_items = _emptyArray;
}
else
{
_items = new KeyValuePair<string, object>[capacity];
}
}
public ListStorage(ListStorage other)
{
if (other.Count == 0)
{
_items = _emptyArray;
}
else
{
_items = new KeyValuePair<string, object>[other.Count];
for (var i = 0; i < other.Count; i++)
{
this.Add(other[i]);
}
}
}
public int Capacity => _items.Length;
public override int Count => _count;
public override KeyValuePair<string, object> this[int index]
{
get
{
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _items[index];
}
set
{
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
_items[index] = value;
}
}
public void Add(KeyValuePair<string, object> item)
{
if (_count == _items.Length)
{
EnsureCapacity(_count + 1);
}
_items[_count++] = item;
}
public void RemoveAt(int index)
{
_count--;
for (var i = index; i < _count; i++)
{
_items[i] = _items[i + 1];
}
_items[_count] = default(KeyValuePair<string, object>);
}
public void Clear()
{
for (var i = 0; i < _count; i++)
{
_items[i] = default(KeyValuePair<string, object>);
}
_count = 0;
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
for (var i = 0; i < _count; i++)
{
array[arrayIndex++] = _items[i];
}
}
public override bool ContainsKey(string key)
{
for (var i = 0; i < Count; i++)
{
var kvp = _items[i];
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
public override bool TrySetValue(string key, object value)
{
for (var i = 0; i < Count; i++)
{
var kvp = _items[i];
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
{
_items[i] = new KeyValuePair<string, object>(key, value);
return true;
}
}
Add(new KeyValuePair<string, object>(key, value));
return true;
}
public override bool TryGetValue(string key, out object value)
{
for (var i = 0; i < Count; i++)
{
var kvp = _items[i];
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
{
value = kvp.Value;
return true;
}
}
value = null;
return false;
}
public override void Upgrade(ref Storage storage)
{
// Do nothing.
}
private void EnsureCapacity(int min)
{
var newLength = _items.Length == 0 ? 4 : _items.Length * 2;
var newItems = new KeyValuePair<string, object>[newLength];
for (var i = 0; i < _count; i++)
{
newItems[i] = _items[i];
}
_items = newItems;
}
}
internal class PropertyStorage : Storage
{
private static readonly PropertyCache _propertyCache = new PropertyCache();
internal readonly object _value;
internal readonly PropertyHelper[] _properties;
public PropertyStorage(object value)
{
Debug.Assert(value != null);
_value = value;
// Cache the properties so we can know if we've already validated them for duplicates.
var type = _value.GetType();
if (!_propertyCache.TryGetValue(type, out _properties))
{
_properties = PropertyHelper.GetVisibleProperties(type);
ValidatePropertyNames(type, _properties);
_propertyCache.TryAdd(type, _properties);
}
}
public PropertyStorage(PropertyStorage propertyStorage)
{
_value = propertyStorage._value;
_properties = propertyStorage._properties;
}
public override int Count => _properties.Length;
public override KeyValuePair<string, object> this[int index]
{
get
{
var property = _properties[index];
return new KeyValuePair<string, object>(property.Name, property.GetValue(_value));
}
set
{
// PropertyStorage never sets a value.
throw new NotImplementedException();
}
}
public override bool TryGetValue(string key, out object value)
{
for (var i = 0; i < _properties.Length; i++)
{
var property = _properties[i];
if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase))
{
value = property.GetValue(_value);
return true;
}
}
value = null;
return false;
}
public override bool ContainsKey(string key)
{
for (var i = 0; i < _properties.Length; i++)
{
var property = _properties[i];
if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
public override bool TrySetValue(string key, object value)
{
// PropertyStorage never sets a value.
return false;
}
public override void Upgrade(ref Storage storage)
{
storage = new ListStorage(Count);
for (var i = 0; i < _properties.Length; i++)
{
var property = _properties[i];
storage.TrySetValue(property.Name, property.GetValue(_value));
}
}
private static void ValidatePropertyNames(Type type, PropertyHelper[] properties)
{
var names = new Dictionary<string, PropertyHelper>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < properties.Length; i++)
{
var property = properties[i];
PropertyHelper duplicate;
if (names.TryGetValue(property.Name, out duplicate))
{
var message = Resources.FormatRouteValueDictionary_DuplicatePropertyName(
type.FullName,
property.Name,
duplicate.Name,
nameof(RouteValueDictionary));
throw new InvalidOperationException(message);
}
names.Add(property.Name, property);
}
}
}
internal class EmptyStorage : Storage
{
public static readonly EmptyStorage Instance = new EmptyStorage();
private EmptyStorage()
{
}
public override int Count => 0;
public override KeyValuePair<string, object> this[int index]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override bool ContainsKey(string key)
{
return false;
}
public override bool TryGetValue(string key, out object value)
{
value = null;
return false;
}
public override bool TrySetValue(string key, object value)
{
return false;
}
public override void Upgrade(ref Storage storage)
{
storage = new ListStorage();
}
}
private class PropertyCache : ConcurrentDictionary<Type, PropertyHelper[]>
{
}
}
}

View File

@ -1,16 +0,0 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Dispatcher.Abstractions.Test
{
public class DispatcherAbstractionsTest
{
[Fact]
public void Test()
{
}
}
}