Make GetOrdereredMetadata return something good

This commit is contained in:
Ryan Nowak 2019-05-08 08:49:41 -07:00
parent f45250ce5e
commit 434bf614d5
5 changed files with 243 additions and 21 deletions

View File

@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Http
public object this[int index] { get { throw null; } }
public Microsoft.AspNetCore.Http.EndpointMetadataCollection.Enumerator GetEnumerator() { throw null; }
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public T GetMetadata<T>() where T : class { throw null; }
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public System.Collections.Generic.IEnumerable<T> GetOrderedMetadata<T>() where T : class { throw null; }
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public Microsoft.AspNetCore.Http.OrderedEndpointMetadataCollection<T> GetOrderedMetadata<T>() where T : class { throw null; }
System.Collections.Generic.IEnumerator<object> System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator() { throw null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
@ -327,6 +327,30 @@ namespace Microsoft.AspNetCore.Http
Microsoft.AspNetCore.Http.IMiddleware Create(System.Type middlewareType);
void Release(Microsoft.AspNetCore.Http.IMiddleware middleware);
}
public sealed partial class OrderedEndpointMetadataCollection<T> : System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.Generic.IReadOnlyList<T>, System.Collections.IEnumerable where T : class
{
internal OrderedEndpointMetadataCollection() { }
public int Count { get { throw null; } }
public T this[int index] { get { throw null; } }
public Microsoft.AspNetCore.Http.OrderedEndpointMetadataCollection<T>.Enumerator GetEnumerator() { throw null; }
System.Collections.Generic.IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator() { throw null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public partial struct Enumerator : System.Collections.Generic.IEnumerator<T>, System.Collections.IEnumerator, System.IDisposable
{
private T[] _items;
[System.Diagnostics.DebuggerBrowsableAttribute(System.Diagnostics.DebuggerBrowsableState.Never)]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
private T _Current_k__BackingField;
private object _dummy;
private int _dummyPrimitive;
public T Current { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
object System.Collections.IEnumerator.Current { get { throw null; } }
public void Dispose() { }
public bool MoveNext() { throw null; }
public void Reset() { }
}
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct PathString : System.IEquatable<Microsoft.AspNetCore.Http.PathString>
{

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Http
public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty<object>());
private readonly object[] _items;
private readonly ConcurrentDictionary<Type, object[]> _cache;
private readonly ConcurrentDictionary<Type, object> _cache;
/// <summary>
/// Creates a new <see cref="EndpointMetadataCollection"/>.
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Http
}
_items = items.ToArray();
_cache = new ConcurrentDictionary<Type, object[]>();
_cache = new ConcurrentDictionary<Type, object>();
}
/// <summary>
@ -74,10 +74,11 @@ namespace Microsoft.AspNetCore.Http
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetMetadata<T>() where T : class
{
if (_cache.TryGetValue(typeof(T), out var result))
if (_cache.TryGetValue(typeof(T), out var obj))
{
var length = result.Length;
return length > 0 ? (T)result[length - 1] : default;
var result = (OrderedEndpointMetadataCollection<T>)obj;
var count = result.Count;
return count > 0 ? result[count - 1] : default;
}
return GetMetadataSlow<T>();
@ -85,9 +86,9 @@ namespace Microsoft.AspNetCore.Http
private T GetMetadataSlow<T>() where T : class
{
var array = GetOrderedMetadataSlow<T>();
var length = array.Length;
return length > 0 ? array[length - 1] : default;
var result = GetOrderedMetadataSlow<T>();
var count = result.Count;
return count > 0 ? result[count - 1] : default;
}
/// <summary>
@ -97,30 +98,34 @@ namespace Microsoft.AspNetCore.Http
/// <typeparam name="T">The type of metadata.</typeparam>
/// <returns>A sequence of metadata items of <typeparamref name="T"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerable<T> GetOrderedMetadata<T>() where T : class
public OrderedEndpointMetadataCollection<T> GetOrderedMetadata<T>() where T : class
{
if (_cache.TryGetValue(typeof(T), out var result))
{
return (T[])result;
return (OrderedEndpointMetadataCollection<T>)result;
}
return GetOrderedMetadataSlow<T>();
}
private T[] GetOrderedMetadataSlow<T>() where T : class
private OrderedEndpointMetadataCollection<T> GetOrderedMetadataSlow<T>() where T : class
{
var items = new List<T>();
for (var i = 0; i < _items.Length; i++)
// Perf: avoid allocations totally for the common case where there are no matching metadata.
List<T> matches = null;
var items = _items;
for (var i = 0; i < items.Length; i++)
{
if (_items[i] is T item)
if (items[i] is T item)
{
items.Add(item);
matches ??= new List<T>();
matches.Add(item);
}
}
var array = items.ToArray();
_cache.TryAdd(typeof(T), array);
return array;
var results = matches == null ? OrderedEndpointMetadataCollection<T>.Empty : new OrderedEndpointMetadataCollection<T>(matches.ToArray());
_cache.TryAdd(typeof(T), results);
return results;
}
/// <summary>

View File

@ -0,0 +1,118 @@
// 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.Generic;
namespace Microsoft.AspNetCore.Http
{
/// <summary>
/// A collection of ordered, strongly-typed metadata associated with an <see cref="Endpoint"/>.
/// </summary>
/// <typeparam name="T">The metadata type.</typeparam>
public sealed class OrderedEndpointMetadataCollection<T> : IReadOnlyList<T>
where T : class
{
internal static readonly OrderedEndpointMetadataCollection<T> Empty = new OrderedEndpointMetadataCollection<T>(Array.Empty<T>());
private readonly T[] _items;
internal OrderedEndpointMetadataCollection(T[] items)
{
_items = items;
}
/// <summary>
/// Gets the item at <paramref name="index"/>.
/// </summary>
/// <param name="index">The index of the item to retrieve.</param>
/// <returns>The item at <paramref name="index"/>.</returns>
public T this[int index] => _items[index];
/// <summary>
/// Gets the count of metadata items.
/// </summary>
public int Count => _items.Length;
/// <summary>
/// Gets an <see cref="IEnumerator"/> of all metadata items.
/// </summary>
/// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
public Enumerator GetEnumerator() => new Enumerator(this);
/// <summary>
/// Gets an <see cref="IEnumerator{T}"/> of all metadata items.
/// </summary>
/// <returns>An <see cref="IEnumerator{T}"/> of all metadata items.</returns>
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
/// <summary>
/// Gets an <see cref="IEnumerator"/> of all metadata items.
/// </summary>
/// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Enumerates the elements of an <see cref="OrderedEndpointMetadataCollection{T}"/>.
/// </summary>
public struct Enumerator : IEnumerator<T>
{
// Intentionally not readonly to prevent defensive struct copies
private T[] _items;
private int _index;
internal Enumerator(OrderedEndpointMetadataCollection<T> collection)
{
_items = collection._items;
_index = 0;
Current = null;
}
/// <summary>
/// Gets the element at the current position of the enumerator
/// </summary>
public T Current { get; private set; }
/// <summary>
/// Gets the element at the current position of the enumerator
/// </summary>
object IEnumerator.Current => Current;
/// <summary>
/// Releases all resources used by the <see cref="Enumerator"/>.
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Advances the enumerator to the next element of the <see cref="Enumerator"/>.
/// </summary>
/// <returns>
/// <c>true</c> if the enumerator was successfully advanced to the next element;
/// <c>false</c> if the enumerator has passed the end of the collection.
/// </returns>
public bool MoveNext()
{
if (_index < _items.Length)
{
Current = _items[_index++];
return true;
}
Current = null;
return false;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
public void Reset()
{
_index = 0;
Current = null;
}
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -45,5 +45,31 @@ namespace Microsoft.AspNetCore.Routing
value => Assert.Equal(2, value),
value => Assert.Equal(3, value));
}
[Fact]
public void GetOrderedMetadata_CanReturnEmptyCollection()
{
// Arrange
var metadata = new EndpointMetadataCollection(1, 2, 3);
// Act
var ordered = metadata.GetOrderedMetadata<string>();
Assert.Same(OrderedEndpointMetadataCollection<string>.Empty, ordered);
}
[Fact]
public void GetOrderedMetadata_CanReturnNonEmptyCollection()
{
// Arrange
var metadata = new EndpointMetadataCollection("1", "2");
// Act
var ordered1 = metadata.GetOrderedMetadata<string>();
var ordered2 = metadata.GetOrderedMetadata<string>();
Assert.Same(ordered1, ordered2);
Assert.Equal(new string[] { "1", "2" }, ordered1);
}
}
}
}

View File

@ -0,0 +1,49 @@
// 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.Http.Abstractions.Tests
{
public class OrderedEndpointMetadataCollectionTest
{
[Fact]
public void Constructor_Enumeration_ContainsValues()
{
// Arrange & Act
var metadata = new OrderedEndpointMetadataCollection<string>(new string[]
{
"1",
"2",
"3",
});
// Assert
Assert.Equal(3, metadata.Count);
Assert.Collection(metadata,
value => Assert.Equal("1", value),
value => Assert.Equal("2", value),
value => Assert.Equal("3", value));
}
[Fact]
public void Constructor_Loop_ContainsValues()
{
// Arrange & Act
var metadata = new OrderedEndpointMetadataCollection<string>(new string[]
{
"1",
"2",
"3",
});
// Assert
Assert.Equal(3, metadata.Count);
for (var i = 0; i < metadata.Count; i++)
{
Assert.Equal((i + 1).ToString(), metadata[i]);
}
}
}
}