// 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.Linq; using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Http { /// /// A collection of arbitrary metadata associated with an endpoint. /// /// /// instances contain a list of metadata items /// of arbitrary types. The metadata items are stored as an ordered collection with /// items arranged in ascending order of precedence. /// public sealed class EndpointMetadataCollection : IReadOnlyList { /// /// An empty . /// public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty()); private readonly object[] _items; private readonly ConcurrentDictionary _cache; /// /// Creates a new . /// /// The metadata items. public EndpointMetadataCollection(IEnumerable items) { if (items == null) { throw new ArgumentNullException(nameof(items)); } _items = items.ToArray(); _cache = new ConcurrentDictionary(); } /// /// Creates a new . /// /// The metadata items. public EndpointMetadataCollection(params object[] items) : this((IEnumerable)items) { } /// /// Gets the item at . /// /// The index of the item to retrieve. /// The item at . public object this[int index] => _items[index]; /// /// Gets the count of metadata items. /// public int Count => _items.Length; /// /// Gets the most significant metadata item of type . /// /// The type of metadata to retrieve. /// /// The most significant metadata of type or null. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetMetadata() where T : class { if (_cache.TryGetValue(typeof(T), out var result)) { var length = result.Length; return length > 0 ? (T)result[length - 1] : default; } return GetMetadataSlow(); } private T GetMetadataSlow() where T : class { var array = GetOrderedMetadataSlow(); var length = array.Length; return length > 0 ? array[length - 1] : default; } /// /// Gets the metadata items of type in ascending /// order of precedence. /// /// The type of metadata. /// A sequence of metadata items of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public IEnumerable GetOrderedMetadata() where T : class { if (_cache.TryGetValue(typeof(T), out var result)) { return (T[])result; } return GetOrderedMetadataSlow(); } private T[] GetOrderedMetadataSlow() where T : class { var items = new List(); for (var i = 0; i < _items.Length; i++) { if (_items[i] is T item) { items.Add(item); } } var array = items.ToArray(); _cache.TryAdd(typeof(T), array); return array; } /// /// Gets an of all metadata items. /// /// An of all metadata items. public Enumerator GetEnumerator() => new Enumerator(this); /// /// Gets an of all metadata items. /// /// An of all metadata items. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Gets an of all metadata items. /// /// An of all metadata items. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Enumerates the elements of an . /// public struct Enumerator : IEnumerator { // Intentionally not readonly to prevent defensive struct copies private object[] _items; private int _index; internal Enumerator(EndpointMetadataCollection collection) { _items = collection._items; _index = 0; Current = null; } /// /// Gets the element at the current position of the enumerator /// public object Current { get; private set; } /// /// Releases all resources used by the . /// public void Dispose() { } /// /// Advances the enumerator to the next element of the . /// /// /// true if the enumerator was successfully advanced to the next element; /// false if the enumerator has passed the end of the collection. /// public bool MoveNext() { if (_index < _items.Length) { Current = _items[_index++]; return true; } Current = null; return false; } /// /// Sets the enumerator to its initial position, which is before the first element in the collection. /// public void Reset() { _index = 0; Current = null; } } } }