From e92b7d0074adcdb026408bc862f11ac484edeba8 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 25 Apr 2024 16:41:23 -0400 Subject: [PATCH] Update ICollection usage to IReadOnlyCollection where applicable (#101469) Anywhere we're casting to `ICollection` just to use its `Count`, we can instead now cast to `IReadOnlyCollection`, as the former inherits the latter as of .NET 9. This expands the set of types that can light-up with the check; in a couple of places it also lets us remove what would then be a duplicative check. We can do the same for `IList` and `IReadOnlyList`. While dispatch via the DIM could result in an extra interface dispatch for types that weren't previously implementing the read-only interface, a) our collection types already implement both, and b) these cases all represent fast paths where the extra savings from the faster path should more than make up for additional call overheads. I audited for anywhere we were missing explicit implementations and added a few corner-cases in. --- .../System/Diagnostics/DiagnosticsHelper.cs | 8 +++++ .../Frozen/FrozenSetInternalBase.cs | 12 +++++++ .../Immutable/ImmutableExtensions.Minimal.cs | 2 ++ .../Collections/Generic/PriorityQueue.cs | 2 +- .../System/Collections/Generic/SortedSet.cs | 2 +- .../src/System/Dynamic/ExpandoObject.cs | 32 ++++++++++++++++++- .../src/System/Linq/ParallelEnumerable.cs | 4 +-- .../System.Linq/src/System/Linq/AnyAll.cs | 2 +- .../src/System/Linq/AppendPrepend.SpeedOpt.cs | 4 +-- .../System.Linq/src/System/Linq/Count.cs | 4 +-- .../System/Linq/DefaultIfEmpty.SpeedOpt.cs | 2 +- .../System.Linq/src/System/Linq/ElementAt.cs | 4 +-- .../System.Linq/src/System/Linq/First.cs | 2 +- .../System.Linq/src/System/Linq/Grouping.cs | 17 +++++++++- .../System.Linq/src/System/Linq/Last.cs | 4 +-- .../System/Linq/OrderedEnumerable.SpeedOpt.cs | 2 +- .../src/System/Linq/Reverse.SpeedOpt.cs | 6 ++-- .../src/System/Linq/SequenceEqual.cs | 4 +-- .../System.Linq/src/System/Linq/Single.cs | 2 +- .../ServiceNameCollection.cs | 2 +- .../Collections/Concurrent/ConcurrentQueue.cs | 2 +- .../System/Collections/Generic/Dictionary.cs | 2 +- .../src/System/Collections/Generic/HashSet.cs | 12 +++---- .../Xml/Xsl/Runtime/XmlQuerySequence.cs | 17 ++++++++-- .../src/System/Text/Json/Nodes/JsonObject.cs | 7 +++- 25 files changed, 121 insertions(+), 36 deletions(-) diff --git a/src/libraries/Common/src/System/Diagnostics/DiagnosticsHelper.cs b/src/libraries/Common/src/System/Diagnostics/DiagnosticsHelper.cs index ce7f345b0ea9cd..dcc9de3e4ad654 100644 --- a/src/libraries/Common/src/System/Diagnostics/DiagnosticsHelper.cs +++ b/src/libraries/Common/src/System/Diagnostics/DiagnosticsHelper.cs @@ -35,14 +35,22 @@ internal static bool CompareTags(List>? sortedTags int size = count / (sizeof(ulong) * 8) + 1; BitMapper bitMapper = new BitMapper(size <= 100 ? stackalloc ulong[size] : new ulong[size]); +#if NET9_0_OR_GREATER // ICollection : IReadOnlyCollection on .NET 9+ + if (tags2 is IReadOnlyCollection> tagsCol) +#else if (tags2 is ICollection> tagsCol) +#endif { if (tagsCol.Count != count) { return false; } +#if NET9_0_OR_GREATER // IList : IReadOnlyList on .NET 9+ + if (tagsCol is IReadOnlyList> secondList) +#else if (tagsCol is IList> secondList) +#endif { for (int i = 0; i < count; i++) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSetInternalBase.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSetInternalBase.cs index 4afe1f7503e757..02e5e0cc07cb25 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSetInternalBase.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSetInternalBase.cs @@ -32,7 +32,11 @@ private protected override bool IsProperSubsetOfCore(IEnumerable other) { Debug.Assert(_thisSet.Count != 0, "EmptyFrozenSet should have been used."); +#if NET9_0_OR_GREATER // ICollection : IReadOnlyCollection on .NET 9+ + if (other is IReadOnlyCollection otherAsCollection) +#else if (other is ICollection otherAsCollection) +#endif { int otherCount = otherAsCollection.Count; @@ -59,7 +63,11 @@ private protected override bool IsProperSupersetOfCore(IEnumerable other) { Debug.Assert(_thisSet.Count != 0, "EmptyFrozenSet should have been used."); +#if NET9_0_OR_GREATER // ICollection : IReadOnlyCollection on .NET 9+ + if (other is IReadOnlyCollection otherAsCollection) +#else if (other is ICollection otherAsCollection) +#endif { int otherCount = otherAsCollection.Count; @@ -103,7 +111,11 @@ private protected override bool IsSupersetOfCore(IEnumerable other) Debug.Assert(_thisSet.Count != 0, "EmptyFrozenSet should have been used."); // Try to compute the answer based purely on counts. +#if NET9_0_OR_GREATER // ICollection : IReadOnlyCollection on .NET 9+ + if (other is IReadOnlyCollection otherAsCollection) +#else if (other is ICollection otherAsCollection) +#endif { int otherCount = otherAsCollection.Count; diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs index 66b365bad872b7..dc9c7d26898b7e 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs @@ -38,11 +38,13 @@ internal static bool TryGetCount(this IEnumerable sequence, out int count) return true; } +#if !NET9_0_OR_GREATER // ICollection : IReadOnlyCollection on .NET 9+ if (sequence is ICollection collectionOfT) { count = collectionOfT.Count; return true; } +#endif if (sequence is IReadOnlyCollection readOnlyCollection) { diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs b/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs index 5047b764337389..072e5ac34d605c 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs @@ -462,7 +462,7 @@ public void EnqueueRange(IEnumerable elements, TPriority priority) ArgumentNullException.ThrowIfNull(elements); int count; - if (elements is ICollection collection && + if (elements is IReadOnlyCollection collection && (count = collection.Count) > _nodes.Length - _size) { Grow(checked(_size + count)); diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs b/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs index 2f7406d0d7bf4c..fd0beccb28a77d 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs @@ -1323,7 +1323,7 @@ public bool Overlaps(IEnumerable other) if (Count == 0) return false; - if (other is ICollection c && c.Count == 0) + if (other is IReadOnlyCollection c && c.Count == 0) return false; SortedSet? asSorted = other as SortedSet; diff --git a/src/libraries/System.Linq.Expressions/src/System/Dynamic/ExpandoObject.cs b/src/libraries/System.Linq.Expressions/src/System/Dynamic/ExpandoObject.cs index 288d01c185dbf5..906ee3fd84fc2f 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Dynamic/ExpandoObject.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Dynamic/ExpandoObject.cs @@ -17,7 +17,7 @@ namespace System.Dynamic /// /// Represents an object with members that can be dynamically added and removed at runtime. /// - public sealed class ExpandoObject : IDynamicMetaObjectProvider, IDictionary, INotifyPropertyChanged + public sealed class ExpandoObject : IDynamicMetaObjectProvider, IDictionary, IReadOnlyDictionary, INotifyPropertyChanged { private static readonly MethodInfo s_expandoTryGetValue = typeof(RuntimeOps).GetMethod(nameof(RuntimeOps.ExpandoTryGetValue))!; @@ -618,6 +618,10 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() ICollection IDictionary.Values => new ValueCollection(this); + IEnumerable IReadOnlyDictionary.Keys => new KeyCollection(this); + + IEnumerable IReadOnlyDictionary.Values => new ValueCollection(this); + object? IDictionary.this[string key] { get @@ -636,6 +640,18 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() } } + object? IReadOnlyDictionary.this[string key] + { + get + { + if (!TryGetValueForKey(key, out object? value)) + { + throw System.Linq.Expressions.Error.KeyDoesNotExistInExpando(key); + } + return value; + } + } + void IDictionary.Add(string key, object? value) { this.TryAddMember(key, value); @@ -650,6 +666,15 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() return index >= 0 && data[index] != Uninitialized; } + bool IReadOnlyDictionary.ContainsKey(string key) + { + ArgumentNullException.ThrowIfNull(key); + + ExpandoData data = _data; + int index = data.Class.GetValueIndexCaseSensitive(key); + return index >= 0 && data[index] != Uninitialized; + } + bool IDictionary.Remove(string key) { ArgumentNullException.ThrowIfNull(key); @@ -662,6 +687,11 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() return TryGetValueForKey(key, out value); } + bool IReadOnlyDictionary.TryGetValue(string key, out object? value) + { + return TryGetValueForKey(key, out value); + } + #endregion #region ICollection> Members diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs index 55ef371ea04226..f0885d3d3e7d48 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs @@ -1852,7 +1852,7 @@ public static int Count(this ParallelQuery source) // If the data source is a collection, we can just return the count right away. if (source is ParallelEnumerableWrapper sourceAsWrapper) { - if (sourceAsWrapper.WrappedEnumerable is ICollection sourceAsCollection) + if (sourceAsWrapper.WrappedEnumerable is IReadOnlyCollection sourceAsCollection) { return sourceAsCollection.Count; } @@ -1923,7 +1923,7 @@ public static long LongCount(this ParallelQuery source) // If the data source is a collection, we can just return the count right away. if (source is ParallelEnumerableWrapper sourceAsWrapper) { - if (sourceAsWrapper.WrappedEnumerable is ICollection sourceAsCollection) + if (sourceAsWrapper.WrappedEnumerable is IReadOnlyCollection sourceAsCollection) { return sourceAsCollection.Count; } diff --git a/src/libraries/System.Linq/src/System/Linq/AnyAll.cs b/src/libraries/System.Linq/src/System/Linq/AnyAll.cs index 28a3783aa88297..d6d6e4adccdeba 100644 --- a/src/libraries/System.Linq/src/System/Linq/AnyAll.cs +++ b/src/libraries/System.Linq/src/System/Linq/AnyAll.cs @@ -15,7 +15,7 @@ public static bool Any(this IEnumerable source) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - if (source is ICollection gc) + if (source is IReadOnlyCollection gc) { return gc.Count != 0; } diff --git a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs index 01adb0af01e456..cd6fb6fa171ad7 100644 --- a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs @@ -127,7 +127,7 @@ public override int GetCount(bool onlyIfCheap) return count == -1 ? -1 : count + 1; } - return !onlyIfCheap || _source is ICollection ? _source.Count() + 1 : -1; + return !onlyIfCheap || _source is IReadOnlyCollection ? _source.Count() + 1 : -1; } public override TSource? TryGetFirst(out bool found) @@ -276,7 +276,7 @@ public override int GetCount(bool onlyIfCheap) return count == -1 ? -1 : count + _appendCount + _prependCount; } - return !onlyIfCheap || _source is ICollection ? _source.Count() + _appendCount + _prependCount : -1; + return !onlyIfCheap || _source is IReadOnlyCollection ? _source.Count() + _appendCount + _prependCount : -1; } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Count.cs b/src/libraries/System.Linq/src/System/Linq/Count.cs index 6b8819f8c3cbcb..9b2511e0563c5a 100644 --- a/src/libraries/System.Linq/src/System/Linq/Count.cs +++ b/src/libraries/System.Linq/src/System/Linq/Count.cs @@ -15,7 +15,7 @@ public static int Count(this IEnumerable source) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - if (source is ICollection collectionoft) + if (source is IReadOnlyCollection collectionoft) { return collectionoft.Count; } @@ -101,7 +101,7 @@ public static bool TryGetNonEnumeratedCount(this IEnumerable s ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - if (source is ICollection collectionoft) + if (source is IReadOnlyCollection collectionoft) { count = collectionoft.Count; return true; diff --git a/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs index c89d6797581e9b..45f673f73486ae 100644 --- a/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs @@ -30,7 +30,7 @@ public override List ToList() public override int GetCount(bool onlyIfCheap) { int count; - if (!onlyIfCheap || _source is ICollection || _source is ICollection) + if (!onlyIfCheap || _source is IReadOnlyCollection || _source is ICollection) { count = _source.Count(); } diff --git a/src/libraries/System.Linq/src/System/Linq/ElementAt.cs b/src/libraries/System.Linq/src/System/Linq/ElementAt.cs index 97b87f9eba9999..824a152a7764c2 100644 --- a/src/libraries/System.Linq/src/System/Linq/ElementAt.cs +++ b/src/libraries/System.Linq/src/System/Linq/ElementAt.cs @@ -16,7 +16,7 @@ public static TSource ElementAt(this IEnumerable source, int i ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - if (source is IList list) + if (source is IReadOnlyList list) { return list[index]; } @@ -115,7 +115,7 @@ public static TSource ElementAt(this IEnumerable source, Index private static TSource? TryGetElementAt(this IEnumerable source, int index, out bool found) { - if (source is IList list) + if (source is IReadOnlyList list) { return (found = (uint)index < (uint)list.Count) ? list[index] : diff --git a/src/libraries/System.Linq/src/System/Linq/First.cs b/src/libraries/System.Linq/src/System/Linq/First.cs index 74ff81c2fec83b..22532cdb18d13a 100644 --- a/src/libraries/System.Linq/src/System/Linq/First.cs +++ b/src/libraries/System.Linq/src/System/Linq/First.cs @@ -78,7 +78,7 @@ public static TSource FirstOrDefault(this IEnumerable source, private static TSource? TryGetFirstNonIterator(IEnumerable source, out bool found) { - if (source is IList list) + if (source is IReadOnlyList list) { if (list.Count > 0) { diff --git a/src/libraries/System.Linq/src/System/Linq/Grouping.cs b/src/libraries/System.Linq/src/System/Linq/Grouping.cs index 6e19e4ab384e44..47be36572acad0 100644 --- a/src/libraries/System.Linq/src/System/Linq/Grouping.cs +++ b/src/libraries/System.Linq/src/System/Linq/Grouping.cs @@ -351,7 +351,7 @@ public interface IGrouping : IEnumerable [DebuggerDisplay("Key = {Key}")] [DebuggerTypeProxy(typeof(SystemLinq_GroupingDebugView<,>))] - internal sealed class Grouping : IGrouping, IList + internal sealed class Grouping : IGrouping, IList, IReadOnlyList { internal readonly TKey _key; internal readonly int _hashCode; @@ -398,6 +398,8 @@ public IEnumerator GetEnumerator() int ICollection.Count => _count; + int IReadOnlyCollection.Count => _count; + bool ICollection.IsReadOnly => true; void ICollection.Add(TElement item) => ThrowHelper.ThrowNotSupportedException(); @@ -431,5 +433,18 @@ TElement IList.this[int index] set => ThrowHelper.ThrowNotSupportedException(); } + + TElement IReadOnlyList.this[int index] + { + get + { + if ((uint)index >= (uint)_count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + + return _elements[index]; + } + } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Last.cs b/src/libraries/System.Linq/src/System/Linq/Last.cs index 007ee1659c3f5f..e17e409e25e3f7 100644 --- a/src/libraries/System.Linq/src/System/Linq/Last.cs +++ b/src/libraries/System.Linq/src/System/Linq/Last.cs @@ -77,7 +77,7 @@ public static TSource LastOrDefault(this IEnumerable source, F private static TSource? TryGetLastNonIterator(IEnumerable source, out bool found) { - if (source is IList list) + if (source is IReadOnlyList list) { int count = list.Count; if (count > 0) @@ -126,7 +126,7 @@ public static TSource LastOrDefault(this IEnumerable source, F return ordered.TryGetLast(predicate, out found); } - if (source is IList list) + if (source is IReadOnlyList list) { for (int i = list.Count - 1; i >= 0; --i) { diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs index bf65a34b06efa7..ac9225303b426f 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs @@ -58,7 +58,7 @@ public override int GetCount(bool onlyIfCheap) return iterator.GetCount(onlyIfCheap); } - return !onlyIfCheap || _source is ICollection || _source is ICollection ? _source.Count() : -1; + return !onlyIfCheap || _source is IReadOnlyCollection || _source is ICollection ? _source.Count() : -1; } internal TElement[] ToArray(int minIdx, int maxIdx) diff --git a/src/libraries/System.Linq/src/System/Linq/Reverse.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Reverse.SpeedOpt.cs index d1ec26de879afd..168ca818c96119 100644 --- a/src/libraries/System.Linq/src/System/Linq/Reverse.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Reverse.SpeedOpt.cs @@ -30,7 +30,7 @@ public override int GetCount(bool onlyIfCheap) => public override TSource? TryGetElementAt(int index, out bool found) { - if (_source is IList list) + if (_source is IReadOnlyList list) { int count = list.Count; if ((uint)index < (uint)count) @@ -59,7 +59,7 @@ public override int GetCount(bool onlyIfCheap) => { return iterator.TryGetLast(out found); } - else if (_source is IList list) + else if (_source is IReadOnlyList list) { int count = list.Count; if (count > 0) @@ -95,7 +95,7 @@ public override int GetCount(bool onlyIfCheap) => { return iterator.TryGetFirst(out found); } - else if (_source is IList list) + else if (_source is IReadOnlyList list) { if (list.Count > 0) { diff --git a/src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs b/src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs index 16bf60e686145e..2568721f1ce169 100644 --- a/src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs +++ b/src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs @@ -22,7 +22,7 @@ public static bool SequenceEqual(this IEnumerable first, IEnum ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second); } - if (first is ICollection firstCol && second is ICollection secondCol) + if (first is IReadOnlyCollection firstCol && second is IReadOnlyCollection secondCol) { if (first.TryGetSpan(out ReadOnlySpan firstSpan) && second.TryGetSpan(out ReadOnlySpan secondSpan)) { @@ -34,7 +34,7 @@ public static bool SequenceEqual(this IEnumerable first, IEnum return false; } - if (firstCol is IList firstList && secondCol is IList secondList) + if (firstCol is IReadOnlyList firstList && secondCol is IReadOnlyList secondList) { comparer ??= EqualityComparer.Default; diff --git a/src/libraries/System.Linq/src/System/Linq/Single.cs b/src/libraries/System.Linq/src/System/Linq/Single.cs index 9c7328cc3ff16e..78e548e680669f 100644 --- a/src/libraries/System.Linq/src/System/Linq/Single.cs +++ b/src/libraries/System.Linq/src/System/Linq/Single.cs @@ -69,7 +69,7 @@ public static TSource SingleOrDefault(this IEnumerable source, ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - if (source is IList list) + if (source is IReadOnlyList list) { switch (list.Count) { diff --git a/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ServiceNameCollection.cs b/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ServiceNameCollection.cs index 532f6e950d6b0d..4abe58873916af 100644 --- a/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ServiceNameCollection.cs +++ b/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ServiceNameCollection.cs @@ -147,7 +147,7 @@ private void AddIfNew(string serviceName) /// private static int GetCountOrOne(IEnumerable collection) { - ICollection? c = collection as ICollection; + IReadOnlyCollection? c = collection as IReadOnlyCollection; return c != null ? c.Count : 1; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs index bfa9fcf65469ed..2eff1c59b12ec4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs @@ -89,7 +89,7 @@ public ConcurrentQueue(IEnumerable collection) // case we round its length up to a power of 2, as all segments must // be a power of 2 in length. int length = InitialSegmentLength; - if (collection is ICollection c) + if (collection is IReadOnlyCollection c) { int count = c.Count; if (count > length) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index 9b18d4a125ed21..f20e3c5cfee211 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -98,7 +98,7 @@ public Dictionary(IDictionary dictionary, IEqualityComparer? public Dictionary(IEnumerable> collection) : this(collection, null) { } public Dictionary(IEnumerable> collection, IEqualityComparer? comparer) : - this((collection as ICollection>)?.Count ?? 0, comparer) + this((collection as IReadOnlyCollection>)?.Count ?? 0, comparer) { if (collection == null) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs index 3b86548d20a7e4..6467164d6becbc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs @@ -99,7 +99,7 @@ public HashSet(IEnumerable collection, IEqualityComparer? comparer) : this { // To avoid excess resizes, first set size based on collection's count. The collection may // contain duplicates, so call TrimExcess if resulting HashSet is larger than the threshold. - if (collection is ICollection coll) + if (collection is IReadOnlyCollection coll) { int count = coll.Count; if (count > 0) @@ -513,7 +513,7 @@ public void IntersectWith(IEnumerable other) } // If other is known to be empty, intersection is empty set; remove all elements, and we're done. - if (other is ICollection otherAsCollection) + if (other is IReadOnlyCollection otherAsCollection) { if (otherAsCollection.Count == 0) { @@ -652,7 +652,7 @@ public bool IsProperSubsetOf(IEnumerable other) return false; } - if (other is ICollection otherAsCollection) + if (other is IReadOnlyCollection otherAsCollection) { // No set is a proper subset of an empty set. if (otherAsCollection.Count == 0) @@ -701,7 +701,7 @@ public bool IsSupersetOf(IEnumerable other) } // Try to fall out early based on counts. - if (other is ICollection otherAsCollection) + if (other is IReadOnlyCollection otherAsCollection) { // If other is the empty set then this is a superset. if (otherAsCollection.Count == 0) @@ -745,7 +745,7 @@ public bool IsProperSupersetOf(IEnumerable other) return false; } - if (other is ICollection otherAsCollection) + if (other is IReadOnlyCollection otherAsCollection) { // If other is the empty set then this is a superset. if (otherAsCollection.Count == 0) @@ -838,7 +838,7 @@ public bool SetEquals(IEnumerable other) { // If this count is 0 but other contains at least one element, they can't be equal. if (Count == 0 && - other is ICollection otherAsCollection && + other is IReadOnlyCollection otherAsCollection && otherAsCollection.Count > 0) { return false; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQuerySequence.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQuerySequence.cs index 3c7e5b6ad25c32..23fc7a5c2e65a7 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQuerySequence.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQuerySequence.cs @@ -19,7 +19,7 @@ namespace System.Xml.Xsl.Runtime /// A sequence of Xml values that dynamically expands and allows random access to items. /// [EditorBrowsable(EditorBrowsableState.Never)] - public class XmlQuerySequence : IList, System.Collections.IList + public class XmlQuerySequence : IList, IReadOnlyList, System.Collections.IList { public static readonly XmlQuerySequence Empty = new XmlQuerySequence(); @@ -489,7 +489,7 @@ public void AddClone(XPathItem item) /// A sequence of Xml nodes that dynamically expands and allows random access to items. /// [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class XmlQueryNodeSequence : XmlQuerySequence, IList + public sealed class XmlQueryNodeSequence : XmlQuerySequence, IList, IReadOnlyList { public static new readonly XmlQueryNodeSequence Empty = new XmlQueryNodeSequence(); @@ -731,6 +731,19 @@ XPathItem IList.this[int index] set { throw new NotSupportedException(); } } + /// + /// Return item at the specified index. + /// + XPathItem IReadOnlyList.this[int index] + { + get + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count); + + return base[index]; + } + } + /// /// Returns the index of the specified value in the sequence. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs index 24f8653053af76..021a4ac75b3c72 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs @@ -35,7 +35,12 @@ public JsonObject(IEnumerable> properties, JsonN { bool isCaseInsensitive = IsCaseInsensitive(options); - JsonPropertyDictionary dictionary = properties is ICollection> propertiesCollection + JsonPropertyDictionary dictionary = +#if NET9_0_OR_GREATER // ICollection : IReadOnlyCollection on .NET 9+ + properties is IReadOnlyCollection> propertiesCollection +#else + properties is ICollection> propertiesCollection +#endif ? new(isCaseInsensitive, propertiesCollection.Count) : new(isCaseInsensitive);