File: System\Collections\Generic\OrderedDictionary.cs
Web Access
Project: src\src\libraries\System.Collections\src\System.Collections.csproj (System.Collections)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
#if NET
using static System.ArgumentNullException;
using static System.ArgumentOutOfRangeException;
#else
using static System.Collections.ThrowHelper;
#endif
 
namespace System.Collections.Generic
{
    /// <summary>
    /// Represents a collection of key/value pairs that are accessible by the key or index.
    /// </summary>
    /// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
    /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
    /// <remarks>
    /// Operations on the collection have algorithmic complexities that are similar to that of the <see cref="List{T}"/>
    /// class, except with lookups by key similar in complexity to that of <see cref="Dictionary{TKey, TValue}"/>.
    /// </remarks>
    [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
    [DebuggerDisplay("Count = {Count}")]
#if SYSTEM_COLLECTIONS
    public
#else
    internal sealed
#endif
    class OrderedDictionary<TKey, TValue> :
        IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary,
        IList<KeyValuePair<TKey, TValue>>, IReadOnlyList<KeyValuePair<TKey, TValue>>, IList
        where TKey : notnull
    {
        /// <summary>The comparer used by the collection. May be null if the default comparer is used.</summary>
        private IEqualityComparer<TKey>? _comparer;
        /// <summary>Indexes into <see cref="_entries"/> for the start of chains; indices are 1-based.</summary>
        private int[]? _buckets;
        /// <summary>Ordered entries in the dictionary.</summary>
        /// <remarks>
        /// Unlike <see cref="Dictionary{TKey, TValue}"/>, removed entries are actually removed rather than left as holes
        /// that can be filled in by subsequent additions. This is done to retain ordering.
        /// </remarks>
        private Entry[]? _entries;
        /// <summary>The number of items in the collection.</summary>
        private int _count;
        /// <summary>Version number used to invalidate an enumerator.</summary>
        private int _version;
        /// <summary>Multiplier used on 64-bit to enable faster % operations.</summary>
        private ulong _fastModMultiplier;
 
        /// <summary>Lazily-initialized wrapper collection that serves up only the keys, in order.</summary>
        private KeyCollection? _keys;
        /// <summary>Lazily-initialized wrapper collection that serves up only the values, in order.</summary>
        private ValueCollection? _values;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="OrderedDictionary{TKey, TValue}"/> class that is empty,
        /// has the default initial capacity, and uses the default equality comparer for the key type.
        /// </summary>
        public OrderedDictionary() : this(0, null)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="OrderedDictionary{TKey, TValue}"/> class that is empty,
        /// has the specified initial capacity, and uses the default equality comparer for the key type.
        /// </summary>
        /// <param name="capacity">The initial number of elements that the <see cref="OrderedDictionary{TKey, TValue}"/> can contain.</param>
        /// <exception cref="ArgumentOutOfRangeException">capacity is less than 0.</exception>
        public OrderedDictionary(int capacity) : this(capacity, null)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="OrderedDictionary{TKey, TValue}"/> class that is empty,
        /// has the default initial capacity, and uses the specified <see cref="IEqualityComparer{TKey}"/>.
        /// </summary>
        /// <param name="comparer">
        /// The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys,
        /// or null to use the default <see cref="EqualityComparer{TKey}"/> for the type of the key.
        /// </param>
        public OrderedDictionary(IEqualityComparer<TKey>? comparer) : this(0, comparer)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="OrderedDictionary{TKey, TValue}"/> class that is empty,
        /// has the specified initial capacity, and uses the specified <see cref="IEqualityComparer{TKey}"/>.
        /// </summary>
        /// <param name="capacity">The initial number of elements that the <see cref="OrderedDictionary{TKey, TValue}"/> can contain.</param>
        /// <param name="comparer">
        /// The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys,
        /// or null to use the default <see cref="EqualityComparer{TKey}"/> for the type of the key.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">capacity is less than 0.</exception>
        public OrderedDictionary(int capacity, IEqualityComparer<TKey>? comparer)
        {
            ThrowIfNegative(capacity);
 
            if (capacity > 0)
            {
                EnsureBucketsAndEntriesInitialized(capacity);
            }
 
            // Initialize the comparer:
            // - Strings: Special-case EqualityComparer<string>.Default, StringComparer.Ordinal, and
            //   StringComparer.OrdinalIgnoreCase. We start with a non-randomized comparer for improved throughput,
            //   falling back to a randomized comparer if the hash buckets become sufficiently unbalanced to cause
            //   more collisions than a preset threshold.
            // - Other reference types: we always want to store a comparer instance, either the one provided,
            //   or if one wasn't provided, the default (accessing EqualityComparer<TKey>.Default
            //   with shared generics on every dictionary access can add measurable overhead).
            // - Value types: if no comparer is provided, or if the default is provided, we'd prefer to use
            //   EqualityComparer<TKey>.Default.Equals on every use, enabling the JIT to
            //   devirtualize and possibly inline the operation.
            if (!typeof(TKey).IsValueType)
            {
                _comparer = comparer ?? EqualityComparer<TKey>.Default;
 
#if SYSTEM_COLLECTIONS
                if (typeof(TKey) == typeof(string) &&
                    NonRandomizedStringEqualityComparer.GetStringComparer(_comparer) is IEqualityComparer<string> stringComparer)
                {
                    _comparer = (IEqualityComparer<TKey>)stringComparer;
                }
#endif
            }
            else if (comparer is not null && // first check for null to avoid forcing default comparer instantiation unnecessarily
                     comparer != EqualityComparer<TKey>.Default)
            {
                _comparer = comparer;
            }
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="OrderedDictionary{TKey, TValue}"/> class that contains elements copied from
        /// the specified <see cref="IDictionary{TKey, TValue}"/> and uses the default equality comparer for the key type.
        /// </summary>
        /// <param name="dictionary">
        /// The <see cref="IDictionary{TKey, TValue}"/> whose elements are copied to the new <see cref="OrderedDictionary{TKey, TValue}"/>.
        /// The initial order of the elements in the new collection is the order the elements are enumerated from the supplied dictionary.
        /// </param>
        /// <exception cref="ArgumentNullException"><paramref name="dictionary"/> is null.</exception>
        public OrderedDictionary(IDictionary<TKey, TValue> dictionary) : this(dictionary, null)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="OrderedDictionary{TKey, TValue}"/> class that contains elements copied from
        /// the specified <see cref="IDictionary{TKey, TValue}"/> and uses the specified <see cref="IEqualityComparer{TKey}"/>.
        /// </summary>
        /// <param name="dictionary">
        /// The <see cref="IDictionary{TKey, TValue}"/> whose elements are copied to the new <see cref="OrderedDictionary{TKey, TValue}"/>.
        /// The initial order of the elements in the new collection is the order the elements are enumerated from the supplied dictionary.
        /// </param>
        /// <param name="comparer">
        /// The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys,
        /// or null to use the default <see cref="EqualityComparer{TKey}"/> for the type of the key.
        /// </param>
        /// <exception cref="ArgumentNullException"><paramref name="dictionary"/> is null.</exception>
        public OrderedDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey>? comparer) :
            this(dictionary?.Count ?? 0, comparer)
        {
            ThrowIfNull(dictionary);
 
            AddRange(dictionary);
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="OrderedDictionary{TKey, TValue}"/> class that contains elements copied
        /// from the specified <see cref="IEnumerable{T}"/> and uses the default equality comparer for the key type.
        /// </summary>
        /// <param name="collection">
        /// The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="OrderedDictionary{TKey, TValue}"/>.
        /// The initial order of the elements in the new collection is the order the elements are enumerated from the supplied collection.
        /// </param>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        public OrderedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, null)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="OrderedDictionary{TKey, TValue}"/> class that contains elements copied
        /// from the specified <see cref="IEnumerable{T}"/> and uses the specified <see cref="IEqualityComparer{TKey}"/>.
        /// </summary>
        /// <param name="collection">
        /// The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="OrderedDictionary{TKey, TValue}"/>.
        /// The initial order of the elements in the new collection is the order the elements are enumerated from the supplied collection.
        /// </param>
        /// <param name="comparer">
        /// The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys,
        /// or null to use the default <see cref="EqualityComparer{TKey}"/> for the type of the key.
        /// </param>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        public OrderedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey>? comparer) :
            this((collection as ICollection<KeyValuePair<TKey, TValue>>)?.Count ?? 0, comparer)
        {
            ThrowIfNull(collection);
 
            AddRange(collection);
        }
 
        /// <summary>Initializes the <see cref="_buckets"/>/<see cref="_entries"/>.</summary>
        /// <param name="capacity"></param>
        [MemberNotNull(nameof(_buckets))]
        [MemberNotNull(nameof(_entries))]
        private void EnsureBucketsAndEntriesInitialized(int capacity)
        {
            Resize(HashHelpers.GetPrime(capacity));
        }
 
        /// <summary>Gets the total number of key/value pairs the internal data structure can hold without resizing.</summary>
        public int Capacity => _entries?.Length ?? 0;
 
        /// <summary>Gets the <see cref="IEqualityComparer{TKey}"/> that is used to determine equality of keys for the dictionary.</summary>
        public IEqualityComparer<TKey> Comparer
        {
            get
            {
                IEqualityComparer<TKey>? comparer = _comparer;
 
#if SYSTEM_COLLECTIONS
                // If the key is a string, we may have substituted a non-randomized comparer during construction.
                // If we did, fish out and return the actual comparer that had been provided.
                if (typeof(TKey) == typeof(string) &&
                    (comparer as NonRandomizedStringEqualityComparer)?.GetUnderlyingEqualityComparer() is IEqualityComparer<TKey> ec)
                {
                    return ec;
                }
#endif
 
                // Otherwise, return whatever comparer we have, or the default if none was provided.
                return comparer ?? EqualityComparer<TKey>.Default;
            }
        }
 
        /// <summary>Gets the number of key/value pairs contained in the <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        public int Count => _count;
 
        /// <inheritdoc/>
        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
 
        /// <inheritdoc/>
        bool IDictionary.IsReadOnly => false;
 
        /// <inheritdoc/>
        bool IList.IsReadOnly => false;
 
        /// <inheritdoc/>
        bool IDictionary.IsFixedSize => false;
 
        /// <inheritdoc/>
        bool IList.IsFixedSize => false;
 
        /// <summary>Gets a collection containing the keys in the <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        public KeyCollection Keys => _keys ??= new(this);
 
        /// <inheritdoc/>
        IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
 
        /// <inheritdoc/>
        ICollection<TKey> IDictionary<TKey, TValue>.Keys => Keys;
 
        /// <inheritdoc/>
        ICollection IDictionary.Keys => Keys;
 
        /// <summary>Gets a collection containing the values in the <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        public ValueCollection Values => _values ??= new(this);
 
        /// <inheritdoc/>
        IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
 
        /// <inheritdoc/>
        ICollection<TValue> IDictionary<TKey, TValue>.Values => Values;
 
        /// <inheritdoc/>
        ICollection IDictionary.Values => Values;
 
        /// <inheritdoc/>
        bool ICollection.IsSynchronized => false;
 
        /// <inheritdoc/>
        object ICollection.SyncRoot => this;
 
        /// <inheritdoc/>
        object? IList.this[int index]
        {
            get => GetAt(index);
            set
            {
                ThrowIfNull(value);
 
                if (value is not KeyValuePair<TKey, TValue> tpair)
                {
                    throw new ArgumentException(SR.Format(SR.Arg_WrongType, value, typeof(KeyValuePair<TKey, TValue>)), nameof(value));
                }
 
                SetAt(index, tpair.Key, tpair.Value);
            }
        }
 
        /// <inheritdoc/>
        object? IDictionary.this[object key]
        {
            get
            {
                ThrowIfNull(key);
 
                if (key is TKey tkey && TryGetValue(tkey, out TValue? value))
                {
                    return value;
                }
 
                return null;
            }
            set
            {
                ThrowIfNull(key);
                if (default(TValue) is not null)
                {
                    ThrowIfNull(value);
                }
 
                if (key is not TKey tkey)
                {
                    throw new ArgumentException(SR.Format(SR.Arg_WrongType, key, typeof(TKey)), nameof(key));
                }
 
                TValue tvalue = default!;
                if (value is not null)
                {
                    if (value is not TValue temp)
                    {
                        throw new ArgumentException(SR.Format(SR.Arg_WrongType, value, typeof(TValue)), nameof(value));
                    }
 
                    tvalue = temp;
                }
 
                this[tkey] = tvalue;
            }
        }
 
        /// <inheritdoc/>
        KeyValuePair<TKey, TValue> IList<KeyValuePair<TKey, TValue>>.this[int index]
        {
            get => GetAt(index);
            set => SetAt(index, value.Key, value.Value);
        }
 
        /// <inheritdoc/>
        KeyValuePair<TKey, TValue> IReadOnlyList<KeyValuePair<TKey, TValue>>.this[int index] => GetAt(index);
 
        /// <summary>Gets or sets the value associated with the specified key.</summary>
        /// <param name="key">The key of the value to get or set.</param>
        /// <returns>The value associated with the specified key. If the specified key is not found, a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="key"/> is null.</exception>
        /// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="key"/> does not exist in the collection.</exception>
        /// <remarks>Setting the value of an existing key does not impact its order in the collection.</remarks>
        public TValue this[TKey key]
        {
            get
            {
                if (!TryGetValue(key, out TValue? value))
                {
                    ThrowHelper.ThrowKeyNotFound(key);
                }
 
                return value;
            }
            set
            {
                ThrowIfNull(key);
 
                bool modified = TryInsert(index: -1, key, value, InsertionBehavior.OverwriteExisting, out _);
                Debug.Assert(modified);
            }
        }
 
        /// <summary>Insert the key/value pair at the specified index.</summary>
        /// <param name="index">The index at which to insert the pair, or -1 to append.</param>
        /// <param name="key">The key to insert.</param>
        /// <param name="value">The value to insert.</param>
        /// <param name="behavior">
        /// The behavior controlling insertion behavior with respect to key duplication:
        /// - IgnoreInsertion: Immediately ends the operation, returning false, if the key already exists, e.g. TryAdd(key, value)
        /// - OverwriteExisting: If the key already exists, overwrites its value with the specified value, e.g. this[key] = value
        /// - ThrowOnExisting: If the key already exists, throws an exception, e.g. Add(key, value)
        /// </param>
        /// <param name="keyIndex">The index of the added or existing key. This is always a valid index into the dictionary.</param>
        /// <returns>true if the collection was updated; otherwise, false.</returns>
        private bool TryInsert(int index, TKey key, TValue value, InsertionBehavior behavior, out int keyIndex)
        {
            // Search for the key in the dictionary.
            uint hashCode = 0, collisionCount = 0;
            int i = IndexOf(key, ref hashCode, ref collisionCount);
 
            // Handle the case where the key already exists, based on the requested behavior.
            if (i >= 0)
            {
                keyIndex = i;
                Debug.Assert(0 <= keyIndex && keyIndex < _count);
 
                Debug.Assert(_entries is not null);
 
                switch (behavior)
                {
                    case InsertionBehavior.OverwriteExisting:
                        Debug.Assert(index < 0, "Expected index to be unspecied when overwriting an existing key.");
                        _entries[i].Value = value;
                        return true;
 
                    case InsertionBehavior.ThrowOnExisting:
                        ThrowHelper.ThrowDuplicateKey(key);
                        break;
 
                    default:
                        Debug.Assert(behavior is InsertionBehavior.IgnoreInsertion, $"Unknown behavior: {behavior}");
                        Debug.Assert(index < 0, "Expected index to be unspecied when ignoring a duplicate key.");
                        return false;
                }
            }
 
            // The key doesn't exist. If a non-negative index was provided, that is the desired index at which to insert,
            // which should have already been validated by the caller. If negative, we're appending.
            if (index < 0)
            {
                index = _count;
            }
            Debug.Assert(index <= _count);
 
            // Ensure the collection has been initialized.
            if (_buckets is null)
            {
                EnsureBucketsAndEntriesInitialized(0);
            }
 
            // As we just initialized the collection, _entries must be non-null.
            Entry[]? entries = _entries;
            Debug.Assert(entries is not null);
 
            // Grow capacity if necessary to accomodate the extra entry.
            if (entries.Length == _count)
            {
                Resize(HashHelpers.ExpandPrime(entries.Length));
                entries = _entries;
            }
 
            // The _entries array is ordered, so we need to insert the new entry at the specified index. That means
            // not only shifting up all elements at that index and higher, but also updating the buckets and chains
            // to record the newly updated indices.
            for (i = _count - 1; i >= index; --i)
            {
                entries[i + 1] = entries[i];
                UpdateBucketIndex(i, shiftAmount: 1);
            }
 
            // Store the new key/value pair.
            ref Entry entry = ref entries[index];
            entry.HashCode = hashCode;
            entry.Key = key;
            entry.Value = value;
            PushEntryIntoBucket(ref entry, index);
            _count++;
            _version++;
 
            RehashIfNecessary(collisionCount, entries);
 
            keyIndex = index;
            Debug.Assert(0 <= keyIndex && keyIndex < _count);
 
            return true;
        }
 
        /// <summary>Adds the specified key and value to the dictionary.</summary>
        /// <param name="key">The key of the element to add.</param>
        /// <param name="value">The value of the element to add. The value can be null for reference types.</param>
        /// <exception cref="ArgumentNullException">key is null.</exception>
        /// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="OrderedDictionary{TKey, TValue}"/>.</exception>
        public void Add(TKey key, TValue value)
        {
            ThrowIfNull(key);
 
            TryInsert(index: -1, key, value, InsertionBehavior.ThrowOnExisting, out _);
        }
 
        /// <summary>Adds the specified key and value to the dictionary if the key doesn't already exist.</summary>
        /// <param name="key">The key of the element to add.</param>
        /// <param name="value">The value of the element to add. The value can be null for reference types.</param>
        /// <exception cref="ArgumentNullException">key is null.</exception>
        /// <returns>true if the key didn't exist and the key and value were added to the dictionary; otherwise, false.</returns>
        public bool TryAdd(TKey key, TValue value) => TryAdd(key, value, out _);
 
        /// <summary>Adds the specified key and value to the dictionary if the key doesn't already exist.</summary>
        /// <param name="key">The key of the element to add.</param>
        /// <param name="value">The value of the element to add. The value can be null for reference types.</param>
        /// <param name="index">The index of the added or existing <paramref name="key"/>. This is always a valid index into the dictionary.</param>
        /// <exception cref="ArgumentNullException">key is null.</exception>
        /// <returns>true if the key didn't exist and the key and value were added to the dictionary; otherwise, false.</returns>
        public bool TryAdd(TKey key, TValue value, out int index)
        {
            ThrowIfNull(key);
 
            return TryInsert(index: -1, key, value, InsertionBehavior.IgnoreInsertion, out index);
        }
 
        /// <summary>Adds each element of the enumerable to the dictionary.</summary>
        private void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        {
            Debug.Assert(collection is not null);
 
            if (collection is KeyValuePair<TKey, TValue>[] array)
            {
                foreach (KeyValuePair<TKey, TValue> pair in array)
                {
                    Add(pair.Key, pair.Value);
                }
            }
            else
            {
                foreach (KeyValuePair<TKey, TValue> pair in collection)
                {
                    Add(pair.Key, pair.Value);
                }
            }
        }
 
        /// <summary>Removes all keys and values from the <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        public void Clear()
        {
            if (_buckets is not null && _count != 0)
            {
                Debug.Assert(_entries is not null);
 
                Array.Clear(_buckets, 0, _buckets.Length);
                Array.Clear(_entries, 0, _count);
                _count = 0;
                _version++;
            }
        }
 
        /// <summary>Determines whether the <see cref="OrderedDictionary{TKey, TValue}"/> contains the specified key.</summary>
        /// <param name="key">The key to locate in the <see cref="OrderedDictionary{TKey, TValue}"/>.</param>
        /// <returns>true if the <see cref="OrderedDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, false.</returns>
        public bool ContainsKey(TKey key) => IndexOf(key) >= 0;
 
        /// <summary>Determines whether the <see cref="OrderedDictionary{TKey, TValue}"/> contains a specific value.</summary>
        /// <param name="value">The value to locate in the <see cref="OrderedDictionary{TKey, TValue}"/>. The value can be null for reference types.</param>
        /// <returns>true if the <see cref="OrderedDictionary{TKey, TValue}"/> contains an element with the specified value; otherwise, false.</returns>
        public bool ContainsValue(TValue value)
        {
            int count = _count;
 
            Entry[]? entries = _entries;
            if (entries is null)
            {
                return false;
            }
 
            if (typeof(TValue).IsValueType)
            {
                for (int i = 0; i < count; i++)
                {
                    if (EqualityComparer<TValue>.Default.Equals(value, entries[i].Value))
                    {
                        return true;
                    }
                }
            }
            else
            {
                EqualityComparer<TValue> comparer = EqualityComparer<TValue>.Default;
                for (int i = 0; i < count; i++)
                {
                    if (comparer.Equals(value, entries[i].Value))
                    {
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        /// <summary>Gets the key/value pair at the specified index.</summary>
        /// <param name="index">The zero-based index of the pair to get.</param>
        /// <returns>The element at the specified index.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than or equal to <see cref="Count"/>.</exception>
        public KeyValuePair<TKey, TValue> GetAt(int index)
        {
            if ((uint)index >= (uint)_count)
            {
                ThrowHelper.ThrowIndexArgumentOutOfRange();
            }
 
            Debug.Assert(_entries is not null, "count must be positive, which means we must have entries");
 
            ref Entry e = ref _entries[index];
            return new(e.Key, e.Value);
        }
 
        /// <summary>Determines the index of a specific key in the <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        /// <param name="key">The key to locate.</param>
        /// <returns>The index of <paramref name="key"/> if found; otherwise, -1.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="key"/> is null.</exception>
        public int IndexOf(TKey key)
        {
            ThrowIfNull(key);
 
            uint _ = 0;
            return IndexOf(key, ref _, ref _);
        }
 
        private int IndexOf(TKey key, ref uint outHashCode, ref uint outCollisionCount)
        {
            Debug.Assert(key is not null, "Key nullness should have been validated by caller.");
 
            uint hashCode;
            uint collisionCount = 0;
            IEqualityComparer<TKey>? comparer = _comparer;
 
            if (_buckets is null)
            {
                hashCode = (uint)(comparer?.GetHashCode(key) ?? key.GetHashCode());
                collisionCount = 0;
                goto ReturnNotFound;
            }
 
            int i = -1;
            ref Entry entry = ref Unsafe.NullRef<Entry>();
 
            Entry[]? entries = _entries;
            Debug.Assert(entries is not null, "expected entries to be is not null");
 
            if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types
                comparer is null)
            {
                // ValueType: Devirtualize with EqualityComparer<TKey>.Default intrinsic
 
                hashCode = (uint)key.GetHashCode();
                i = GetBucket(hashCode) - 1; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
                do
                {
                    // Test in if to drop range check for following array access
                    if ((uint)i >= (uint)entries.Length)
                    {
                        goto ReturnNotFound;
                    }
 
                    entry = ref entries[i];
                    if (entry.HashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entry.Key, key))
                    {
                        goto Return;
                    }
 
                    i = entry.Next;
 
                    collisionCount++;
                }
                while (collisionCount <= (uint)entries.Length);
 
                // The chain of entries forms a loop; which means a concurrent update has happened.
                // Break out of the loop and throw, rather than looping forever.
                goto ConcurrentOperation;
            }
            else
            {
                Debug.Assert(comparer is not null);
                hashCode = (uint)comparer.GetHashCode(key);
                i = GetBucket(hashCode) - 1; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
                do
                {
                    // Test in if to drop range check for following array access
                    if ((uint)i >= (uint)entries.Length)
                    {
                        goto ReturnNotFound;
                    }
 
                    entry = ref entries[i];
                    if (entry.HashCode == hashCode && comparer.Equals(entry.Key, key))
                    {
                        goto Return;
                    }
 
                    i = entry.Next;
 
                    collisionCount++;
                }
                while (collisionCount <= (uint)entries.Length);
 
                // The chain of entries forms a loop; which means a concurrent update has happened.
                // Break out of the loop and throw, rather than looping forever.
                goto ConcurrentOperation;
            }
 
        ReturnNotFound:
            i = -1;
            outCollisionCount = collisionCount;
            goto Return;
 
        ConcurrentOperation:
            // We examined more entries than are actually in the list, which means there's a cycle
            // that's caused by erroneous concurrent use.
            ThrowHelper.ThrowConcurrentOperation();
 
        Return:
            outHashCode = hashCode;
            return i;
        }
 
        /// <summary>Inserts an item into the collection at the specified index.</summary>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        /// <param name="key">The key to insert.</param>
        /// <param name="value">The value to insert.</param>
        /// <exception cref="ArgumentNullException">key is null.</exception>
        /// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="OrderedDictionary{TKey, TValue}"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than <see cref="Count"/>.</exception>
        public void Insert(int index, TKey key, TValue value)
        {
            if ((uint)index > (uint)_count)
            {
                ThrowHelper.ThrowIndexArgumentOutOfRange();
            }
 
            ThrowIfNull(key);
 
            TryInsert(index, key, value, InsertionBehavior.ThrowOnExisting, out _);
        }
 
        /// <summary>Removes the value with the specified key from the <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <returns></returns>
        public bool Remove(TKey key) => Remove(key, out _);
 
        /// <summary>Removes the value with the specified key from the <see cref="OrderedDictionary{TKey, TValue}"/> and copies the element to the value parameter.</summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <param name="value">The removed element.</param>
        /// <returns>true if the element is successfully found and removed; otherwise, false.</returns>
        public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
        {
            ThrowIfNull(key);
 
            // Find the key.
            int index = IndexOf(key);
            if (index >= 0)
            {
                // It exists. Remove it.
                Debug.Assert(_entries is not null);
 
                value = _entries[index].Value;
                RemoveAt(index);
 
                return true;
            }
 
            value = default;
            return false;
        }
 
        /// <summary>Removes the key/value pair at the specified index.</summary>
        /// <param name="index">The zero-based index of the item to remove.</param>
        public void RemoveAt(int index)
        {
            int count = _count;
            if ((uint)index >= (uint)count)
            {
                ThrowHelper.ThrowIndexArgumentOutOfRange();
            }
 
            // Remove from the associated bucket chain the entry that lives at the specified index.
            RemoveEntryFromBucket(index);
 
            // Shift down all entries above this one, and fix up the bucket chains to reflect the new indices.
            Entry[]? entries = _entries;
            Debug.Assert(entries is not null);
            for (int i = index + 1; i < count; i++)
            {
                entries[i - 1] = entries[i];
                UpdateBucketIndex(i, shiftAmount: -1);
            }
 
            entries[--_count] = default;
            _version++;
        }
 
        /// <summary>Sets the value for the key at the specified index.</summary>
        /// <param name="index">The zero-based index of the element to get or set.</param>
        /// <param name="value">The value to store at the specified index.</param>
        public void SetAt(int index, TValue value)
        {
            if ((uint)index >= (uint)_count)
            {
                ThrowHelper.ThrowIndexArgumentOutOfRange();
            }
 
            Debug.Assert(_entries is not null);
 
            _entries[index].Value = value;
        }
 
        /// <summary>Sets the key/value pair at the specified index.</summary>
        /// <param name="index">The zero-based index of the element to get or set.</param>
        /// <param name="key">The key to store at the specified index.</param>
        /// <param name="value">The value to store at the specified index.</param>
        /// <exception cref="ArgumentException"></exception>
        public void SetAt(int index, TKey key, TValue value)
        {
            if ((uint)index >= (uint)_count)
            {
                ThrowHelper.ThrowIndexArgumentOutOfRange();
            }
 
            ThrowIfNull(key);
 
            Debug.Assert(_entries is not null);
            ref Entry e = ref _entries[index];
 
            // If the key matches the one that's already in that slot, just update the value.
            if (typeof(TKey).IsValueType && _comparer is null)
            {
                if (EqualityComparer<TKey>.Default.Equals(key, e.Key))
                {
                    e.Value = value;
                    return;
                }
            }
            else
            {
                Debug.Assert(_comparer is not null);
                if (_comparer.Equals(key, e.Key))
                {
                    e.Value = value;
                    return;
                }
            }
 
            // The key doesn't match that index. If it exists elsewhere in the collection, fail.
            uint hashCode = 0, collisionCount = 0;
            if (IndexOf(key, ref hashCode, ref collisionCount) >= 0)
            {
                ThrowHelper.ThrowDuplicateKey(key);
            }
 
            // The key doesn't exist in the collection. Update the key and value, but also update
            // the bucket chains, as the new key may not hash to the same bucket as the old key
            // (we could check for this, but in a properly balanced dictionary the chances should
            // be low for a match, so it's not worth it).
            RemoveEntryFromBucket(index);
            e.HashCode = hashCode;
            e.Key = key;
            e.Value = value;
            PushEntryIntoBucket(ref e, index);
 
            _version++;
 
            RehashIfNecessary(collisionCount, _entries);
        }
 
        /// <summary>Ensures that the dictionary can hold up to <paramref name="capacity"/> entries without resizing.</summary>
        /// <param name="capacity">The desired minimum capacity of the dictionary. The actual capacity provided may be larger.</param>
        /// <returns>The new capacity of the dictionary.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is negative.</exception>
        public int EnsureCapacity(int capacity)
        {
            ThrowIfNegative(capacity);
 
            if (Capacity < capacity)
            {
                if (_buckets is null)
                {
                    EnsureBucketsAndEntriesInitialized(capacity);
                }
                else
                {
                    Resize(HashHelpers.GetPrime(capacity));
                }
 
                _version++;
            }
 
            return Capacity;
        }
 
        /// <summary>Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries.</summary>
        public void TrimExcess() => TrimExcess(_count);
 
        /// <summary>Sets the capacity of this dictionary to hold up a specified number of entries without resizing.</summary>
        /// <param name="capacity">The desired capacity to which to shrink the dictionary.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than <see cref="Count"/>.</exception>
        public void TrimExcess(int capacity)
        {
            ThrowIfLessThan(capacity, Count);
 
            int currentCapacity = _entries?.Length ?? 0;
            capacity = HashHelpers.GetPrime(capacity);
            if (capacity < currentCapacity)
            {
                Resize(capacity);
            }
        }
 
        /// <summary>Gets the value associated with the specified key.</summary>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="value">
        /// When this method returns, contains the value associated with the specified key, if the key is found;
        /// otherwise, the default value for the type of the value parameter.
        /// </param>
        /// <returns>true if the <see cref="OrderedDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, false.</returns>
        public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => TryGetValue(key, out value, out _);
 
        /// <summary>Gets the value associated with the specified key.</summary>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="value">
        /// When this method returns, contains the value associated with the specified key, if the key is found;
        /// otherwise, the default value for the type of the value parameter.
        /// </param>
        /// <param name="index">The index of <paramref name="key"/> if found; otherwise, -1.</param>
        /// <returns>true if the <see cref="OrderedDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, false.</returns>
        public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value, out int index)
        {
            ThrowIfNull(key);
 
            // Find the key.
            index = IndexOf(key);
            if (index >= 0)
            {
                // It exists. Return its value.
                Debug.Assert(_entries is not null);
                value = _entries[index].Value;
                return true;
            }
 
            value = default;
            return false;
        }
 
        /// <summary>Pushes the entry into its bucket.</summary>
        /// <remarks>
        /// The bucket is a linked list by index into the <see cref="_entries"/> array.
        /// The new entry's <see cref="Entry.Next"/> is set to the bucket's current
        /// head, and then the new entry is made the new head.
        /// </remarks>
        private void PushEntryIntoBucket(ref Entry entry, int entryIndex)
        {
            ref int bucket = ref GetBucket(entry.HashCode);
            entry.Next = bucket - 1;
            bucket = entryIndex + 1;
        }
 
        /// <summary>Removes an entry from its bucket.</summary>
        private void RemoveEntryFromBucket(int entryIndex)
        {
            // We're only calling this method if there's an entry to be removed, in which case
            // entries must have been initialized.
            Entry[]? entries = _entries;
            Debug.Assert(entries is not null);
 
            // Get the entry to be removed and the associated bucket.
            Entry entry = entries[entryIndex];
            ref int bucket = ref GetBucket(entry.HashCode);
 
            if (bucket == entryIndex + 1)
            {
                // If the entry was at the head of its bucket list, to remove it from the list we
                // simply need to update the next entry in the list to be the new head.
                bucket = entry.Next + 1;
            }
            else
            {
                // The entry wasn't the head of the list. Walk the chain until we find the entry,
                // updating the previous entry's Next to point to this entry's Next.
                int i = bucket - 1;
                int collisionCount = 0;
                while (true)
                {
                    ref Entry e = ref entries[i];
                    if (e.Next == entryIndex)
                    {
                        e.Next = entry.Next;
                        return;
                    }
 
                    i = e.Next;
 
                    if (++collisionCount > entries.Length)
                    {
                        // We examined more entries than are actually in the list, which means there's a cycle
                        // that's caused by erroneous concurrent use.
                        ThrowHelper.ThrowConcurrentOperation();
                    }
                }
            }
        }
 
        /// <summary>
        /// Updates the bucket chain containing the specified entry (by index) to shift indices
        /// by the specified amount.
        /// </summary>
        /// <param name="entryIndex">The index of the target entry.</param>
        /// <param name="shiftAmount">
        /// 1 if this is part of an insert and the values are being shifted one higher.
        /// -1 if this is part of a remove and the values are being shifted one lower.
        /// </param>
        private void UpdateBucketIndex(int entryIndex, int shiftAmount)
        {
            Debug.Assert(shiftAmount is 1 or -1);
 
            Entry[]? entries = _entries;
            Debug.Assert(entries is not null);
 
            Entry entry = entries[entryIndex];
            ref int bucket = ref GetBucket(entry.HashCode);
 
            if (bucket == entryIndex + 1)
            {
                // If the entry was at the head of its bucket list, the only thing that needs to be updated
                // is the bucket head value itself, since no other entries' Next will be referencing this node.
                bucket += shiftAmount;
            }
            else
            {
                // The entry wasn't the head of the list. Walk the chain until we find the entry, updating
                // the previous entry's Next that's pointing to the target entry.
                int i = bucket - 1;
                int collisionCount = 0;
                while (true)
                {
                    ref Entry e = ref entries[i];
                    if (e.Next == entryIndex)
                    {
                        e.Next += shiftAmount;
                        return;
                    }
 
                    i = e.Next;
 
                    if (++collisionCount > entries.Length)
                    {
                        // We examined more entries than are actually in the list, which means there's a cycle
                        // that's caused by erroneous concurrent use.
                        ThrowHelper.ThrowConcurrentOperation();
                    }
                }
            }
        }
 
        /// <summary>
        /// Checks to see whether the collision count that occurred during lookup warrants upgrading to a non-randomized comparer,
        /// and does so if necessary.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Is no-op in certain targets")]
        private void RehashIfNecessary(uint collisionCount, Entry[] entries)
        {
#if SYSTEM_COLLECTIONS
            // If we exceeded the hash collision threshold and we're using a randomized comparer, rehash.
            // This is only ever done for string keys, so we can optimize it all away for value type keys.
            if (!typeof(TKey).IsValueType &&
                collisionCount > HashHelpers.HashCollisionThreshold &&
                _comparer is NonRandomizedStringEqualityComparer)
            {
                // Switch to a randomized comparer and rehash.
                Resize(entries.Length, forceNewHashCodes: true);
            }
#endif
        }
 
        /// <summary>Grow or shrink <see cref="_buckets"/> and <see cref="_entries"/> to the specified capacity.</summary>
        [MemberNotNull(nameof(_buckets))]
        [MemberNotNull(nameof(_entries))]
        private void Resize(int newSize, bool forceNewHashCodes = false)
        {
            Debug.Assert(!forceNewHashCodes || !typeof(TKey).IsValueType, "Value types never rehash.");
            Debug.Assert(newSize >= _count, "The requested size must accomodate all of the current elements.");
 
            // Create the new arrays. We allocate both prior to storing either; in case one of the allocation fails,
            // we want to avoid corrupting the data structure.
            int[] newBuckets = new int[newSize];
            Entry[] newEntries = new Entry[newSize];
            if (IntPtr.Size == 8)
            {
                // Any time the capacity changes, that impacts the divisor of modulo operations,
                // and we need to update our fast modulo multiplier.
                _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize);
            }
 
            // Copy the existing entries to the new entries array.
            int count = _count;
            if (_entries is not null)
            {
                Array.Copy(_entries, newEntries, count);
            }
 
#if SYSTEM_COLLECTIONS
            // If we're being asked to upgrade to a non-randomized comparer due to too many collisions, do so.
            if (!typeof(TKey).IsValueType && forceNewHashCodes)
            {
                // Store the original randomized comparer instead of the non-randomized one.
                Debug.Assert(_comparer is NonRandomizedStringEqualityComparer);
                IEqualityComparer<TKey> comparer = _comparer = (IEqualityComparer<TKey>)((NonRandomizedStringEqualityComparer)_comparer).GetUnderlyingEqualityComparer();
                Debug.Assert(_comparer is not null);
                Debug.Assert(_comparer is not NonRandomizedStringEqualityComparer);
 
                // Update all of the entries' hash codes based on the new comparer.
                for (int i = 0; i < count; i++)
                {
                    newEntries[i].HashCode = (uint)comparer.GetHashCode(newEntries[i].Key);
                }
            }
#endif
 
            // Now publish the buckets array. It's necessary to do this prior to the below loop,
            // as PushEntryIntoBucket will be populating _buckets.
            _buckets = newBuckets;
 
            // Populate the buckets.
            for (int i = 0; i < count; i++)
            {
                PushEntryIntoBucket(ref newEntries[i], i);
            }
 
            _entries = newEntries;
        }
 
        /// <summary>Gets the bucket assigned to the specified hash code.</summary>
        /// <remarks>
        /// Buckets are 1-based. This is so that the default initialized value of 0
        /// maps to -1 and is usable as a sentinel.
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ref int GetBucket(uint hashCode)
        {
            int[]? buckets = _buckets;
            Debug.Assert(buckets is not null);
 
            if (IntPtr.Size == 8)
            {
                return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)];
            }
            else
            {
                return ref buckets[(uint)hashCode % buckets.Length];
            }
        }
 
        /// <summary>Returns an enumerator that iterates through the <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        /// <returns>A <see cref="OrderedDictionary{TKey, TValue}.Enumerator"/> structure for the <see cref="OrderedDictionary{TKey, TValue}"/>.</returns>
        public Enumerator GetEnumerator() => new(this, useDictionaryEntry: false);
 
        /// <inheritdoc/>
        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() =>
            Count == 0 ? EnumerableHelpers.GetEmptyEnumerator<KeyValuePair<TKey, TValue>>() :
            GetEnumerator();
 
        /// <inheritdoc/>
        IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<KeyValuePair<TKey, TValue>>)this).GetEnumerator();
 
        /// <inheritdoc/>
        IDictionaryEnumerator IDictionary.GetEnumerator() => new Enumerator(this, useDictionaryEntry: true);
 
        /// <inheritdoc/>
        int IList<KeyValuePair<TKey, TValue>>.IndexOf(KeyValuePair<TKey, TValue> item)
        {
            ThrowIfNull(item.Key, nameof(item));
 
            int index = IndexOf(item.Key);
            if (index >= 0)
            {
                Debug.Assert(_entries is not null);
                if (EqualityComparer<TValue>.Default.Equals(item.Value, _entries[index].Value))
                {
                    return index;
                }
            }
 
            return -1;
        }
 
        /// <inheritdoc/>
        void IList<KeyValuePair<TKey, TValue>>.Insert(int index, KeyValuePair<TKey, TValue> item) => Insert(index, item.Key, item.Value);
 
        /// <inheritdoc/>
        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
 
        /// <inheritdoc/>
        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
        {
            ThrowIfNull(item.Key, nameof(item));
 
            return
                TryGetValue(item.Key, out TValue? value) &&
                EqualityComparer<TValue>.Default.Equals(value, item.Value);
        }
 
        /// <inheritdoc/>
        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            ThrowIfNull(array);
            ThrowIfNegative(arrayIndex);
            if (array.Length - arrayIndex < _count)
            {
                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
            }
 
            for (int i = 0; i < _count; i++)
            {
                ref Entry entry = ref _entries![i];
                array[arrayIndex++] = new(entry.Key, entry.Value);
            }
        }
 
        /// <inheritdoc/>
        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) =>
            TryGetValue(item.Key, out TValue? value) &&
            EqualityComparer<TValue>.Default.Equals(value, item.Value) &&
            Remove(item.Key);
 
        /// <inheritdoc/>
        void IDictionary.Add(object key, object? value)
        {
            ThrowIfNull(key);
            if (default(TValue) is not null)
            {
                ThrowIfNull(value);
            }
 
            if (key is not TKey tkey)
            {
                throw new ArgumentException(SR.Format(SR.Arg_WrongType, key, typeof(TKey)), nameof(key));
            }
 
            if (default(TValue) is not null)
            {
                ThrowIfNull(value);
            }
 
            TValue tvalue = default!;
            if (value is not null)
            {
                if (value is not TValue temp)
                {
                    throw new ArgumentException(SR.Format(SR.Arg_WrongType, value, typeof(TValue)), nameof(value));
                }
 
                tvalue = temp;
            }
 
            Add(tkey, tvalue);
        }
 
        /// <inheritdoc/>
        bool IDictionary.Contains(object key)
        {
            ThrowIfNull(key);
 
            return key is TKey tkey && ContainsKey(tkey);
        }
 
        /// <inheritdoc/>
        void IDictionary.Remove(object key)
        {
            ThrowIfNull(key);
 
            if (key is TKey tkey)
            {
                Remove(tkey);
            }
        }
 
        /// <inheritdoc/>
        void ICollection.CopyTo(Array array, int index)
        {
            ThrowIfNull(array);
 
            if (array.Rank != 1)
            {
                throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
            }
 
            if (array.GetLowerBound(0) != 0)
            {
                throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
            }
 
            ThrowIfNegative(index);
 
            if (array.Length - index < _count)
            {
                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
            }
 
            if (array is KeyValuePair<TKey, TValue>[] tarray)
            {
                ((ICollection<KeyValuePair<TKey, TValue>>)this).CopyTo(tarray, index);
            }
            else
            {
                try
                {
                    if (array is not object[] objects)
                    {
                        throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
                    }
 
                    foreach (KeyValuePair<TKey, TValue> pair in this)
                    {
                        objects[index++] = pair;
                    }
                }
                catch (ArrayTypeMismatchException)
                {
                    throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
                }
            }
        }
 
        /// <inheritdoc/>
        int IList.Add(object? value)
        {
            if (value is not KeyValuePair<TKey, TValue> pair)
            {
                throw new ArgumentException(SR.Format(SR.Arg_WrongType, value, typeof(KeyValuePair<TKey, TValue>)), nameof(value));
            }
 
            Add(pair.Key, pair.Value);
            return Count - 1;
        }
 
        /// <inheritdoc/>
        bool IList.Contains(object? value) =>
            value is KeyValuePair<TKey, TValue> pair &&
            TryGetValue(pair.Key, out TValue? v) &&
            EqualityComparer<TValue>.Default.Equals(v, pair.Value);
 
        /// <inheritdoc/>
        int IList.IndexOf(object? value)
        {
            if (value is KeyValuePair<TKey, TValue> pair)
            {
                return ((IList<KeyValuePair<TKey, TValue>>)this).IndexOf(pair);
            }
 
            return -1;
        }
 
        /// <inheritdoc/>
        void IList.Insert(int index, object? value)
        {
            if (value is not KeyValuePair<TKey, TValue> pair)
            {
                throw new ArgumentException(SR.Format(SR.Arg_WrongType, value, typeof(KeyValuePair<TKey, TValue>)), nameof(value));
            }
 
            Insert(index, pair.Key, pair.Value);
        }
 
        /// <inheritdoc/>
        void IList.Remove(object? value)
        {
            if (value is KeyValuePair<TKey, TValue> pair)
            {
                ((ICollection<KeyValuePair<TKey, TValue>>)this).Remove(pair);
            }
        }
 
        /// <summary>Represents a key/value pair in the dictionary.</summary>
        private struct Entry
        {
            /// <summary>The index of the next entry in the chain, or -1 if this is the last entry in the chain.</summary>
            public int Next;
            /// <summary>Cached hash code of <see cref="Key"/>.</summary>
            public uint HashCode;
            /// <summary>The key.</summary>
            public TKey Key;
            /// <summary>The value associated with <see cref="Key"/>.</summary>
            public TValue Value;
        }
 
        /// <summary>Enumerates the elements of a <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        [StructLayout(LayoutKind.Auto)]
        public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDictionaryEnumerator
        {
            /// <summary>The dictionary being enumerated.</summary>
            private readonly OrderedDictionary<TKey, TValue> _dictionary;
            /// <summary>A snapshot of the dictionary's version when enumeration began.</summary>
            private readonly int _version;
            /// <summary>Whether Current should be a DictionaryEntry.</summary>
            private readonly bool _useDictionaryEntry;
            /// <summary>The current index.</summary>
            private int _index;
 
            /// <summary>Initialize the enumerator.</summary>
            internal Enumerator(OrderedDictionary<TKey, TValue> dictionary, bool useDictionaryEntry)
            {
                _dictionary = dictionary;
                _version = _dictionary._version;
                _useDictionaryEntry = useDictionaryEntry;
            }
 
            /// <inheritdoc/>
            public KeyValuePair<TKey, TValue> Current { get; private set; }
 
            /// <inheritdoc/>
            readonly object IEnumerator.Current => _useDictionaryEntry ?
                new DictionaryEntry(Current.Key, Current.Value) :
                Current;
 
            /// <inheritdoc/>
            readonly DictionaryEntry IDictionaryEnumerator.Entry => new(Current.Key, Current.Value);
 
            /// <inheritdoc/>
            readonly object IDictionaryEnumerator.Key => Current.Key;
 
            /// <inheritdoc/>
            readonly object? IDictionaryEnumerator.Value => Current.Value;
 
            /// <inheritdoc/>
            public bool MoveNext()
            {
                OrderedDictionary<TKey, TValue> dictionary = _dictionary;
 
                if (_version != dictionary._version)
                {
                    ThrowHelper.ThrowVersionCheckFailed();
                }
 
                if (_index < dictionary._count)
                {
                    Debug.Assert(dictionary._entries is not null);
                    ref Entry entry = ref dictionary._entries[_index];
                    Current = new KeyValuePair<TKey, TValue>(entry.Key, entry.Value);
                    _index++;
                    return true;
                }
 
                Current = default;
                return false;
            }
 
            /// <inheritdoc/>
            void IEnumerator.Reset()
            {
                if (_version != _dictionary._version)
                {
                    ThrowHelper.ThrowVersionCheckFailed();
                }
 
                _index = 0;
                Current = default;
            }
 
            /// <inheritdoc/>
            readonly void IDisposable.Dispose() { }
        }
 
        /// <summary>Represents the collection of keys in a <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
        [DebuggerDisplay("Count = {Count}")]
        public sealed class KeyCollection : IList<TKey>, IReadOnlyList<TKey>, IList
        {
            /// <summary>The dictionary whose keys are being exposed.</summary>
            private readonly OrderedDictionary<TKey, TValue> _dictionary;
 
            /// <summary>Initialize the collection wrapper.</summary>
            internal KeyCollection(OrderedDictionary<TKey, TValue> dictionary) => _dictionary = dictionary;
 
            /// <inheritdoc/>
            public int Count => _dictionary.Count;
 
            /// <inheritdoc/>
            bool ICollection<TKey>.IsReadOnly => true;
 
            /// <inheritdoc/>
            bool IList.IsReadOnly => true;
 
            /// <inheritdoc/>
            bool IList.IsFixedSize => false;
 
            /// <inheritdoc/>
            bool ICollection.IsSynchronized => false;
 
            /// <inheritdoc/>
            object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
 
            /// <inheritdoc/>
            public bool Contains(TKey key) => _dictionary.ContainsKey(key);
 
            /// <inheritdoc/>
            bool IList.Contains(object? value) => value is TKey key && Contains(key);
 
            /// <inheritdoc/>
            public void CopyTo(TKey[] array, int arrayIndex)
            {
                ThrowIfNull(array);
                ThrowIfNegative(arrayIndex);
 
                OrderedDictionary<TKey, TValue> dictionary = _dictionary;
                int count = dictionary._count;
 
                if (array.Length - arrayIndex < count)
                {
                    throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall, nameof(array));
                }
 
                Entry[]? entries = dictionary._entries;
                for (int i = 0; i < count; i++)
                {
                    Debug.Assert(entries is not null);
                    array[arrayIndex++] = entries[i].Key;
                }
            }
 
            /// <inheritdoc/>
            void ICollection.CopyTo(Array array, int index)
            {
                ThrowIfNull(array);
 
                if (array.Rank != 1)
                {
                    throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
                }
 
                if (array.GetLowerBound(0) != 0)
                {
                    throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
                }
 
                ThrowIfNegative(index);
 
                if (array.Length - index < _dictionary.Count)
                {
                    throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
                }
 
                if (array is TKey[] keys)
                {
                    CopyTo(keys, index);
                }
                else
                {
                    try
                    {
                        if (array is not object?[] objects)
                        {
                            throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
                        }
 
                        foreach (TKey key in this)
                        {
                            objects[index++] = key;
                        }
                    }
                    catch (ArrayTypeMismatchException)
                    {
                        throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
                    }
                }
            }
 
            /// <inheritdoc/>
            TKey IList<TKey>.this[int index]
            {
                get => _dictionary.GetAt(index).Key;
                set => throw new NotSupportedException();
            }
 
            /// <inheritdoc/>
            object? IList.this[int index]
            {
                get => _dictionary.GetAt(index).Key;
                set => throw new NotSupportedException();
            }
 
            /// <inheritdoc/>
            TKey IReadOnlyList<TKey>.this[int index] => _dictionary.GetAt(index).Key;
 
            /// <summary>Returns an enumerator that iterates through the <see cref="OrderedDictionary{TKey, TValue}.KeyCollection"/>.</summary>
            /// <returns>A <see cref="OrderedDictionary{TKey, TValue}.KeyCollection.Enumerator"/> for the <see cref="OrderedDictionary{TKey, TValue}.KeyCollection"/>.</returns>
            public Enumerator GetEnumerator() => new(_dictionary);
 
            /// <inheritdoc/>
            IEnumerator<TKey> IEnumerable<TKey>.GetEnumerator() =>
                Count == 0 ? EnumerableHelpers.GetEmptyEnumerator<TKey>() :
                GetEnumerator();
 
            /// <inheritdoc/>
            IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<TKey>)this).GetEnumerator();
 
            /// <inheritdoc/>
            int IList<TKey>.IndexOf(TKey item) => _dictionary.IndexOf(item);
 
            /// <inheritdoc/>
            void ICollection<TKey>.Add(TKey item) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void ICollection<TKey>.Clear() => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList<TKey>.Insert(int index, TKey item) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            bool ICollection<TKey>.Remove(TKey item) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList<TKey>.RemoveAt(int index) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            int IList.Add(object? value) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList.Clear() => throw new NotSupportedException();
 
            /// <inheritdoc/>
            int IList.IndexOf(object? value) => value is TKey key ? _dictionary.IndexOf(key) : -1;
 
            /// <inheritdoc/>
            void IList.Insert(int index, object? value) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList.Remove(object? value) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList.RemoveAt(int index) => throw new NotSupportedException();
 
            /// <summary>Enumerates the elements of a <see cref="OrderedDictionary{TKey, TValue}.KeyCollection"/>.</summary>
            public struct Enumerator : IEnumerator<TKey>
            {
                /// <summary>The dictionary's enumerator.</summary>
                private OrderedDictionary<TKey, TValue>.Enumerator _enumerator;
 
                /// <summary>Initialize the enumerator.</summary>
                internal Enumerator(OrderedDictionary<TKey, TValue> dictionary) => _enumerator = dictionary.GetEnumerator();
 
                /// <inheritdoc/>
                public TKey Current => _enumerator.Current.Key;
 
                /// <inheritdoc/>
                object IEnumerator.Current => Current;
 
                /// <inheritdoc/>
                public bool MoveNext() => _enumerator.MoveNext();
 
                /// <inheritdoc/>
                void IEnumerator.Reset() => EnumerableHelpers.Reset(ref _enumerator);
 
                /// <inheritdoc/>
                readonly void IDisposable.Dispose() { }
            }
        }
 
        /// <summary>Represents the collection of values in a <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
        [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
        [DebuggerDisplay("Count = {Count}")]
        public sealed class ValueCollection : IList<TValue>, IReadOnlyList<TValue>, IList
        {
            /// <summary>The dictionary whose values are being exposed.</summary>
            private readonly OrderedDictionary<TKey, TValue> _dictionary;
 
            /// <summary>Initialize the collection wrapper.</summary>
            internal ValueCollection(OrderedDictionary<TKey, TValue> dictionary) => _dictionary = dictionary;
 
            /// <inheritdoc/>
            public int Count => _dictionary.Count;
 
            /// <inheritdoc/>
            bool ICollection<TValue>.IsReadOnly => true;
 
            /// <inheritdoc/>
            bool IList.IsReadOnly => true;
 
            /// <inheritdoc/>
            bool IList.IsFixedSize => false;
 
            /// <inheritdoc/>
            bool ICollection.IsSynchronized => false;
 
            /// <inheritdoc/>
            object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
 
            /// <inheritdoc/>
            public void CopyTo(TValue[] array, int arrayIndex)
            {
                ThrowIfNull(array);
                ThrowIfNegative(arrayIndex);
 
                OrderedDictionary<TKey, TValue> dictionary = _dictionary;
                int count = dictionary._count;
 
                if (array.Length - arrayIndex < count)
                {
                    throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall, nameof(array));
                }
 
                Entry[]? entries = dictionary._entries;
                for (int i = 0; i < count; i++)
                {
                    Debug.Assert(entries is not null);
                    array[arrayIndex++] = entries[i].Value;
                }
            }
 
            /// <summary>Returns an enumerator that iterates through the <see cref="OrderedDictionary{TKey, TValue}.ValueCollection"/>.</summary>
            /// <returns>A <see cref="OrderedDictionary{TKey, TValue}.ValueCollection.Enumerator"/> for the <see cref="OrderedDictionary{TKey, TValue}.ValueCollection"/>.</returns>
            public Enumerator GetEnumerator() => new(_dictionary);
 
            /// <inheritdoc/>
            TValue IList<TValue>.this[int index]
            {
                get => _dictionary.GetAt(index).Value;
                set => throw new NotSupportedException();
            }
 
            /// <inheritdoc/>
            TValue IReadOnlyList<TValue>.this[int index] => _dictionary.GetAt(index).Value;
 
            /// <inheritdoc/>
            object? IList.this[int index]
            {
                get => _dictionary.GetAt(index).Value;
                set => throw new NotSupportedException();
            }
 
            /// <inheritdoc/>
            bool ICollection<TValue>.Contains(TValue item) => _dictionary.ContainsValue(item);
 
            /// <inheritdoc/>
            IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() =>
                Count == 0 ? EnumerableHelpers.GetEmptyEnumerator<TValue>() :
                GetEnumerator();
 
            /// <inheritdoc/>
            IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<TValue>)this).GetEnumerator();
 
            /// <inheritdoc/>
            int IList<TValue>.IndexOf(TValue item)
            {
                Entry[]? entries = _dictionary._entries;
                if (entries is not null)
                {
                    int count = _dictionary._count;
                    for (int i = 0; i < count; i++)
                    {
                        if (EqualityComparer<TValue>.Default.Equals(item, entries[i].Value))
                        {
                            return i;
                        }
                    }
                }
 
                return -1;
            }
 
            /// <inheritdoc/>
            void ICollection<TValue>.Add(TValue item) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void ICollection<TValue>.Clear() => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList<TValue>.Insert(int index, TValue item) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            bool ICollection<TValue>.Remove(TValue item) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList<TValue>.RemoveAt(int index) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            int IList.Add(object? value) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList.Clear() => throw new NotSupportedException();
 
            /// <inheritdoc/>
            bool IList.Contains(object? value) =>
                value is null && default(TValue) is null ?
                    _dictionary.ContainsValue(default!) :
                    value is TValue tvalue && _dictionary.ContainsValue(tvalue);
 
            /// <inheritdoc/>
            int IList.IndexOf(object? value)
            {
                Entry[]? entries = _dictionary._entries;
                if (entries is not null)
                {
                    int count = _dictionary._count;
 
                    if (value is null && default(TValue) is null)
                    {
                        for (int i = 0; i < count; i++)
                        {
                            if (entries[i].Value is null)
                            {
                                return i;
                            }
                        }
                    }
                    else if (value is TValue tvalue)
                    {
                        for (int i = 0; i < count; i++)
                        {
                            if (EqualityComparer<TValue>.Default.Equals(tvalue, entries[i].Value))
                            {
                                return i;
                            }
                        }
                    }
                }
 
                return -1;
            }
 
            /// <inheritdoc/>
            void IList.Insert(int index, object? value) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList.Remove(object? value) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void IList.RemoveAt(int index) => throw new NotSupportedException();
 
            /// <inheritdoc/>
            void ICollection.CopyTo(Array array, int index)
            {
                ThrowIfNull(array);
 
                if (array.Rank != 1)
                {
                    throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
                }
 
                if (array.GetLowerBound(0) != 0)
                {
                    throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
                }
 
                ThrowIfNegative(index);
 
                if (array.Length - index < _dictionary.Count)
                {
                    throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
                }
 
                if (array is TValue[] values)
                {
                    CopyTo(values, index);
                }
                else
                {
                    try
                    {
                        if (array is not object?[] objects)
                        {
                            throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
                        }
 
                        foreach (TValue value in this)
                        {
                            objects[index++] = value;
                        }
                    }
                    catch (ArrayTypeMismatchException)
                    {
                        throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
                    }
                }
            }
 
            /// <summary>Enumerates the elements of a <see cref="OrderedDictionary{TKey, TValue}.ValueCollection"/>.</summary>
            public struct Enumerator : IEnumerator<TValue>
            {
                /// <summary>The dictionary's enumerator.</summary>
                private OrderedDictionary<TKey, TValue>.Enumerator _enumerator;
 
                /// <summary>Initialize the enumerator.</summary>
                internal Enumerator(OrderedDictionary<TKey, TValue> dictionary) => _enumerator = dictionary.GetEnumerator();
 
                /// <inheritdoc/>
                public TValue Current => _enumerator.Current.Value;
 
                /// <inheritdoc/>
                object? IEnumerator.Current => Current;
 
                /// <inheritdoc/>
                public bool MoveNext() => _enumerator.MoveNext();
 
                /// <inheritdoc/>
                void IEnumerator.Reset() => EnumerableHelpers.Reset(ref _enumerator);
 
                /// <inheritdoc/>
                readonly void IDisposable.Dispose() { }
            }
        }
    }
 
    /// <summary>Used to control behavior of insertion into a <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
    /// <remarks>Not nested in <see cref="OrderedDictionary{TKey, TValue}"/> to avoid multiple generic instantiations.</remarks>
    internal enum InsertionBehavior
    {
        /// <summary>Skip the insertion operation.</summary>
        IgnoreInsertion = 0,
 
        /// <summary>Specifies that an existing entry with the same key should be overwritten if encountered.</summary>
        OverwriteExisting = 1,
 
        /// <summary>Specifies that if an existing entry with the same key is encountered, an exception should be thrown.</summary>
        ThrowOnExisting = 2
    }
}