diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs
index f73a2d3193f32f..00b05b11cc7596 100644
--- a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs
+++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs
@@ -8,6 +8,10 @@ namespace System.Collections.Generic
///
internal static partial class EnumerableHelpers
{
+ /// Calls Reset on an enumerator instance.
+ /// Enables Reset to be called without boxing on a struct enumerator that lacks a public Reset.
+ internal static void Reset(ref T enumerator) where T : IEnumerator => enumerator.Reset();
+
/// Gets an enumerator singleton for an empty collection.
internal static IEnumerator GetEmptyEnumerator() =>
((IEnumerable)Array.Empty()).GetEnumerator();
diff --git a/src/libraries/Common/tests/System/Collections/IDictionary.NonGeneric.Tests.cs b/src/libraries/Common/tests/System/Collections/IDictionary.NonGeneric.Tests.cs
index adca2780d96d5a..3ce765467e5129 100644
--- a/src/libraries/Common/tests/System/Collections/IDictionary.NonGeneric.Tests.cs
+++ b/src/libraries/Common/tests/System/Collections/IDictionary.NonGeneric.Tests.cs
@@ -491,7 +491,6 @@ public virtual void IDictionary_NonGeneric_Values_Enumeration_ParentDictionaryMo
{
Assert.Throws(() => valuesEnum.MoveNext());
Assert.Throws(() => valuesEnum.Reset());
- Assert.Throws(() => valuesEnum.Current);
}
else
{
@@ -832,7 +831,7 @@ public virtual void IDictionary_NonGeneric_IDictionaryEnumerator_Current_AfterEn
object current, key, value, entry;
IDictionaryEnumerator enumerator = NonGenericIDictionaryFactory(count).GetEnumerator();
while (enumerator.MoveNext()) ;
- if (Enumerator_Current_UndefinedOperation_Throws)
+ if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throw : Enumerator_Current_UndefinedOperation_Throws)
{
Assert.Throws(() => enumerator.Current);
Assert.Throws(() => enumerator.Key);
diff --git a/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs b/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs
index a441cf4d787330..b4877c902d07fc 100644
--- a/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs
+++ b/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs
@@ -60,6 +60,18 @@ public abstract partial class IEnumerable_NonGeneric_Tests : TestBase
///
protected virtual bool Enumerator_Current_UndefinedOperation_Throws => false;
+ ///
+ /// When calling Current of the empty enumerator before the first MoveNext, after the end of the collection,
+ /// or after modification of the enumeration, the resulting behavior is undefined. Tests are included
+ /// to cover two behavioral scenarios:
+ /// - Throwing an InvalidOperationException
+ /// - Returning an undefined value.
+ ///
+ /// If this property is set to true, the tests ensure that the exception is thrown. The default value is
+ /// false.
+ ///
+ protected virtual bool Enumerator_Empty_Current_UndefinedOperation_Throw => Enumerator_Current_UndefinedOperation_Throws;
+
///
/// When calling MoveNext or Reset after modification of the enumeration, the resulting behavior is
/// undefined. Tests are included to cover two behavioral scenarios:
@@ -305,7 +317,7 @@ public virtual void Enumerator_Current_BeforeFirstMoveNext_UndefinedBehavior(int
object current;
IEnumerable enumerable = NonGenericIEnumerableFactory(count);
IEnumerator enumerator = enumerable.GetEnumerator();
- if (Enumerator_Current_UndefinedOperation_Throws)
+ if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throw : Enumerator_Current_UndefinedOperation_Throws)
Assert.Throws(() => enumerator.Current);
else
current = enumerator.Current;
@@ -319,7 +331,7 @@ public virtual void Enumerator_Current_AfterEndOfEnumerable_UndefinedBehavior(in
IEnumerable enumerable = NonGenericIEnumerableFactory(count);
IEnumerator enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext()) ;
- if (Enumerator_Current_UndefinedOperation_Throws)
+ if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throw : Enumerator_Current_UndefinedOperation_Throws)
Assert.Throws(() => enumerator.Current);
else
current = enumerator.Current;
@@ -336,7 +348,7 @@ public virtual void Enumerator_Current_ModifiedDuringEnumeration_UndefinedBehavi
IEnumerator enumerator = enumerable.GetEnumerator();
if (ModifyEnumerable(enumerable))
{
- if (Enumerator_Current_UndefinedOperation_Throws)
+ if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throw : Enumerator_Current_UndefinedOperation_Throws)
Assert.Throws(() => enumerator.Current);
else
current = enumerator.Current;
diff --git a/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs b/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs
index 44f67e0ca24f83..811ac67950ac0c 100644
--- a/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs
+++ b/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs
@@ -103,8 +103,15 @@ protected override IEnumerable GetModifyEnumerables(ModifyOper
public void IList_Generic_ItemGet_NegativeIndex_ThrowsException(int count)
{
IList list = GenericIListFactory(count);
+
Assert.Throws(IList_Generic_Item_InvalidIndex_ThrowType, () => list[-1]);
Assert.Throws(IList_Generic_Item_InvalidIndex_ThrowType, () => list[int.MinValue]);
+
+ if (list is IReadOnlyList rol)
+ {
+ Assert.Throws(IList_Generic_Item_InvalidIndex_ThrowType, () => rol[-1]);
+ Assert.Throws(IList_Generic_Item_InvalidIndex_ThrowType, () => rol[int.MinValue]);
+ }
}
[Theory]
@@ -112,8 +119,15 @@ public void IList_Generic_ItemGet_NegativeIndex_ThrowsException(int count)
public void IList_Generic_ItemGet_IndexGreaterThanListCount_ThrowsException(int count)
{
IList list = GenericIListFactory(count);
+
Assert.Throws(IList_Generic_Item_InvalidIndex_ThrowType, () => list[count]);
Assert.Throws(IList_Generic_Item_InvalidIndex_ThrowType, () => list[count + 1]);
+
+ if (list is IReadOnlyList rol)
+ {
+ Assert.Throws(IList_Generic_Item_InvalidIndex_ThrowType, () => rol[count]);
+ Assert.Throws(IList_Generic_Item_InvalidIndex_ThrowType, () => rol[count + 1]);
+ }
}
[Theory]
@@ -122,7 +136,15 @@ public void IList_Generic_ItemGet_ValidGetWithinListBounds(int count)
{
IList list = GenericIListFactory(count);
T result;
+
Assert.All(Enumerable.Range(0, count), index => result = list[index]);
+ Assert.All(Enumerable.Range(0, count), index => Assert.Equal(list[index], list[index]));
+
+ if (list is IReadOnlyList rol)
+ {
+ Assert.All(Enumerable.Range(0, count), index => result = rol[index]);
+ Assert.All(Enumerable.Range(0, count), index => Assert.Equal(rol[index], rol[index]));
+ }
}
#endregion
@@ -369,7 +391,7 @@ public void IList_Generic_IndexOf_InvalidValue(int count)
[MemberData(nameof(ValidCollectionSizes))]
public void IList_Generic_IndexOf_ReturnsFirstMatchingValue(int count)
{
- if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported)
+ if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported && DuplicateValuesAllowed)
{
IList list = GenericIListFactory(count);
foreach (T duplicate in list.ToList()) // hard copies list to circumvent enumeration error
diff --git a/src/libraries/Common/tests/System/Collections/IList.NonGeneric.Tests.cs b/src/libraries/Common/tests/System/Collections/IList.NonGeneric.Tests.cs
index b823b530b33839..2b88b3ef7561f0 100644
--- a/src/libraries/Common/tests/System/Collections/IList.NonGeneric.Tests.cs
+++ b/src/libraries/Common/tests/System/Collections/IList.NonGeneric.Tests.cs
@@ -83,6 +83,17 @@ protected virtual object CreateT(int seed)
///
protected virtual bool IList_CurrentAfterAdd_Throws => Enumerator_Current_UndefinedOperation_Throws;
+ ///
+ /// When calling Current of the empty enumerator after the end of the list and list is extended by new items.
+ /// Tests are included to cover two behavioral scenarios:
+ /// - Throwing an InvalidOperationException
+ /// - Returning an undefined value.
+ ///
+ /// If this property is set to true, the tests ensure that the exception is thrown. The default value is
+ /// the same as Enumerator_Current_UndefinedOperation_Throws.
+ ///
+ protected virtual bool IList_Empty_CurrentAfterAdd_Throws => Enumerator_Empty_Current_UndefinedOperation_Throw;
+
#endregion
#region ICollection Helper Methods
@@ -697,7 +708,7 @@ public void IList_NonGeneric_IndexOf_InvalidValue(int count)
[MemberData(nameof(ValidCollectionSizes))]
public void IList_NonGeneric_IndexOf_ReturnsFirstMatchingValue(int count)
{
- if (!IsReadOnly && !ExpectedFixedSize)
+ if (!IsReadOnly && !ExpectedFixedSize && DuplicateValuesAllowed)
{
IList list = NonGenericIListFactory(count);
@@ -1084,7 +1095,7 @@ public void IList_NonGeneric_CurrentAtEnd_AfterAdd(int count)
IEnumerator enumerator = collection.GetEnumerator();
while (enumerator.MoveNext()) ; // Go to end of enumerator
- if (Enumerator_Current_UndefinedOperation_Throws)
+ if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throw : Enumerator_Current_UndefinedOperation_Throws)
{
Assert.Throws(() => enumerator.Current); // Enumerator.Current should fail
}
@@ -1099,7 +1110,7 @@ public void IList_NonGeneric_CurrentAtEnd_AfterAdd(int count)
{
collection.Add(CreateT(seed++));
- if (IList_CurrentAfterAdd_Throws)
+ if (count == 0 ? IList_Empty_CurrentAfterAdd_Throws : IList_CurrentAfterAdd_Throws)
{
Assert.Throws(() => enumerator.Current); // Enumerator.Current should fail
}
diff --git a/src/libraries/System.Collections/ref/System.Collections.cs b/src/libraries/System.Collections/ref/System.Collections.cs
index 70f4abe1a4bf0d..3625b253780b12 100644
--- a/src/libraries/System.Collections/ref/System.Collections.cs
+++ b/src/libraries/System.Collections/ref/System.Collections.cs
@@ -106,6 +106,175 @@ void System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(obj
void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
}
}
+ public partial class OrderedDictionary : System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.Generic.IList>, System.Collections.Generic.IReadOnlyCollection>, System.Collections.Generic.IReadOnlyDictionary, System.Collections.Generic.IReadOnlyList>, System.Collections.ICollection, System.Collections.IDictionary, System.Collections.IEnumerable, System.Collections.IList where TKey : notnull
+ {
+ public OrderedDictionary() { }
+ public OrderedDictionary(System.Collections.Generic.IDictionary dictionary) { }
+ public OrderedDictionary(System.Collections.Generic.IDictionary dictionary, System.Collections.Generic.IEqualityComparer? comparer) { }
+ public OrderedDictionary(System.Collections.Generic.IEnumerable> collection) { }
+ public OrderedDictionary(System.Collections.Generic.IEnumerable> collection, System.Collections.Generic.IEqualityComparer? comparer) { }
+ public OrderedDictionary(System.Collections.Generic.IEqualityComparer? comparer) { }
+ public OrderedDictionary(int capacity) { }
+ public OrderedDictionary(int capacity, System.Collections.Generic.IEqualityComparer? comparer) { }
+ public int Capacity { get { throw null; } }
+ public System.Collections.Generic.IEqualityComparer Comparer { get { throw null; } }
+ public int Count { get { throw null; } }
+ public TValue this[TKey key] { get { throw null; } set { } }
+ public System.Collections.Generic.OrderedDictionary.KeyCollection Keys { get { throw null; } }
+ bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } }
+ System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Keys { get { throw null; } }
+ System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Values { get { throw null; } }
+ System.Collections.Generic.KeyValuePair System.Collections.Generic.IList>.this[int index] { get { throw null; } set { } }
+ System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } }
+ System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } }
+ System.Collections.Generic.KeyValuePair System.Collections.Generic.IReadOnlyList>.this[int index] { get { throw null; } }
+ bool System.Collections.ICollection.IsSynchronized { get { throw null; } }
+ object System.Collections.ICollection.SyncRoot { get { throw null; } }
+ bool System.Collections.IDictionary.IsFixedSize { get { throw null; } }
+ bool System.Collections.IDictionary.IsReadOnly { get { throw null; } }
+ object? System.Collections.IDictionary.this[object key] { get { throw null; } set { } }
+ System.Collections.ICollection System.Collections.IDictionary.Keys { get { throw null; } }
+ System.Collections.ICollection System.Collections.IDictionary.Values { get { throw null; } }
+ bool System.Collections.IList.IsFixedSize { get { throw null; } }
+ bool System.Collections.IList.IsReadOnly { get { throw null; } }
+ object? System.Collections.IList.this[int index] { get { throw null; } set { } }
+ public System.Collections.Generic.OrderedDictionary.ValueCollection Values { get { throw null; } }
+ public void Add(TKey key, TValue value) { }
+ public void Clear() { }
+ public bool ContainsKey(TKey key) { throw null; }
+ public bool ContainsValue(TValue value) { throw null; }
+ public int EnsureCapacity(int capacity) { throw null; }
+ public System.Collections.Generic.KeyValuePair GetAt(int index) { throw null; }
+ public System.Collections.Generic.OrderedDictionary.Enumerator GetEnumerator() { throw null; }
+ public int IndexOf(TKey key) { throw null; }
+ public void Insert(int index, TKey key, TValue value) { }
+ public bool Remove(TKey key) { throw null; }
+ public bool Remove(TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; }
+ public void RemoveAt(int index) { }
+ public void SetAt(int index, TKey key, TValue value) { }
+ public void SetAt(int index, TValue value) { }
+ void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) { }
+ bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) { throw null; }
+ void System.Collections.Generic.ICollection>.CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { }
+ bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair item) { throw null; }
+ System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; }
+ int System.Collections.Generic.IList>.IndexOf(System.Collections.Generic.KeyValuePair item) { throw null; }
+ void System.Collections.Generic.IList>.Insert(int index, System.Collections.Generic.KeyValuePair item) { }
+ void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
+ void System.Collections.IDictionary.Add(object key, object? value) { }
+ bool System.Collections.IDictionary.Contains(object key) { throw null; }
+ System.Collections.IDictionaryEnumerator System.Collections.IDictionary.GetEnumerator() { throw null; }
+ void System.Collections.IDictionary.Remove(object key) { }
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+ int System.Collections.IList.Add(object? value) { throw null; }
+ bool System.Collections.IList.Contains(object? value) { throw null; }
+ int System.Collections.IList.IndexOf(object? value) { throw null; }
+ void System.Collections.IList.Insert(int index, object? value) { }
+ void System.Collections.IList.Remove(object? value) { }
+ public void TrimExcess() { }
+ public void TrimExcess(int capacity) { }
+ public bool TryAdd(TKey key, TValue value) { throw null; }
+ public bool TryGetValue(TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; }
+ public partial struct Enumerator : System.Collections.Generic.IEnumerator>, System.Collections.IDictionaryEnumerator, System.Collections.IEnumerator, System.IDisposable
+ {
+ private object _dummy;
+ private int _dummyPrimitive;
+ public readonly System.Collections.Generic.KeyValuePair Current { get { throw null; } }
+ System.Collections.DictionaryEntry System.Collections.IDictionaryEnumerator.Entry { get { throw null; } }
+ object System.Collections.IDictionaryEnumerator.Key { get { throw null; } }
+ object? System.Collections.IDictionaryEnumerator.Value { get { throw null; } }
+ object System.Collections.IEnumerator.Current { get { throw null; } }
+ public bool MoveNext() { throw null; }
+ void System.Collections.IEnumerator.Reset() { }
+ void System.IDisposable.Dispose() { }
+ }
+ public sealed partial class KeyCollection : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IList
+ {
+ internal KeyCollection() { }
+ public int Count { get { throw null; } }
+ bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } }
+ TKey System.Collections.Generic.IList.this[int index] { get { throw null; } set { } }
+ TKey System.Collections.Generic.IReadOnlyList.this[int index] { get { throw null; } }
+ bool System.Collections.ICollection.IsSynchronized { get { throw null; } }
+ object System.Collections.ICollection.SyncRoot { get { throw null; } }
+ bool System.Collections.IList.IsFixedSize { get { throw null; } }
+ bool System.Collections.IList.IsReadOnly { get { throw null; } }
+ object? System.Collections.IList.this[int index] { get { throw null; } set { } }
+ public bool Contains(TKey key) { throw null; }
+ public void CopyTo(TKey[] array, int arrayIndex) { }
+ public System.Collections.Generic.OrderedDictionary.KeyCollection.Enumerator GetEnumerator() { throw null; }
+ void System.Collections.Generic.ICollection.Add(TKey item) { }
+ void System.Collections.Generic.ICollection.Clear() { }
+ bool System.Collections.Generic.ICollection.Remove(TKey item) { throw null; }
+ System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; }
+ int System.Collections.Generic.IList.IndexOf(TKey item) { throw null; }
+ void System.Collections.Generic.IList.Insert(int index, TKey item) { }
+ void System.Collections.Generic.IList.RemoveAt(int index) { }
+ void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+ int System.Collections.IList.Add(object? value) { throw null; }
+ void System.Collections.IList.Clear() { }
+ bool System.Collections.IList.Contains(object? value) { throw null; }
+ int System.Collections.IList.IndexOf(object? value) { throw null; }
+ void System.Collections.IList.Insert(int index, object? value) { }
+ void System.Collections.IList.Remove(object? value) { }
+ void System.Collections.IList.RemoveAt(int index) { }
+ public partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable
+ {
+ private TKey _Current_k__BackingField;
+ private object _dummy;
+ private int _dummyPrimitive;
+ public readonly TKey Current { get { throw null; } }
+ object System.Collections.IEnumerator.Current { get { throw null; } }
+ public bool MoveNext() { throw null; }
+ void System.Collections.IEnumerator.Reset() { }
+ void System.IDisposable.Dispose() { }
+ }
+ }
+ public sealed partial class ValueCollection : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IList
+ {
+ internal ValueCollection() { }
+ public int Count { get { throw null; } }
+ bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } }
+ TValue System.Collections.Generic.IList.this[int index] { get { throw null; } set { } }
+ TValue System.Collections.Generic.IReadOnlyList.this[int index] { get { throw null; } }
+ bool System.Collections.ICollection.IsSynchronized { get { throw null; } }
+ object System.Collections.ICollection.SyncRoot { get { throw null; } }
+ bool System.Collections.IList.IsFixedSize { get { throw null; } }
+ bool System.Collections.IList.IsReadOnly { get { throw null; } }
+ object? System.Collections.IList.this[int index] { get { throw null; } set { } }
+ public void CopyTo(TValue[] array, int arrayIndex) { }
+ public System.Collections.Generic.OrderedDictionary.ValueCollection.Enumerator GetEnumerator() { throw null; }
+ void System.Collections.Generic.ICollection.Add(TValue item) { }
+ void System.Collections.Generic.ICollection.Clear() { }
+ bool System.Collections.Generic.ICollection.Contains(TValue item) { throw null; }
+ bool System.Collections.Generic.ICollection.Remove(TValue item) { throw null; }
+ System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; }
+ int System.Collections.Generic.IList.IndexOf(TValue item) { throw null; }
+ void System.Collections.Generic.IList.Insert(int index, TValue item) { }
+ void System.Collections.Generic.IList.RemoveAt(int index) { }
+ void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+ int System.Collections.IList.Add(object? value) { throw null; }
+ void System.Collections.IList.Clear() { }
+ bool System.Collections.IList.Contains(object? value) { throw null; }
+ int System.Collections.IList.IndexOf(object? value) { throw null; }
+ void System.Collections.IList.Insert(int index, object? value) { }
+ void System.Collections.IList.Remove(object? value) { }
+ void System.Collections.IList.RemoveAt(int index) { }
+ public partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable
+ {
+ private TValue _Current_k__BackingField;
+ private object _dummy;
+ private int _dummyPrimitive;
+ public readonly TValue Current { get { throw null; } }
+ object? System.Collections.IEnumerator.Current { get { throw null; } }
+ public bool MoveNext() { throw null; }
+ void System.Collections.IEnumerator.Reset() { }
+ void System.IDisposable.Dispose() { }
+ }
+ }
+ }
public partial class PriorityQueue
{
public PriorityQueue() { }
diff --git a/src/libraries/System.Collections/src/System.Collections.csproj b/src/libraries/System.Collections/src/System.Collections.csproj
index 871c508e84104a..ba91a4f329133a 100644
--- a/src/libraries/System.Collections/src/System.Collections.csproj
+++ b/src/libraries/System.Collections/src/System.Collections.csproj
@@ -10,15 +10,13 @@
-
-
-
-
+
+
+
+
+
+
@@ -31,14 +29,10 @@
-
-
-
-
+
+
+
+
diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/OrderedDictionary.cs b/src/libraries/System.Collections/src/System/Collections/Generic/OrderedDictionary.cs
new file mode 100644
index 00000000000000..cb8b2008fa4f45
--- /dev/null
+++ b/src/libraries/System.Collections/src/System/Collections/Generic/OrderedDictionary.cs
@@ -0,0 +1,1867 @@
+// 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;
+
+namespace System.Collections.Generic
+{
+ ///
+ /// Represents a collection of key/value pairs that are accessible by the key or index.
+ ///
+ /// The type of the keys in the dictionary.
+ /// The type of the values in the dictionary.
+ ///
+ /// Operations on the collection have algorithmic complexities that are similar to that of the
+ /// class, except with lookups by key similar in complexity to that of .
+ ///
+ [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
+ [DebuggerDisplay("Count = {Count}")]
+ public class OrderedDictionary :
+ IDictionary, IReadOnlyDictionary, IDictionary,
+ IList>, IReadOnlyList>, IList
+ where TKey : notnull
+ {
+ /// The comparer used by the collection. May be null if the default comparer is used.
+ private IEqualityComparer? _comparer;
+ /// Indexes into for the start of chains; indices are 1-based.
+ private int[]? _buckets;
+ /// Ordered entries in the dictionary.
+ ///
+ /// Unlike , removed entries are actually removed rather than left as holes
+ /// that can be filled in by subsequent additions. This is done to retain ordering.
+ ///
+ private Entry[]? _entries;
+ /// The number of items in the collection.
+ private int _count;
+ /// Version number used to invalidate an enumerator.
+ private int _version;
+ /// Multiplier used on 64-bit to enable faster % operations.
+ private ulong _fastModMultiplier;
+
+ /// Lazily-initialized wrapper collection that serves up only the keys, in order.
+ private KeyCollection? _keys;
+ /// Lazily-initialized wrapper collection that serves up only the values, in order.
+ private ValueCollection? _values;
+
+ ///
+ /// Initializes a new instance of the class that is empty,
+ /// has the default initial capacity, and uses the default equality comparer for the key type.
+ ///
+ public OrderedDictionary() : this(0, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that is empty,
+ /// has the specified initial capacity, and uses the default equality comparer for the key type.
+ ///
+ /// The initial number of elements that the can contain.
+ /// capacity is less than 0.
+ public OrderedDictionary(int capacity) : this(capacity, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that is empty,
+ /// has the default initial capacity, and uses the specified .
+ ///
+ ///
+ /// The implementation to use when comparing keys,
+ /// or null to use the default for the type of the key.
+ ///
+ public OrderedDictionary(IEqualityComparer? comparer) : this(0, comparer)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that is empty,
+ /// has the specified initial capacity, and uses the specified .
+ ///
+ /// The initial number of elements that the can contain.
+ ///
+ /// The implementation to use when comparing keys,
+ /// or null to use the default for the type of the key.
+ ///
+ /// capacity is less than 0.
+ public OrderedDictionary(int capacity, IEqualityComparer? comparer)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(capacity);
+
+ if (capacity > 0)
+ {
+ EnsureBucketsAndEntriesInitialized(capacity);
+ }
+
+ // Initialize the comparer:
+ // - Strings: Special-case EqualityComparer.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.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.Default.Equals on every use, enabling the JIT to
+ // devirtualize and possibly inline the operation.
+ if (!typeof(TKey).IsValueType)
+ {
+ _comparer = comparer ?? EqualityComparer.Default;
+
+ if (typeof(TKey) == typeof(string) &&
+ NonRandomizedStringEqualityComparer.GetStringComparer(_comparer) is IEqualityComparer stringComparer)
+ {
+ _comparer = (IEqualityComparer)stringComparer;
+ }
+ }
+ else if (comparer is not null && // first check for null to avoid forcing default comparer instantiation unnecessarily
+ comparer != EqualityComparer.Default)
+ {
+ _comparer = comparer;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains elements copied from
+ /// the specified and uses the default equality comparer for the key type.
+ ///
+ ///
+ /// The whose elements are copied to the new .
+ /// The initial order of the elements in the new collection is the order the elements are enumerated from the supplied dictionary.
+ ///
+ /// is null.
+ public OrderedDictionary(IDictionary dictionary) : this(dictionary, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains elements copied from
+ /// the specified and uses the specified .
+ ///
+ ///
+ /// The whose elements are copied to the new .
+ /// The initial order of the elements in the new collection is the order the elements are enumerated from the supplied dictionary.
+ ///
+ ///
+ /// The implementation to use when comparing keys,
+ /// or null to use the default for the type of the key.
+ ///
+ /// is null.
+ public OrderedDictionary(IDictionary dictionary, IEqualityComparer? comparer) :
+ this(dictionary?.Count ?? 0, comparer)
+ {
+ ArgumentNullException.ThrowIfNull(dictionary);
+
+ AddRange(dictionary);
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains elements copied
+ /// from the specified and uses the default equality comparer for the key type.
+ ///
+ ///
+ /// The whose elements are copied to the new .
+ /// The initial order of the elements in the new collection is the order the elements are enumerated from the supplied collection.
+ ///
+ /// is null.
+ public OrderedDictionary(IEnumerable> collection) : this(collection, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains elements copied
+ /// from the specified and uses the specified .
+ ///
+ ///
+ /// The whose elements are copied to the new .
+ /// The initial order of the elements in the new collection is the order the elements are enumerated from the supplied collection.
+ ///
+ ///
+ /// The implementation to use when comparing keys,
+ /// or null to use the default for the type of the key.
+ ///
+ /// is null.
+ public OrderedDictionary(IEnumerable> collection, IEqualityComparer? comparer) :
+ this((collection as ICollection>)?.Count ?? 0, comparer)
+ {
+ ArgumentNullException.ThrowIfNull(collection);
+
+ AddRange(collection);
+ }
+
+ /// Initializes the /.
+ ///
+ [MemberNotNull(nameof(_buckets))]
+ [MemberNotNull(nameof(_entries))]
+ private void EnsureBucketsAndEntriesInitialized(int capacity)
+ {
+ Resize(HashHelpers.GetPrime(capacity));
+ }
+
+ /// Gets the total number of key/value pairs the internal data structure can hold without resizing.
+ public int Capacity => _entries?.Length ?? 0;
+
+ /// Gets the that is used to determine equality of keys for the dictionary.
+ public IEqualityComparer Comparer
+ {
+ get
+ {
+ IEqualityComparer? comparer = _comparer;
+
+ // 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 ec)
+ {
+ return ec;
+ }
+
+ // Otherwise, return whatever comparer we have, or the default if none was provided.
+ return comparer ?? EqualityComparer.Default;
+ }
+ }
+
+ /// Gets the number of key/value pairs contained in the .
+ public int Count => _count;
+
+ ///
+ bool ICollection>.IsReadOnly => false;
+
+ ///
+ bool IDictionary.IsReadOnly => false;
+
+ ///
+ bool IList.IsReadOnly => false;
+
+ ///
+ bool IDictionary.IsFixedSize => false;
+
+ ///
+ bool IList.IsFixedSize => false;
+
+ /// Gets a collection containing the keys in the .
+ public KeyCollection Keys => _keys ??= new(this);
+
+ ///
+ IEnumerable IReadOnlyDictionary.Keys => Keys;
+
+ ///
+ ICollection IDictionary.Keys => Keys;
+
+ ///
+ ICollection IDictionary.Keys => Keys;
+
+ /// Gets a collection containing the values in the .
+ public ValueCollection Values => _values ??= new(this);
+
+ ///
+ IEnumerable IReadOnlyDictionary.Values => Values;
+
+ ///
+ ICollection IDictionary.Values => Values;
+
+ ///
+ ICollection IDictionary.Values => Values;
+
+ ///
+ bool ICollection.IsSynchronized => false;
+
+ ///
+ object ICollection.SyncRoot => this;
+
+ ///
+ object? IList.this[int index]
+ {
+ get => GetAt(index);
+ set
+ {
+ ArgumentNullException.ThrowIfNull(value);
+
+ if (value is not KeyValuePair tpair)
+ {
+ throw new ArgumentException(SR.Format(SR.Arg_WrongType, value, typeof(KeyValuePair)), nameof(value));
+ }
+
+ SetAt(index, tpair.Key, tpair.Value);
+ }
+ }
+
+ ///
+ object? IDictionary.this[object key]
+ {
+ get
+ {
+ ArgumentNullException.ThrowIfNull(key);
+
+ if (key is TKey tkey && TryGetValue(tkey, out TValue? value))
+ {
+ return value;
+ }
+
+ return null;
+ }
+ set
+ {
+ ArgumentNullException.ThrowIfNull(key);
+ if (default(TValue) is not null)
+ {
+ ArgumentNullException.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;
+ }
+ }
+
+ ///
+ KeyValuePair IList>.this[int index]
+ {
+ get => GetAt(index);
+ set => SetAt(index, value.Key, value.Value);
+ }
+
+ ///
+ KeyValuePair IReadOnlyList>.this[int index] => GetAt(index);
+
+ /// Gets or sets the value associated with the specified key.
+ /// The key of the value to get or set.
+ /// The value associated with the specified key. If the specified key is not found, a get operation throws a , and a set operation creates a new element with the specified key.
+ /// is null.
+ /// The property is retrieved and does not exist in the collection.
+ /// Setting the value of an existing key does not impact its order in the collection.
+ public TValue this[TKey key]
+ {
+ get
+ {
+ if (!TryGetValue(key, out TValue? value))
+ {
+ ThrowHelper.ThrowKeyNotFound(key);
+ }
+
+ return value;
+ }
+ set
+ {
+ ArgumentNullException.ThrowIfNull(key);
+
+ bool modified = TryInsert(index: -1, key, value, InsertionBehavior.OverwriteExisting);
+ Debug.Assert(modified);
+ }
+ }
+
+ /// Insert the key/value pair at the specified index.
+ /// The index at which to insert the pair, or -1 to append.
+ /// The key to insert.
+ /// The value to insert.
+ ///
+ /// 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)
+ ///
+ /// true if the collection was updated; otherwise, false.
+ private bool TryInsert(int index, TKey key, TValue value, InsertionBehavior behavior)
+ {
+ // 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)
+ {
+ 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);
+
+ return true;
+ }
+
+ /// Adds the specified key and value to the dictionary.
+ /// The key of the element to add.
+ /// The value of the element to add. The value can be null for reference types.
+ /// key is null.
+ /// An element with the same key already exists in the .
+ public void Add(TKey key, TValue value)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+
+ TryInsert(index: -1, key, value, InsertionBehavior.ThrowOnExisting);
+ }
+
+ /// Adds the specified key and value to the dictionary if the key doesn't already exist.
+ /// The key of the element to add.
+ /// The value of the element to add. The value can be null for reference types.
+ /// key is null.
+ /// true if the key didn't exist and the key and value were added to the dictionary; otherwise, false.
+ public bool TryAdd(TKey key, TValue value)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+
+ return TryInsert(index: -1, key, value, InsertionBehavior.IgnoreInsertion);
+ }
+
+ /// Adds each element of the enumerable to the dictionary.
+ private void AddRange(IEnumerable> collection)
+ {
+ Debug.Assert(collection is not null);
+
+ if (collection is KeyValuePair[] array)
+ {
+ foreach (KeyValuePair pair in array)
+ {
+ Add(pair.Key, pair.Value);
+ }
+ }
+ else
+ {
+ foreach (KeyValuePair pair in collection)
+ {
+ Add(pair.Key, pair.Value);
+ }
+ }
+ }
+
+ /// Removes all keys and values from the .
+ 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++;
+ }
+ }
+
+ /// Determines whether the contains the specified key.
+ /// The key to locate in the .
+ /// true if the contains an element with the specified key; otherwise, false.
+ public bool ContainsKey(TKey key) => IndexOf(key) >= 0;
+
+ /// Determines whether the contains a specific value.
+ /// The value to locate in the . The value can be null for reference types.
+ /// true if the contains an element with the specified value; otherwise, false.
+ 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.Default.Equals(value, entries[i].Value))
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ EqualityComparer comparer = EqualityComparer.Default;
+ for (int i = 0; i < count; i++)
+ {
+ if (comparer.Equals(value, entries[i].Value))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /// Gets the key/value pair at the specified index.
+ /// The zero-based index of the pair to get.
+ /// The element at the specified index.
+ /// is less than 0 or greater than or equal to .
+ public KeyValuePair 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 KeyValuePair.Create(e.Key, e.Value);
+ }
+
+ /// Determines the index of a specific key in the .
+ /// The key to locate.
+ /// The index of if found; otherwise, -1.
+ /// is null.
+ public int IndexOf(TKey key)
+ {
+ ArgumentNullException.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? 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[]? 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.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.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;
+ }
+
+ /// Inserts an item into the collection at the specified index.
+ /// The zero-based index at which item should be inserted.
+ /// The key to insert.
+ /// The value to insert.
+ /// key is null.
+ /// An element with the same key already exists in the .
+ /// is less than 0 or greater than .
+ public void Insert(int index, TKey key, TValue value)
+ {
+ if ((uint)index > (uint)_count)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange();
+ }
+
+ ArgumentNullException.ThrowIfNull(key);
+
+ TryInsert(index, key, value, InsertionBehavior.ThrowOnExisting);
+ }
+
+ /// Removes the value with the specified key from the .
+ /// The key of the element to remove.
+ ///
+ public bool Remove(TKey key) => Remove(key, out _);
+
+ /// Removes the value with the specified key from the and copies the element to the value parameter.
+ /// The key of the element to remove.
+ /// The removed element.
+ /// true if the element is successfully found and removed; otherwise, false.
+ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
+ {
+ ArgumentNullException.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;
+ }
+
+ /// Removes the key/value pair at the specified index.
+ /// The zero-based index of the item to remove.
+ 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++;
+ }
+
+ /// Sets the value for the key at the specified index.
+ /// The zero-based index of the element to get or set.
+ /// The value to store at the specified index.
+ public void SetAt(int index, TValue value)
+ {
+ if ((uint)index >= (uint)_count)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange();
+ }
+
+ Debug.Assert(_entries is not null);
+
+ _entries[index].Value = value;
+ }
+
+ /// Sets the key/value pair at the specified index.
+ /// The zero-based index of the element to get or set.
+ /// The key to store at the specified index.
+ /// The value to store at the specified index.
+ ///
+ public void SetAt(int index, TKey key, TValue value)
+ {
+ if ((uint)index >= (uint)_count)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange();
+ }
+
+ ArgumentNullException.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.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 _ = 0, collisionCount = 0;
+ if (IndexOf(key, ref _, 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.Key = key;
+ e.Value = value;
+ PushEntryIntoBucket(ref e, index);
+
+ _version++;
+
+ RehashIfNecessary(collisionCount, _entries);
+ }
+
+ /// Ensures that the dictionary can hold up to entries without resizing.
+ /// The desired minimum capacity of the dictionary. The actual capacity provided may be larger.
+ /// The new capacity of the dictionary.
+ /// is negative.
+ public int EnsureCapacity(int capacity)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(capacity);
+
+ if (Capacity < capacity)
+ {
+ if (_buckets is null)
+ {
+ EnsureBucketsAndEntriesInitialized(capacity);
+ }
+ else
+ {
+ Resize(HashHelpers.GetPrime(capacity));
+ }
+
+ _version++;
+ }
+
+ return Capacity;
+ }
+
+ /// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries.
+ public void TrimExcess() => TrimExcess(_count);
+
+ /// Sets the capacity of this dictionary to hold up a specified number of entries without resizing.
+ /// The desired capacity to which to shrink the dictionary.
+ /// is less than .
+ public void TrimExcess(int capacity)
+ {
+ ArgumentOutOfRangeException.ThrowIfLessThan(capacity, Count);
+
+ int currentCapacity = _entries?.Length ?? 0;
+ capacity = HashHelpers.GetPrime(capacity);
+ if (capacity < currentCapacity)
+ {
+ Resize(capacity);
+ }
+ }
+
+ /// Gets the value associated with the specified key.
+ /// The key of the value to get.
+ ///
+ /// 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.
+ ///
+ /// true if the contains an element with the specified key; otherwise, false.
+ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+
+ // Find the key.
+ int 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;
+ }
+
+ /// Pushes the entry into its bucket.
+ ///
+ /// The bucket is a linked list by index into the array.
+ /// The new entry's is set to the bucket's current
+ /// head, and then the new entry is made the new head.
+ ///
+ private void PushEntryIntoBucket(ref Entry entry, int entryIndex)
+ {
+ ref int bucket = ref GetBucket(entry.HashCode);
+ entry.Next = bucket - 1;
+ bucket = entryIndex + 1;
+ }
+
+ /// Removes an entry from its bucket.
+ 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();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Updates the bucket chain containing the specified entry (by index) to shift indices
+ /// by the specified amount.
+ ///
+ /// The index of the target entry.
+ ///
+ /// 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.
+ ///
+ 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();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Checks to see whether the collision count that occurred during lookup warrants upgrading to a non-randomized comparer,
+ /// and does so if necessary.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void RehashIfNecessary(uint collisionCount, Entry[] entries)
+ {
+ // 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);
+ }
+ }
+
+ /// Grow or shrink and to the specified capacity.
+ [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 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 comparer = _comparer = (IEqualityComparer)((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);
+ }
+ }
+
+ // 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;
+ }
+
+ /// Gets the bucket assigned to the specified hash code.
+ ///
+ /// Buckets are 1-based. This is so that the default initialized value of 0
+ /// maps to -1 and is usable as a sentinel.
+ ///
+ [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];
+ }
+ }
+
+ /// Returns an enumerator that iterates through the .
+ /// A structure for the .
+ public Enumerator GetEnumerator() => new(this, useDictionaryEntry: false);
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator() =>
+ Count == 0 ? EnumerableHelpers.GetEmptyEnumerator>() :
+ GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator();
+
+ ///
+ IDictionaryEnumerator IDictionary.GetEnumerator() => new Enumerator(this, useDictionaryEntry: true);
+
+ ///
+ int IList>.IndexOf(KeyValuePair item)
+ {
+ ArgumentNullException.ThrowIfNull(item.Key, nameof(item));
+
+ int index = IndexOf(item.Key);
+ if (index >= 0)
+ {
+ Debug.Assert(_entries is not null);
+ if (EqualityComparer.Default.Equals(item.Value, _entries[index].Value))
+ {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ void IList>.Insert(int index, KeyValuePair item) => Insert(index, item.Key, item.Value);
+
+ ///
+ void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value);
+
+ ///
+ bool ICollection>.Contains(KeyValuePair item)
+ {
+ ArgumentNullException.ThrowIfNull(item.Key, nameof(item));
+
+ return
+ TryGetValue(item.Key, out TValue? value) &&
+ EqualityComparer.Default.Equals(value, item.Value);
+ }
+
+ ///
+ void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ ArgumentNullException.ThrowIfNull(array);
+ ArgumentOutOfRangeException.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);
+ }
+ }
+
+ ///
+ bool ICollection>.Remove(KeyValuePair item) =>
+ TryGetValue(item.Key, out TValue? value) &&
+ EqualityComparer.Default.Equals(value, item.Value) &&
+ Remove(item.Key);
+
+ ///
+ void IDictionary.Add(object key, object? value)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+ if (default(TValue) is not null)
+ {
+ ArgumentNullException.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)
+ {
+ ArgumentNullException.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);
+ }
+
+ ///
+ bool IDictionary.Contains(object key)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+
+ return key is TKey tkey && ContainsKey(tkey);
+ }
+
+ ///
+ void IDictionary.Remove(object key)
+ {
+ ArgumentNullException.ThrowIfNull(key);
+
+ if (key is TKey tkey)
+ {
+ Remove(tkey);
+ }
+ }
+
+ ///
+ void ICollection.CopyTo(Array array, int index)
+ {
+ ArgumentNullException.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));
+ }
+
+ ArgumentOutOfRangeException.ThrowIfNegative(index);
+
+ if (array.Length - index < _count)
+ {
+ throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
+ }
+
+ if (array is KeyValuePair[] tarray)
+ {
+ ((ICollection>)this).CopyTo(tarray, index);
+ }
+ else
+ {
+ try
+ {
+ if (array is not object[] objects)
+ {
+ throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
+ }
+
+ foreach (KeyValuePair pair in this)
+ {
+ objects[index++] = pair;
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
+ }
+ }
+ }
+
+ ///
+ int IList.Add(object? value)
+ {
+ if (value is not KeyValuePair pair)
+ {
+ throw new ArgumentException(SR.Format(SR.Arg_WrongType, value, typeof(KeyValuePair)), nameof(value));
+ }
+
+ Add(pair.Key, pair.Value);
+ return Count - 1;
+ }
+
+ ///
+ bool IList.Contains(object? value) =>
+ value is KeyValuePair pair &&
+ TryGetValue(pair.Key, out TValue? v) &&
+ EqualityComparer.Default.Equals(v, pair.Value);
+
+ ///
+ int IList.IndexOf(object? value)
+ {
+ if (value is KeyValuePair pair)
+ {
+ return ((IList>)this).IndexOf(pair);
+ }
+
+ return -1;
+ }
+
+ ///
+ void IList.Insert(int index, object? value)
+ {
+ if (value is not KeyValuePair pair)
+ {
+ throw new ArgumentException(SR.Format(SR.Arg_WrongType, value, typeof(KeyValuePair)), nameof(value));
+ }
+
+ Insert(index, pair.Key, pair.Value);
+ }
+
+ ///
+ void IList.Remove(object? value)
+ {
+ if (value is KeyValuePair pair)
+ {
+ ((ICollection>)this).Remove(pair);
+ }
+ }
+
+ /// Represents a key/value pair in the dictionary.
+ private struct Entry
+ {
+ /// The index of the next entry in the chain, or -1 if this is the last entry in the chain.
+ public int Next;
+ /// Cached hash code of .
+ public uint HashCode;
+ /// The key.
+ public TKey Key;
+ /// The value associated with .
+ public TValue Value;
+ }
+
+ /// Enumerates the elements of a .
+ [StructLayout(LayoutKind.Auto)]
+ public struct Enumerator : IEnumerator>, IDictionaryEnumerator
+ {
+ /// The dictionary being enumerated.
+ private readonly OrderedDictionary _dictionary;
+ /// A snapshot of the dictionary's version when enumeration began.
+ private readonly int _version;
+ /// Whether Current should be a DictionaryEntry.
+ private readonly bool _useDictionaryEntry;
+ /// The current index.
+ private int _index;
+
+ /// Initialize the enumerator.
+ internal Enumerator(OrderedDictionary dictionary, bool useDictionaryEntry)
+ {
+ _dictionary = dictionary;
+ _version = _dictionary._version;
+ _useDictionaryEntry = useDictionaryEntry;
+ }
+
+ ///
+ public KeyValuePair Current { get; private set; }
+
+ ///
+ readonly object IEnumerator.Current => _useDictionaryEntry ?
+ new DictionaryEntry(Current.Key, Current.Value) :
+ Current;
+
+ ///
+ readonly DictionaryEntry IDictionaryEnumerator.Entry => new(Current.Key, Current.Value);
+
+ ///
+ readonly object IDictionaryEnumerator.Key => Current.Key;
+
+ ///
+ readonly object? IDictionaryEnumerator.Value => Current.Value;
+
+ ///
+ public bool MoveNext()
+ {
+ OrderedDictionary 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(entry.Key, entry.Value);
+ _index++;
+ return true;
+ }
+
+ Current = default;
+ return false;
+ }
+
+ ///
+ void IEnumerator.Reset()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowVersionCheckFailed();
+ }
+
+ _index = 0;
+ Current = default;
+ }
+
+ ///
+ readonly void IDisposable.Dispose() { }
+ }
+
+ /// Represents the collection of keys in a .
+ [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
+ [DebuggerDisplay("Count = {Count}")]
+ public sealed class KeyCollection : IList, IReadOnlyList, IList
+ {
+ /// The dictionary whose keys are being exposed.
+ private readonly OrderedDictionary _dictionary;
+
+ /// Initialize the collection wrapper.
+ internal KeyCollection(OrderedDictionary dictionary) => _dictionary = dictionary;
+
+ ///
+ public int Count => _dictionary.Count;
+
+ ///
+ bool ICollection.IsReadOnly => true;
+
+ ///
+ bool IList.IsReadOnly => true;
+
+ ///
+ bool IList.IsFixedSize => false;
+
+ ///
+ bool ICollection.IsSynchronized => false;
+
+ ///
+ object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
+
+ ///
+ public bool Contains(TKey key) => _dictionary.ContainsKey(key);
+
+ ///
+ bool IList.Contains(object? value) => value is TKey key && Contains(key);
+
+ ///
+ public void CopyTo(TKey[] array, int arrayIndex)
+ {
+ ArgumentNullException.ThrowIfNull(array);
+ ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
+
+ OrderedDictionary 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;
+ }
+ }
+
+ ///
+ void ICollection.CopyTo(Array array, int index)
+ {
+ ArgumentNullException.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));
+ }
+
+ ArgumentOutOfRangeException.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));
+ }
+ }
+ }
+
+ ///
+ TKey IList.this[int index]
+ {
+ get => _dictionary.GetAt(index).Key;
+ set => throw new NotSupportedException();
+ }
+
+ ///
+ object? IList.this[int index]
+ {
+ get => _dictionary.GetAt(index).Key;
+ set => throw new NotSupportedException();
+ }
+
+ ///
+ TKey IReadOnlyList.this[int index] => _dictionary.GetAt(index).Key;
+
+ /// Returns an enumerator that iterates through the .
+ /// A for the .
+ public Enumerator GetEnumerator() => new(_dictionary);
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() =>
+ Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() :
+ GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator();
+
+ ///
+ int IList.IndexOf(TKey item) => _dictionary.IndexOf(item);
+
+ ///
+ void ICollection.Add(TKey item) => throw new NotSupportedException();
+
+ ///
+ void ICollection.Clear() => throw new NotSupportedException();
+
+ ///
+ void IList.Insert(int index, TKey item) => throw new NotSupportedException();
+
+ ///
+ bool ICollection.Remove(TKey item) => throw new NotSupportedException();
+
+ ///
+ void IList.RemoveAt(int index) => throw new NotSupportedException();
+
+ ///
+ int IList.Add(object? value) => throw new NotSupportedException();
+
+ ///
+ void IList.Clear() => throw new NotSupportedException();
+
+ ///
+ int IList.IndexOf(object? value) => value is TKey key ? _dictionary.IndexOf(key) : -1;
+
+ ///
+ void IList.Insert(int index, object? value) => throw new NotSupportedException();
+
+ ///
+ void IList.Remove(object? value) => throw new NotSupportedException();
+
+ ///
+ void IList.RemoveAt(int index) => throw new NotSupportedException();
+
+ /// Enumerates the elements of a .
+ public struct Enumerator : IEnumerator
+ {
+ /// The dictionary's enumerator.
+ private OrderedDictionary.Enumerator _enumerator;
+
+ /// Initialize the enumerator.
+ internal Enumerator(OrderedDictionary dictionary) => _enumerator = dictionary.GetEnumerator();
+
+ ///
+ public TKey Current => _enumerator.Current.Key;
+
+ ///
+ object IEnumerator.Current => Current;
+
+ ///
+ public bool MoveNext() => _enumerator.MoveNext();
+
+ ///
+ void IEnumerator.Reset() => EnumerableHelpers.Reset(ref _enumerator);
+
+ ///
+ readonly void IDisposable.Dispose() { }
+ }
+ }
+
+ /// Represents the collection of values in a .
+ [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
+ [DebuggerDisplay("Count = {Count}")]
+ public sealed class ValueCollection : IList, IReadOnlyList, IList
+ {
+ /// The dictionary whose values are being exposed.
+ private readonly OrderedDictionary _dictionary;
+
+ /// Initialize the collection wrapper.
+ internal ValueCollection(OrderedDictionary dictionary) => _dictionary = dictionary;
+
+ ///
+ public int Count => _dictionary.Count;
+
+ ///
+ bool ICollection.IsReadOnly => true;
+
+ ///
+ bool IList.IsReadOnly => true;
+
+ ///
+ bool IList.IsFixedSize => false;
+
+ ///
+ bool ICollection.IsSynchronized => false;
+
+ ///
+ object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
+
+ ///
+ public void CopyTo(TValue[] array, int arrayIndex)
+ {
+ ArgumentNullException.ThrowIfNull(array);
+ ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
+
+ OrderedDictionary 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;
+ }
+ }
+
+ /// Returns an enumerator that iterates through the .
+ /// A for the .
+ public Enumerator GetEnumerator() => new(_dictionary);
+
+ ///
+ TValue IList.this[int index]
+ {
+ get => _dictionary.GetAt(index).Value;
+ set => throw new NotSupportedException();
+ }
+
+ ///
+ TValue IReadOnlyList.this[int index] => _dictionary.GetAt(index).Value;
+
+ ///
+ object? IList.this[int index]
+ {
+ get => _dictionary.GetAt(index).Value;
+ set => throw new NotSupportedException();
+ }
+
+ ///
+ bool ICollection.Contains(TValue item) => _dictionary.ContainsValue(item);
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() =>
+ Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() :
+ GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator();
+
+ ///
+ int IList.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.Default.Equals(item, entries[i].Value))
+ {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ void ICollection.Add(TValue item) => throw new NotSupportedException();
+
+ ///
+ void ICollection.Clear() => throw new NotSupportedException();
+
+ ///
+ void IList.Insert(int index, TValue item) => throw new NotSupportedException();
+
+ ///
+ bool ICollection.Remove(TValue item) => throw new NotSupportedException();
+
+ ///
+ void IList.RemoveAt(int index) => throw new NotSupportedException();
+
+ ///
+ int IList.Add(object? value) => throw new NotSupportedException();
+
+ ///
+ void IList.Clear() => throw new NotSupportedException();
+
+ ///
+ bool IList.Contains(object? value) =>
+ value is null && default(TValue) is null ?
+ _dictionary.ContainsValue(default!) :
+ value is TValue tvalue && _dictionary.ContainsValue(tvalue);
+
+ ///
+ 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.Default.Equals(tvalue, entries[i].Value))
+ {
+ return i;
+ }
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ void IList.Insert(int index, object? value) => throw new NotSupportedException();
+
+ ///
+ void IList.Remove(object? value) => throw new NotSupportedException();
+
+ ///
+ void IList.RemoveAt(int index) => throw new NotSupportedException();
+
+ ///
+ void ICollection.CopyTo(Array array, int index)
+ {
+ ArgumentNullException.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));
+ }
+
+ ArgumentOutOfRangeException.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));
+ }
+ }
+ }
+
+ /// Enumerates the elements of a .
+ public struct Enumerator : IEnumerator
+ {
+ /// The dictionary's enumerator.
+ private OrderedDictionary.Enumerator _enumerator;
+
+ /// Initialize the enumerator.
+ internal Enumerator(OrderedDictionary dictionary) => _enumerator = dictionary.GetEnumerator();
+
+ ///
+ public TValue Current => _enumerator.Current.Value;
+
+ ///
+ object? IEnumerator.Current => Current;
+
+ ///
+ public bool MoveNext() => _enumerator.MoveNext();
+
+ ///
+ void IEnumerator.Reset() => EnumerableHelpers.Reset(ref _enumerator);
+
+ ///
+ readonly void IDisposable.Dispose() { }
+ }
+ }
+ }
+
+ /// Used to control behavior of insertion into a .
+ /// Not nested in to avoid multiple generic instantiations.
+ internal enum InsertionBehavior
+ {
+ /// Skip the insertion operation.
+ IgnoreInsertion = 0,
+
+ /// Specifies that an existing entry with the same key should be overwritten if encountered.
+ OverwriteExisting = 1,
+
+ /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown.
+ ThrowOnExisting = 2
+ }
+}
diff --git a/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs b/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs
new file mode 100644
index 00000000000000..1f40b6185ce5c7
--- /dev/null
+++ b/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Collections
+{
+ internal static class ThrowHelper
+ {
+ /// Throws an exception for a key not being found in the dictionary.
+ [DoesNotReturn]
+ internal static void ThrowKeyNotFound(TKey key) =>
+ throw new KeyNotFoundException(SR.Format(SR.Arg_KeyNotFoundWithKey, key));
+
+ /// Throws an exception for trying to insert a duplicate key into the dictionary.
+ [DoesNotReturn]
+ internal static void ThrowDuplicateKey(TKey key) =>
+ throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate, key), nameof(key));
+
+ /// Throws an exception when erroneous concurrent use of a collection is detected.
+ [DoesNotReturn]
+ internal static void ThrowConcurrentOperation() =>
+ throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
+
+ /// Throws an exception for an index being out of range.
+ [DoesNotReturn]
+ internal static void ThrowIndexArgumentOutOfRange() =>
+ throw new ArgumentOutOfRangeException("index");
+
+ /// Throws an exception for a version check failing during enumeration.
+ [DoesNotReturn]
+ internal static void ThrowVersionCheckFailed() =>
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ }
+}
diff --git a/src/libraries/System.Collections/tests/Generic/Dictionary/HashCollisionScenarios/OutOfBoundsRegression.cs b/src/libraries/System.Collections/tests/Generic/Dictionary/HashCollisionScenarios/OutOfBoundsRegression.cs
index c9088f393e839c..6fe06dd432e743 100644
--- a/src/libraries/System.Collections/tests/Generic/Dictionary/HashCollisionScenarios/OutOfBoundsRegression.cs
+++ b/src/libraries/System.Collections/tests/Generic/Dictionary/HashCollisionScenarios/OutOfBoundsRegression.cs
@@ -10,27 +10,21 @@
namespace System.Collections.Tests
{
- public class InternalHashCodeTests
+ #region Dictionary
+ public class InternalHashCodeTests_Dictionary_NullComparer : InternalHashCodeTests>
{
- private static Type nonRandomizedDefaultComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.NonRandomizedStringEqualityComparer+DefaultComparer", throwOnError: true);
- private static Type nonRandomizedOrdinalComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalComparer", throwOnError: true);
- private static Type nonRandomizedOrdinalIgnoreCaseComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer", throwOnError: true);
- private static Type randomizedOrdinalComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.RandomizedStringEqualityComparer+OrdinalComparer", throwOnError: true);
- private static Type randomizedOrdinalIgnoreCaseComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.RandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer", throwOnError: true);
+ protected override Dictionary CreateCollection() => new Dictionary();
+ protected override void AddKey(Dictionary collection, string key) => collection.Add(key, key);
+ protected override bool ContainsKey(Dictionary collection, string key) => collection.ContainsKey(key);
+ protected override IEqualityComparer GetComparer(Dictionary collection) => collection.Comparer;
- ///
- /// Given a byte array, copies it to the string, without messing with any encoding. This issue was hit on a x64 machine
- ///
- private static string GetString(byte[] bytes)
- {
- var chars = new char[bytes.Length / sizeof(char)];
- Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
- return new string(chars);
- }
+ protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedDefaultComparerType;
+ protected override IEqualityComparer ExpectedPublicComparerBeforeCollisionThreshold => EqualityComparer.Default;
+ protected override Type ExpectedInternalComparerTypeAfterCollisionThreshold => randomizedOrdinalComparerType;
[Fact]
[OuterLoop("Takes over 55% of System.Collections.Tests testing time")]
- public static void OutOfBoundsRegression()
+ public void OutOfBoundsRegression()
{
var dictionary = new Dictionary();
@@ -48,147 +42,225 @@ public static void OutOfBoundsRegression()
}
}
- [Fact]
- public static void ComparerImplementations_Dictionary_WithWellKnownStringComparers()
+ ///
+ /// Given a byte array, copies it to the string, without messing with any encoding. This issue was hit on a x64 machine
+ ///
+ private static string GetString(byte[] bytes)
{
- // null comparer
-
- RunDictionaryTest(
- equalityComparer: null,
- expectedInternalComparerTypeBeforeCollisionThreshold: nonRandomizedDefaultComparerType,
- expectedPublicComparerBeforeCollisionThreshold: EqualityComparer.Default,
- expectedInternalComparerTypeAfterCollisionThreshold: randomizedOrdinalComparerType);
-
- // EqualityComparer.Default comparer
-
- RunDictionaryTest(
- equalityComparer: EqualityComparer