RVD crazy

This commit is contained in:
Ryan Nowak 2016-04-08 15:12:21 -07:00
parent e8ce0e7523
commit 96b7678c8f
6 changed files with 1931 additions and 308 deletions

View File

@ -3,9 +3,11 @@
using System.Reflection; using System.Reflection;
using System.Resources; using System.Resources;
using System.Runtime.CompilerServices;
[assembly: AssemblyMetadata("Serviceable", "True")] [assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: NeutralResourcesLanguage("en-us")] [assembly: NeutralResourcesLanguage("en-us")]
[assembly: AssemblyCompany("Microsoft Corporation.")] [assembly: AssemblyCompany("Microsoft Corporation.")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] [assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyProduct("Microsoft ASP.NET Core")] [assembly: AssemblyProduct("Microsoft ASP.NET Core")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,62 @@
// <auto-generated />
namespace Microsoft.AspNetCore.Routing.Abstractions
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
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 { return 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)
{
return 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 { return 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)
{
return 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);
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="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,7 +3,10 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Routing.Abstractions;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
@ -13,11 +16,14 @@ namespace Microsoft.AspNetCore.Routing
/// </summary> /// </summary>
public class RouteValueDictionary : IDictionary<string, object>, IReadOnlyDictionary<string, object> public class RouteValueDictionary : IDictionary<string, object>, IReadOnlyDictionary<string, object>
{ {
internal Storage _storage;
/// <summary> /// <summary>
/// Creates an empty <see cref="RouteValueDictionary"/>. /// Creates an empty <see cref="RouteValueDictionary"/>.
/// </summary> /// </summary>
public RouteValueDictionary() public RouteValueDictionary()
{ {
_storage = EmptyStorage.Instance;
} }
/// <summary> /// <summary>
@ -35,42 +41,43 @@ namespace Microsoft.AspNetCore.Routing
/// </remarks> /// </remarks>
public RouteValueDictionary(object values) public RouteValueDictionary(object values)
{ {
var otherDictionary = values as RouteValueDictionary; var dictionary = values as RouteValueDictionary;
if (otherDictionary != null) if (dictionary != null)
{ {
if (otherDictionary.InnerDictionary != null) var listStorage = dictionary._storage as ListStorage;
if (listStorage != null)
{ {
InnerDictionary = new Dictionary<string, object>( _storage = new ListStorage(listStorage);
otherDictionary.InnerDictionary.Count, return;
StringComparer.OrdinalIgnoreCase); }
foreach (var kvp in otherDictionary.InnerDictionary) var propertyStorage = dictionary._storage as PropertyStorage;
{ if (propertyStorage != null)
InnerDictionary[kvp.Key] = kvp.Value; {
} // PropertyStorage is immutable so we can just copy it.
_storage = dictionary._storage;
return;
}
return; // If we get here, it's an EmptyStorage.
} _storage = EmptyStorage.Instance;
else if (otherDictionary.Properties != null) return;
{
Properties = otherDictionary.Properties;
Value = otherDictionary.Value;
return;
}
else
{
return;
}
} }
var keyValuePairCollection = values as IEnumerable<KeyValuePair<string, object>>; var keyValueEnumerable = values as IEnumerable<KeyValuePair<string, object>>;
if (keyValuePairCollection != null) if (keyValueEnumerable != null)
{ {
InnerDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); var listStorage = new ListStorage();
_storage = listStorage;
foreach (var kvp in keyValuePairCollection) foreach (var kvp in keyValueEnumerable)
{ {
InnerDictionary[kvp.Key] = kvp.Value; if (listStorage.ContainsKey(kvp.Key))
{
var message = Resources.FormatRouteValueDictionary_DuplicateKey(kvp.Key, nameof(RouteValueDictionary));
throw new ArgumentException(message, nameof(values));
}
listStorage._inner.Add(kvp);
} }
return; return;
@ -78,19 +85,13 @@ namespace Microsoft.AspNetCore.Routing
if (values != null) if (values != null)
{ {
Properties = PropertyHelper.GetVisibleProperties(values); _storage = new PropertyStorage(values);
Value = values;
return; return;
} }
_storage = EmptyStorage.Instance;
} }
private Dictionary<string, object> InnerDictionary { get; set; }
private PropertyHelper[] Properties { get; }
private object Value { get; }
/// <inheritdoc /> /// <inheritdoc />
public object this[string key] public object this[string key]
{ {
@ -113,8 +114,11 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
EnsureWritable(); if (!_storage.TrySetValue(key, value))
InnerDictionary[key] = value; {
Upgrade();
_storage.TrySetValue(key, value);
}
} }
} }
@ -127,7 +131,7 @@ namespace Microsoft.AspNetCore.Routing
public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase; public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase;
/// <inheritdoc /> /// <inheritdoc />
public int Count => InnerDictionary?.Count ?? Properties?.Length ?? 0; public int Count => _storage.Count;
/// <inheritdoc /> /// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false; bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
@ -137,8 +141,16 @@ namespace Microsoft.AspNetCore.Routing
{ {
get get
{ {
EnsureWritable(); Upgrade();
return InnerDictionary.Keys;
var list = ((ListStorage)_storage)._inner;
var keys = new string[list.Count];
for (var i = 0; i < keys.Length; i++)
{
keys[i] = list[i].Key;
}
return keys;
} }
} }
@ -146,8 +158,7 @@ namespace Microsoft.AspNetCore.Routing
{ {
get get
{ {
EnsureWritable(); return Keys;
return InnerDictionary.Keys;
} }
} }
@ -156,8 +167,16 @@ namespace Microsoft.AspNetCore.Routing
{ {
get get
{ {
EnsureWritable(); Upgrade();
return InnerDictionary.Values;
var list = ((ListStorage)_storage)._inner;
var values = new object[list.Count];
for (var i = 0; i < values.Length; i++)
{
values[i] = list[i].Value;
}
return values;
} }
} }
@ -165,16 +184,14 @@ namespace Microsoft.AspNetCore.Routing
{ {
get get
{ {
EnsureWritable(); return Values;
return InnerDictionary.Values;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{ {
EnsureWritable(); Add(item.Key, item.Value);
((ICollection<KeyValuePair<string, object>>)InnerDictionary).Add(item);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -185,22 +202,55 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
EnsureWritable(); Upgrade();
InnerDictionary.Add(key, value);
var list = ((ListStorage)_storage)._inner;
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 /> /// <inheritdoc />
public void Clear() public void Clear()
{ {
EnsureWritable(); if (_storage.Count == 0)
InnerDictionary.Clear(); {
return;
}
Upgrade();
var list = ((ListStorage)_storage)._inner;
list.Clear();
} }
/// <inheritdoc /> /// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{ {
EnsureWritable(); if (_storage.Count == 0)
return ((ICollection<KeyValuePair<string, object>>)InnerDictionary).Contains(item); {
return false;
}
Upgrade();
var list = ((ListStorage)_storage)._inner;
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 /> /// <inheritdoc />
@ -211,27 +261,7 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
if (InnerDictionary != null) return _storage.ContainsKey(key);
{
return InnerDictionary.ContainsKey(key);
}
else if (Properties != null)
{
for (var i = 0; i < Properties.Length; i++)
{
var property = Properties[i];
if (Comparer.Equals(property.Name, key))
{
return true;
}
}
return false;
}
else
{
return false;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -244,8 +274,20 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(nameof(array)); throw new ArgumentNullException(nameof(array));
} }
EnsureWritable(); if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count)
((ICollection<KeyValuePair<string, object>>)InnerDictionary).CopyTo(array, arrayIndex); {
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (_storage.Count == 0)
{
return;
}
Upgrade();
var list = ((ListStorage)_storage)._inner;
list.CopyTo(array, arrayIndex);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -269,8 +311,25 @@ namespace Microsoft.AspNetCore.Routing
/// <inheritdoc /> /// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{ {
EnsureWritable(); if (_storage.Count == 0)
return ((ICollection<KeyValuePair<string, object>>)InnerDictionary).Remove(item); {
return false;
}
Upgrade();
var list = ((ListStorage)_storage)._inner;
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 /> /// <inheritdoc />
@ -281,8 +340,24 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
EnsureWritable(); if (_storage.Count == 0)
return InnerDictionary.Remove(key); {
return false;
}
Upgrade();
var list = ((ListStorage)_storage)._inner;
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 /> /// <inheritdoc />
@ -293,56 +368,18 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
if (InnerDictionary != null) return _storage.TryGetValue(key, out value);
{
return InnerDictionary.TryGetValue(key, out value);
}
else if (Properties != null)
{
for (var i = 0; i < Properties.Length; i++)
{
var property = Properties[i];
if (Comparer.Equals(property.Name, key))
{
value = property.ValueGetter(Value);
return true;
}
}
value = null;
return false;
}
else
{
value = null;
return false;
}
} }
private void EnsureWritable() private void Upgrade()
{ {
if (InnerDictionary == null && Properties == null) _storage.Upgrade(ref _storage);
{
InnerDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
else if (InnerDictionary == null)
{
InnerDictionary = new Dictionary<string, object>(Properties.Length, StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < Properties.Length; i++)
{
var property = Properties[i];
InnerDictionary.Add(property.Property.Name, property.ValueGetter(Value));
}
}
} }
public struct Enumerator : IEnumerator<KeyValuePair<string, object>> public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
{ {
private readonly RouteValueDictionary _dictionary; private readonly Storage _storage;
private int _index; private int _index;
private Dictionary<string, object>.Enumerator _enumerator;
public Enumerator(RouteValueDictionary dictionary) public Enumerator(RouteValueDictionary dictionary)
{ {
@ -351,13 +388,10 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(); throw new ArgumentNullException();
} }
_dictionary = dictionary; _storage = dictionary._storage;
Current = default(KeyValuePair<string, object>); Current = default(KeyValuePair<string, object>);
_index = -1; _index = -1;
_enumerator = _dictionary.InnerDictionary == null ?
default(Dictionary<string, object>.Enumerator) :
_dictionary.InnerDictionary.GetEnumerator();
} }
public KeyValuePair<string, object> Current { get; private set; } public KeyValuePair<string, object> Current { get; private set; }
@ -370,24 +404,10 @@ namespace Microsoft.AspNetCore.Routing
public bool MoveNext() public bool MoveNext()
{ {
if (_dictionary?.InnerDictionary != null) if (++_index < _storage.Count)
{ {
if (_enumerator.MoveNext()) Current = _storage[_index];
{ return true;
Current = _enumerator.Current;
return true;
}
}
else if (_dictionary?.Properties != null)
{
_index++;
if (_index < _dictionary.Properties.Length)
{
var property = _dictionary.Properties[_index];
var value = property.ValueGetter(_dictionary.Value);
Current = new KeyValuePair<string, object>(property.Name, value);
return true;
}
} }
Current = default(KeyValuePair<string, object>); Current = default(KeyValuePair<string, object>);
@ -398,10 +418,260 @@ namespace Microsoft.AspNetCore.Routing
{ {
Current = default(KeyValuePair<string, object>); Current = default(KeyValuePair<string, object>);
_index = -1; _index = -1;
_enumerator = _dictionary?.InnerDictionary == null ?
default(Dictionary<string, object>.Enumerator) :
_dictionary.InnerDictionary.GetEnumerator();
} }
} }
// 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; }
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
{
internal readonly List<KeyValuePair<string, object>> _inner;
public ListStorage()
{
_inner = new List<KeyValuePair<string, object>>();
}
public ListStorage(int capacity)
{
_inner = new List<KeyValuePair<string, object>>(capacity);
}
public ListStorage(ListStorage other)
{
// Perf: Don't call the copy constructor, that would box the enumerator.
_inner = new List<KeyValuePair<string, object>>(other._inner.Capacity);
for (var i = 0; i < other._inner.Count; i++)
{
_inner.Add(other._inner[i]);
}
}
public override int Count => _inner.Count;
public override KeyValuePair<string, object> this[int index] => _inner[index];
public void Clear()
{
_inner.Clear();
}
public override bool ContainsKey(string key)
{
for (var i = 0; i < _inner.Count; i++)
{
var kvp = _inner[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 < _inner.Count; i++)
{
var kvp = _inner[i];
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
{
_inner[i] = new KeyValuePair<string, object>(key, value);
return true;
}
}
_inner.Add(new KeyValuePair<string, object>(key, value));
return true;
}
public override bool TryGetValue(string key, out object value)
{
for (var i = 0; i < _inner.Count; i++)
{
var kvp = _inner[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.
}
}
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));
}
}
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();
}
}
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,6 +1,7 @@
{ {
"compilationOptions": { "compilationOptions": {
"warningsAsErrors": true "warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
}, },
"dependencies": { "dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1-*", "Microsoft.NETCore.Platforms": "1.0.1-*",