From 1a2e34f8bf370063b3c5fc007e90557d70b4f531 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Mon, 15 Jul 2024 22:18:52 +0100 Subject: [PATCH 1/2] Replace the bespoke OrderedDictionary with a backport of System.Collections.Generic.OrderedDictionary. --- .../Collections/Generic/EnumerableHelpers.cs | 11 +- .../src/System.Collections.csproj | 1 + .../Collections/Generic/OrderedDictionary.cs | 97 +++--- .../src/System/Collections/ThrowHelper.cs | 61 ++++ .../Generic/ICollectionDebugView.cs | 7 + .../ObjectModel/CollectionHelpers.cs | 14 + .../CallerArgumentExpressionAttribute.cs | 7 +- .../src/Resources/Strings.resx | 40 +++ .../src/System.Text.Json.csproj | 13 +- .../Json/OrderedDictionary.KeyCollection.cs | 76 ----- .../Json/OrderedDictionary.ValueCollection.cs | 88 ----- .../src/System/Text/Json/OrderedDictionary.cs | 321 ------------------ .../JsonNode/ParseTests.cs | 22 -- 13 files changed, 207 insertions(+), 551 deletions(-) delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.KeyCollection.cs delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.ValueCollection.cs delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.cs diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs index 00b05b11cc7596..d44bd276de688f 100644 --- a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs +++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs @@ -1,6 +1,8 @@ // 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; + namespace System.Collections.Generic { /// @@ -25,6 +27,11 @@ internal static IEnumerator GetEmptyEnumerator() => /// internal static T[] ToArray(IEnumerable source, out int length) { + // Copied from Array.MaxLength in System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Array.cs + const int ArrayMaxLength = 0X7FFFFFC7; +#if NET6_0_OR_GREATER + Debug.Assert(Array.MaxLength == ArrayMaxLength); +#endif if (source is ICollection ic) { int count = ic.Count; @@ -64,9 +71,9 @@ internal static T[] ToArray(IEnumerable source, out int length) // constrain the length to be Array.MaxLength (this overflow check works because of the // cast to uint). int newLength = count << 1; - if ((uint)newLength > Array.MaxLength) + if ((uint)newLength > ArrayMaxLength) { - newLength = Array.MaxLength <= count ? count + 1 : Array.MaxLength; + newLength = ArrayMaxLength <= count ? count + 1 : ArrayMaxLength; } Array.Resize(ref arr, newLength); diff --git a/src/libraries/System.Collections/src/System.Collections.csproj b/src/libraries/System.Collections/src/System.Collections.csproj index ba91a4f329133a..ac718a129a9ad6 100644 --- a/src/libraries/System.Collections/src/System.Collections.csproj +++ b/src/libraries/System.Collections/src/System.Collections.csproj @@ -6,6 +6,7 @@ true true false + SYSTEM_COLLECTIONS;$(DefineConstants) diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/OrderedDictionary.cs b/src/libraries/System.Collections/src/System/Collections/Generic/OrderedDictionary.cs index aa48a53033d0c4..af6c0c719b62c6 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/OrderedDictionary.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/OrderedDictionary.cs @@ -6,6 +6,13 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if NET8_0_OR_GREATER +using static System.ArgumentNullException; +using static System.ArgumentOutOfRangeException; +#else +using static System.Collections.ThrowHelper; +#endif + namespace System.Collections.Generic { /// @@ -19,7 +26,12 @@ namespace System.Collections.Generic /// [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))] [DebuggerDisplay("Count = {Count}")] - public class OrderedDictionary : +#if SYSTEM_COLLECTIONS + public +#else + internal sealed +#endif + class OrderedDictionary : IDictionary, IReadOnlyDictionary, IDictionary, IList>, IReadOnlyList>, IList where TKey : notnull @@ -88,7 +100,7 @@ public OrderedDictionary(IEqualityComparer? comparer) : this(0, comparer) /// capacity is less than 0. public OrderedDictionary(int capacity, IEqualityComparer? comparer) { - ArgumentOutOfRangeException.ThrowIfNegative(capacity); + ThrowIfNegative(capacity); if (capacity > 0) { @@ -110,11 +122,13 @@ public OrderedDictionary(int capacity, IEqualityComparer? comparer) { _comparer = comparer ?? EqualityComparer.Default; +#if SYSTEM_COLLECTIONS if (typeof(TKey) == typeof(string) && NonRandomizedStringEqualityComparer.GetStringComparer(_comparer) is IEqualityComparer stringComparer) { _comparer = (IEqualityComparer)stringComparer; } +#endif } else if (comparer is not null && // first check for null to avoid forcing default comparer instantiation unnecessarily comparer != EqualityComparer.Default) @@ -152,7 +166,7 @@ public OrderedDictionary(IDictionary dictionary) : this(dictionary public OrderedDictionary(IDictionary dictionary, IEqualityComparer? comparer) : this(dictionary?.Count ?? 0, comparer) { - ArgumentNullException.ThrowIfNull(dictionary); + ThrowIfNull(dictionary); AddRange(dictionary); } @@ -186,7 +200,7 @@ public OrderedDictionary(IEnumerable> collection) : t public OrderedDictionary(IEnumerable> collection, IEqualityComparer? comparer) : this((collection as ICollection>)?.Count ?? 0, comparer) { - ArgumentNullException.ThrowIfNull(collection); + ThrowIfNull(collection); AddRange(collection); } @@ -210,6 +224,7 @@ public IEqualityComparer Comparer { IEqualityComparer? 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) && @@ -217,6 +232,7 @@ public IEqualityComparer Comparer { return ec; } +#endif // Otherwise, return whatever comparer we have, or the default if none was provided. return comparer ?? EqualityComparer.Default; @@ -277,7 +293,7 @@ public IEqualityComparer Comparer get => GetAt(index); set { - ArgumentNullException.ThrowIfNull(value); + ThrowIfNull(value); if (value is not KeyValuePair tpair) { @@ -293,7 +309,7 @@ public IEqualityComparer Comparer { get { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); if (key is TKey tkey && TryGetValue(tkey, out TValue? value)) { @@ -304,10 +320,10 @@ public IEqualityComparer Comparer } set { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); if (default(TValue) is not null) { - ArgumentNullException.ThrowIfNull(value); + ThrowIfNull(value); } if (key is not TKey tkey) @@ -359,7 +375,7 @@ public TValue this[TKey key] } set { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); bool modified = TryInsert(index: -1, key, value, InsertionBehavior.OverwriteExisting); Debug.Assert(modified); @@ -461,7 +477,7 @@ private bool TryInsert(int index, TKey key, TValue value, InsertionBehavior beha /// An element with the same key already exists in the . public void Add(TKey key, TValue value) { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); TryInsert(index: -1, key, value, InsertionBehavior.ThrowOnExisting); } @@ -473,7 +489,7 @@ public void Add(TKey key, TValue value) /// 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); + ThrowIfNull(key); return TryInsert(index: -1, key, value, InsertionBehavior.IgnoreInsertion); } @@ -570,7 +586,7 @@ public KeyValuePair GetAt(int index) 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); + return new(e.Key, e.Value); } /// Determines the index of a specific key in the . @@ -579,7 +595,7 @@ public KeyValuePair GetAt(int index) /// is null. public int IndexOf(TKey key) { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); uint _ = 0; return IndexOf(key, ref _, ref _); @@ -696,7 +712,7 @@ public void Insert(int index, TKey key, TValue value) ThrowHelper.ThrowIndexArgumentOutOfRange(); } - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); TryInsert(index, key, value, InsertionBehavior.ThrowOnExisting); } @@ -712,7 +728,7 @@ public void Insert(int index, TKey key, TValue value) /// true if the element is successfully found and removed; otherwise, false. public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value) { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); // Find the key. int index = IndexOf(key); @@ -784,7 +800,7 @@ public void SetAt(int index, TKey key, TValue value) ThrowHelper.ThrowIndexArgumentOutOfRange(); } - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); Debug.Assert(_entries is not null); ref Entry e = ref _entries[index]; @@ -836,7 +852,7 @@ public void SetAt(int index, TKey key, TValue value) /// is negative. public int EnsureCapacity(int capacity) { - ArgumentOutOfRangeException.ThrowIfNegative(capacity); + ThrowIfNegative(capacity); if (Capacity < capacity) { @@ -863,7 +879,7 @@ public int EnsureCapacity(int capacity) /// is less than . public void TrimExcess(int capacity) { - ArgumentOutOfRangeException.ThrowIfLessThan(capacity, Count); + ThrowIfLessThan(capacity, Count); int currentCapacity = _entries?.Length ?? 0; capacity = HashHelpers.GetPrime(capacity); @@ -882,7 +898,7 @@ public void TrimExcess(int capacity) /// 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); + ThrowIfNull(key); // Find the key. int index = IndexOf(key); @@ -1013,8 +1029,10 @@ private void UpdateBucketIndex(int entryIndex, int shiftAmount) /// and does so if necessary. /// [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 && @@ -1024,6 +1042,7 @@ private void RehashIfNecessary(uint collisionCount, Entry[] entries) // Switch to a randomized comparer and rehash. Resize(entries.Length, forceNewHashCodes: true); } +#endif } /// Grow or shrink and to the specified capacity. @@ -1052,6 +1071,7 @@ private void Resize(int newSize, bool forceNewHashCodes = false) 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) { @@ -1067,6 +1087,7 @@ private void Resize(int newSize, bool forceNewHashCodes = false) 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. @@ -1120,7 +1141,7 @@ IEnumerator> IEnumerable>. /// int IList>.IndexOf(KeyValuePair item) { - ArgumentNullException.ThrowIfNull(item.Key, nameof(item)); + ThrowIfNull(item.Key, nameof(item)); int index = IndexOf(item.Key); if (index >= 0) @@ -1144,7 +1165,7 @@ int IList>.IndexOf(KeyValuePair item) /// bool ICollection>.Contains(KeyValuePair item) { - ArgumentNullException.ThrowIfNull(item.Key, nameof(item)); + ThrowIfNull(item.Key, nameof(item)); return TryGetValue(item.Key, out TValue? value) && @@ -1154,8 +1175,8 @@ bool ICollection>.Contains(KeyValuePair /// void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { - ArgumentNullException.ThrowIfNull(array); - ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); + ThrowIfNull(array); + ThrowIfNegative(arrayIndex); if (array.Length - arrayIndex < _count) { throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); @@ -1177,10 +1198,10 @@ bool ICollection>.Remove(KeyValuePair i /// void IDictionary.Add(object key, object? value) { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); if (default(TValue) is not null) { - ArgumentNullException.ThrowIfNull(value); + ThrowIfNull(value); } if (key is not TKey tkey) @@ -1190,7 +1211,7 @@ void IDictionary.Add(object key, object? value) if (default(TValue) is not null) { - ArgumentNullException.ThrowIfNull(value); + ThrowIfNull(value); } TValue tvalue = default!; @@ -1210,7 +1231,7 @@ void IDictionary.Add(object key, object? value) /// bool IDictionary.Contains(object key) { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); return key is TKey tkey && ContainsKey(tkey); } @@ -1218,7 +1239,7 @@ bool IDictionary.Contains(object key) /// void IDictionary.Remove(object key) { - ArgumentNullException.ThrowIfNull(key); + ThrowIfNull(key); if (key is TKey tkey) { @@ -1229,7 +1250,7 @@ void IDictionary.Remove(object key) /// void ICollection.CopyTo(Array array, int index) { - ArgumentNullException.ThrowIfNull(array); + ThrowIfNull(array); if (array.Rank != 1) { @@ -1241,7 +1262,7 @@ void ICollection.CopyTo(Array array, int index) throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array)); } - ArgumentOutOfRangeException.ThrowIfNegative(index); + ThrowIfNegative(index); if (array.Length - index < _count) { @@ -1450,8 +1471,8 @@ public sealed class KeyCollection : IList, IReadOnlyList, IList /// public void CopyTo(TKey[] array, int arrayIndex) { - ArgumentNullException.ThrowIfNull(array); - ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); + ThrowIfNull(array); + ThrowIfNegative(arrayIndex); OrderedDictionary dictionary = _dictionary; int count = dictionary._count; @@ -1472,7 +1493,7 @@ public void CopyTo(TKey[] array, int arrayIndex) /// void ICollection.CopyTo(Array array, int index) { - ArgumentNullException.ThrowIfNull(array); + ThrowIfNull(array); if (array.Rank != 1) { @@ -1484,7 +1505,7 @@ void ICollection.CopyTo(Array array, int index) throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array)); } - ArgumentOutOfRangeException.ThrowIfNegative(index); + ThrowIfNegative(index); if (array.Length - index < _dictionary.Count) { @@ -1639,8 +1660,8 @@ public sealed class ValueCollection : IList, IReadOnlyList, ILis /// public void CopyTo(TValue[] array, int arrayIndex) { - ArgumentNullException.ThrowIfNull(array); - ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); + ThrowIfNull(array); + ThrowIfNegative(arrayIndex); OrderedDictionary dictionary = _dictionary; int count = dictionary._count; @@ -1781,7 +1802,7 @@ int IList.IndexOf(object? value) /// void ICollection.CopyTo(Array array, int index) { - ArgumentNullException.ThrowIfNull(array); + ThrowIfNull(array); if (array.Rank != 1) { @@ -1793,7 +1814,7 @@ void ICollection.CopyTo(Array array, int index) throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array)); } - ArgumentOutOfRangeException.ThrowIfNegative(index); + ThrowIfNegative(index); if (array.Length - index < _dictionary.Count) { diff --git a/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs b/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs index 1f40b6185ce5c7..7ee8b3b929076a 100644 --- a/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs +++ b/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace System.Collections { @@ -32,5 +33,65 @@ internal static void ThrowIndexArgumentOutOfRange() => [DoesNotReturn] internal static void ThrowVersionCheckFailed() => throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); + +#if !NET8_0_OR_GREATER + /// Throws an if is null. + /// The reference type argument to validate as non-null. + /// The name of the parameter with which corresponds. + public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) + { + if (argument is null) + { + ThrowNull(paramName); + } + } + + /// Throws an if is negative. + /// The argument to validate as non-negative. + /// The name of the parameter with which corresponds. + public static void ThrowIfNegative(int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (value < 0) + ThrowNegative(value, paramName); + } + + /// Throws an if is greater than . + /// The argument to validate as less or equal than . + /// The value to compare with . + /// The name of the parameter with which corresponds. + public static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null) + where T : IComparable + { + if (value.CompareTo(other) > 0) + ThrowGreater(value, other, paramName); + } + + /// Throws an if is less than . + /// The argument to validate as greatar than or equal than . + /// The value to compare with . + /// The name of the parameter with which corresponds. + public static void ThrowIfLessThan(T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null) + where T : IComparable + { + if (value.CompareTo(other) < 0) + ThrowLess(value, other, paramName); + } + + [DoesNotReturn] + private static void ThrowNull(string? paramName) => + throw new ArgumentNullException(paramName); + + [DoesNotReturn] + private static void ThrowNegative(int value, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeNonNegative, paramName, value)); + + [DoesNotReturn] + private static void ThrowGreater(T value, T other, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeLessOrEqual, paramName, value, other)); + + [DoesNotReturn] + private static void ThrowLess(T value, T other, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual, paramName, value, other)); +#endif } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ICollectionDebugView.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ICollectionDebugView.cs index 4e58fc58b20265..424694d4f8e0ba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ICollectionDebugView.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ICollectionDebugView.cs @@ -11,7 +11,14 @@ internal sealed class ICollectionDebugView public ICollectionDebugView(ICollection collection) { +#if NET8_0_OR_GREATER ArgumentNullException.ThrowIfNull(collection); +#else + if (collection is null) + { + throw new ArgumentNullException(nameof(collection)); + } +#endif _collection = collection; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/CollectionHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/CollectionHelpers.cs index 72ce5ec5e9f200..3896ea5543561a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/CollectionHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/CollectionHelpers.cs @@ -9,7 +9,14 @@ internal static class CollectionHelpers { internal static void ValidateCopyToArguments(int sourceCount, Array array, int index) { +#if NET8_0_OR_GREATER ArgumentNullException.ThrowIfNull(array); +#else + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } +#endif if (array.Rank != 1) { @@ -21,8 +28,15 @@ internal static void ValidateCopyToArguments(int sourceCount, Array array, int i throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array)); } +#if NET8_0_OR_GREATER ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThan(index, array.Length); +#else + if (index < 0 || index > array.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } +#endif if (array.Length - index < sourceCount) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs index 55bd89258cc4dd..232affb7e550f0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs @@ -4,7 +4,12 @@ namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] - public sealed class CallerArgumentExpressionAttribute : Attribute +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class CallerArgumentExpressionAttribute : Attribute { public CallerArgumentExpressionAttribute(string parameterName) { diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index d7b73df8a9e87c..63b26b0fb9bbd3 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -761,4 +761,44 @@ The depth of the generated JSON schema exceeds the JsonSerializerOptions.MaxDepth setting. + + + The value '{0}' is not of type '{1}' and cannot be used in this generic collection. + + + Destination array is not long enough to copy all the items in the collection. Check array index and length. + + + Only single dimensional arrays are supported for the requested action. + + + The lower bound of target array must be zero. + + + Target array type is not compatible with the type of items in the collection. + + + The given key '{0}' was not present in the dictionary. + + + An item with the same key has already been added. Key: {0} + + + Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct. + + + Collection was modified after the enumerator was instantiated. + + + {0} ('{1}') must be a non-negative value. + + + {0} ('{1}') must be greater than or equal to '{2}'. + + + {0} ('{1}') must be less than or equal to '{2}'. + + + Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table. + diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 28aef5494651c6..736c6be2fdbfa8 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -358,9 +358,15 @@ The System.Text.Json library is built-in as part of the shared framework in .NET - - - + + + + + + + + + @@ -382,6 +388,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.KeyCollection.cs b/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.KeyCollection.cs deleted file mode 100644 index e366c4a5774771..00000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.KeyCollection.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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; -using System.Collections.Generic; - -namespace System.Text.Json -{ - internal sealed partial class OrderedDictionary - { - private sealed class KeyCollection : IList - { - private readonly OrderedDictionary _parent; - - public KeyCollection(OrderedDictionary parent) - { - _parent = parent; - } - - public int Count => _parent.Count; - - public bool IsReadOnly => true; - - public TKey this[int index] - { - get => _parent.GetAt(index).Key; - set => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - foreach (KeyValuePair item in _parent) - { - yield return item.Key; - } - } - - public void Add(TKey propertyName) => ThrowHelper.ThrowNotSupportedException_CollectionIsReadOnly(); - - public void Clear() => ThrowHelper.ThrowNotSupportedException_CollectionIsReadOnly(); - - public bool Contains(TKey propertyName) => _parent.ContainsKey(propertyName); - - public void CopyTo(TKey[] propertyNameArray, int index) - { - if (index < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException_ArrayIndexNegative(nameof(index)); - } - - foreach (KeyValuePair item in _parent) - { - if (index >= propertyNameArray.Length) - { - ThrowHelper.ThrowArgumentException_ArrayTooSmall(nameof(propertyNameArray)); - } - - propertyNameArray[index++] = item.Key; - } - } - - public IEnumerator GetEnumerator() - { - foreach (KeyValuePair item in _parent) - { - yield return item.Key; - } - } - - bool ICollection.Remove(TKey propertyName) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - public int IndexOf(TKey item) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - public void Insert(int index, TKey item) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - public void RemoveAt(int index) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - } - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.ValueCollection.cs b/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.ValueCollection.cs deleted file mode 100644 index ae9993afa91432..00000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.ValueCollection.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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; -using System.Collections.Generic; - -namespace System.Text.Json -{ - internal sealed partial class OrderedDictionary - { - private sealed class ValueCollection : IList - { - private readonly OrderedDictionary _parent; - - public ValueCollection(OrderedDictionary parent) - { - _parent = parent; - } - - public int Count => _parent.Count; - - public bool IsReadOnly => true; - - public TValue this[int index] - { - get => _parent.GetAt(index).Value; - set => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - foreach (KeyValuePair item in _parent) - { - yield return item.Value; - } - } - - public void Add(TValue value) => ThrowHelper.ThrowNotSupportedException_CollectionIsReadOnly(); - - public void Clear() => ThrowHelper.ThrowNotSupportedException_CollectionIsReadOnly(); - - public bool Contains(TValue value) - { - EqualityComparer comparer = _parent._valueComparer; - foreach (KeyValuePair item in _parent._propertyList) - { - if (comparer.Equals(item.Value, value)) - { - return true; - } - } - - return false; - } - - public void CopyTo(TValue[] destination, int index) - { - if (index < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException_ArrayIndexNegative(nameof(index)); - } - - foreach (KeyValuePair item in _parent) - { - if (index >= destination.Length) - { - ThrowHelper.ThrowArgumentException_ArrayTooSmall(nameof(destination)); - } - - destination[index++] = item.Value; - } - } - - public IEnumerator GetEnumerator() - { - foreach (KeyValuePair item in _parent) - { - yield return item.Value; - } - } - - bool ICollection.Remove(TValue value) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - public int IndexOf(TValue item) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - public void Insert(int index, TValue value) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - public void RemoveAt(int index) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); - } - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.cs deleted file mode 100644 index 2ca4750eb2adfc..00000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/OrderedDictionary.cs +++ /dev/null @@ -1,321 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -namespace System.Text.Json -{ - /// - /// Polyfill for System.Collections.Generic.OrderedDictionary added in .NET 9. - /// - internal sealed partial class OrderedDictionary : IDictionary, IList> - where TKey : notnull - { - private const int ListToDictionaryThreshold = 9; - - private Dictionary? _propertyDictionary; - private readonly List> _propertyList; - private readonly IEqualityComparer _keyComparer; - private readonly EqualityComparer _valueComparer = EqualityComparer.Default; - - public OrderedDictionary(int capacity, IEqualityComparer? keyComparer = null) - { - _keyComparer = keyComparer ?? EqualityComparer.Default; - _propertyList = new(capacity); - if (capacity > ListToDictionaryThreshold) - { - _propertyDictionary = new(capacity, _keyComparer); - } - } - - public void Add(TKey key, TValue value) - { - if (!TryAdd(key, value)) - { - ThrowHelper.ThrowArgumentException_DuplicateKey(nameof(key), key); - } - } - - public void Clear() - { - _propertyList.Clear(); - _propertyDictionary?.Clear(); - } - - public bool ContainsKey(TKey key) - { - return _propertyDictionary is { } dict - ? dict.ContainsKey(key) - : IndexOf(key) >= 0; - } - - public int Count => _propertyList.Count; - public List>.Enumerator GetEnumerator() => _propertyList.GetEnumerator(); - - public ICollection Keys => _keys ??= new(this); - private KeyCollection? _keys; - - public ICollection Values => _values ??= new(this); - private ValueCollection? _values; - - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) - { - if (key is null) - { - ThrowHelper.ThrowArgumentNullException(nameof(key)); - } - - if (_propertyDictionary is { } dict) - { - return dict.TryGetValue(key, out value); - } - else - { - IEqualityComparer comparer = _keyComparer; - foreach (KeyValuePair item in _propertyList) - { - if (comparer.Equals(key, item.Key)) - { - value = item.Value; - return true; - } - } - } - - value = default; - return false; - } - - public TValue this[TKey key] - { - get - { - if (!TryGetValue(key, out TValue? value)) - { - ThrowHelper.ThrowKeyNotFoundException(); - } - - return value; - } - - set - { - if (_propertyDictionary is { } dict) - { - dict[key] = value; - } - - KeyValuePair item = new(key, value); - int i = IndexOf(key); - if (i < 0) - { - _propertyList.Add(item); - } - else - { - _propertyList[i] = item; - } - } - } - - public bool TryAdd(TKey key, TValue value) - { - if (key is null) - { - ThrowHelper.ThrowArgumentNullException(nameof(key)); - } - - CreateDictionaryIfThresholdMet(); - - if (_propertyDictionary is { } dict) - { - if (!dict.TryAdd(key, value)) - { - return false; - } - } - else if (IndexOf(key) >= 0) - { - return false; - } - - _propertyList.Add(new(key, value)); - return true; - } - - private void CreateDictionaryIfThresholdMet() - { - if (_propertyDictionary == null && _propertyList.Count > ListToDictionaryThreshold) - { - _propertyDictionary = JsonHelpers.CreateDictionaryFromCollection(_propertyList, _keyComparer); - } - } - - public int IndexOf(TKey key) - { - if (key is null) - { - ThrowHelper.ThrowArgumentNullException(nameof(key)); - } - - List> propertyList = _propertyList; - IEqualityComparer keyComparer = _keyComparer; - - for (int i = 0; i < propertyList.Count; i++) - { - if (keyComparer.Equals(key, propertyList[i].Key)) - { - return i; - } - } - - return -1; - } - - public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue existing) - { - if (_propertyDictionary != null) - { - if (!_propertyDictionary.TryGetValue(key, out existing)) - { - return false; - } - - bool success = _propertyDictionary.Remove(key); - Debug.Assert(success); - } - - for (int i = 0; i < _propertyList.Count; i++) - { - KeyValuePair current = _propertyList[i]; - - if (_keyComparer.Equals(current.Key, key)) - { - _propertyList.RemoveAt(i); - existing = current.Value; - return true; - } - } - - existing = default; - return false; - } - - public KeyValuePair GetAt(int index) => _propertyList[index]; - - public void SetAt(int index, TKey key, TValue value) - { - TKey existingKey = _propertyList[index].Key; - if (!_keyComparer.Equals(existingKey, key)) - { - if (ContainsKey(key)) - { - // The key already exists in a different position, throw an exception. - ThrowHelper.ThrowArgumentException_DuplicateKey(nameof(key), key); - } - - _propertyDictionary?.Remove(existingKey); - } - - if (_propertyDictionary != null) - { - _propertyDictionary[key] = value; - } - - _propertyList[index] = new(key, value); - } - - public void SetAt(int index, TValue value) - { - TKey key = _propertyList[index].Key; - if (_propertyDictionary != null) - { - _propertyDictionary[key] = value; - } - - _propertyList[index] = new(key, value); - } - - public void Insert(int index, TKey key, TValue value) - { - if (key is null) - { - ThrowHelper.ThrowArgumentNullException(nameof(key)); - } - - if (ContainsKey(key)) - { - ThrowHelper.ThrowArgumentException_DuplicateKey(nameof(key), key); - } - - _propertyList.Insert(index, new(key, value)); - _propertyDictionary?.Add(key, value); - } - - public void RemoveAt(int index) - { - KeyValuePair item = _propertyList[index]; - _propertyList.RemoveAt(index); - _propertyDictionary?.Remove(item.Key); - } - - IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - bool ICollection>.IsReadOnly => false; - void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); - bool ICollection>.Contains(KeyValuePair item) => TryGetValue(item.Key, out TValue? existingValue) && _valueComparer.Equals(item.Value, existingValue); - bool ICollection>.Remove(KeyValuePair item) - { - return TryGetValue(item.Key, out TValue? existingValue) && _valueComparer.Equals(existingValue, item.Value) - ? Remove(item.Key, out _) - : false; - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (arrayIndex < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException_ArrayIndexNegative(nameof(arrayIndex)); - } - - foreach (KeyValuePair item in _propertyList) - { - if (arrayIndex >= array.Length) - { - ThrowHelper.ThrowArgumentException_ArrayTooSmall(nameof(array)); - } - - array[arrayIndex++] = item; - } - } - - bool IDictionary.Remove(TKey key) => Remove(key, out _); - void IList>.Insert(int index, KeyValuePair item) => Insert(index, item.Key, item.Value); - int IList>.IndexOf(KeyValuePair item) - { - List> propertyList = _propertyList; - IEqualityComparer keyComparer = _keyComparer; - EqualityComparer valueComparer = _valueComparer; - - for (int i = 0; i < propertyList.Count; i++) - { - KeyValuePair entry = propertyList[i]; - if (keyComparer.Equals(entry.Key, item.Key) && valueComparer.Equals(item.Value, entry.Value)) - { - return i; - } - } - - return -1; - } - - KeyValuePair IList>.this[int index] - { - get => GetAt(index); - set => SetAt(index, value.Key, value.Value); - } - } -} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/ParseTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/ParseTests.cs index 721ee7bfeffcf5..7c6e82db5d30bd 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/ParseTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/ParseTests.cs @@ -159,19 +159,6 @@ public static async Task InternalValueFields() FieldInfo jsonDictionaryField = typeof(JsonObject).GetField("_dictionary", BindingFlags.Instance | BindingFlags.NonPublic); Assert.NotNull(jsonDictionaryField); -#if !NET9_0_OR_GREATER // Bespoke implementation replaced with OrderedDictionary - Type jsonPropertyDictionaryType = typeof(JsonObject).Assembly.GetType("System.Text.Json.OrderedDictionary`2"); - Assert.NotNull(jsonPropertyDictionaryType); - - jsonPropertyDictionaryType = jsonPropertyDictionaryType.MakeGenericType(new Type[] { typeof(string), typeof(JsonNode) }); - - FieldInfo listField = jsonPropertyDictionaryType.GetField("_propertyList", BindingFlags.Instance | BindingFlags.NonPublic); - Assert.NotNull(listField); - - FieldInfo dictionaryField = jsonPropertyDictionaryType.GetField("_propertyDictionary", BindingFlags.Instance | BindingFlags.NonPublic); - Assert.NotNull(dictionaryField); -#endif - using (MemoryStream stream = new MemoryStream(SimpleTestClass.s_data)) { // Only JsonElement is present. @@ -188,10 +175,6 @@ public static async Task InternalValueFields() jsonDictionary = jsonDictionaryField.GetValue(node); Assert.NotNull(jsonDictionary); -#if !NET9_0_OR_GREATER // Bespoke implementation replaced with OrderedDictionary - Assert.NotNull(listField.GetValue(jsonDictionary)); - Assert.NotNull(dictionaryField.GetValue(jsonDictionary)); // The dictionary threshold was reached. -#endif Test(); void Test() @@ -221,11 +204,6 @@ void Test() jsonDictionary = jsonDictionaryField.GetValue(node); Assert.NotNull(jsonDictionary); -#if !NET9_0_OR_GREATER // Bespoke implementation replaced with OrderedDictionary - Assert.NotNull(listField.GetValue(jsonDictionary)); - Assert.NotNull(dictionaryField.GetValue(jsonDictionary)); // The dictionary threshold was reached. -#endif - Test(); void Test() From f596a56f4944c62c685cd5061e339eb7c683eb1d Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 18 Jul 2024 10:15:42 +0100 Subject: [PATCH 2/2] Address feedback --- .../src/System/Collections/ThrowHelper.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs b/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs index 7ee8b3b929076a..64a2e552bbb64d 100644 --- a/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs +++ b/src/libraries/System.Collections/src/System/Collections/ThrowHelper.cs @@ -52,7 +52,9 @@ public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpres public static void ThrowIfNegative(int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) { if (value < 0) + { ThrowNegative(value, paramName); + } } /// Throws an if is greater than . @@ -63,7 +65,9 @@ public static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpres where T : IComparable { if (value.CompareTo(other) > 0) + { ThrowGreater(value, other, paramName); + } } /// Throws an if is less than . @@ -74,7 +78,9 @@ public static void ThrowIfLessThan(T value, T other, [CallerArgumentExpressio where T : IComparable { if (value.CompareTo(other) < 0) + { ThrowLess(value, other, paramName); + } } [DoesNotReturn]