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 ce7f345b0ea9c..dcc9de3e4ad65 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 4afe1f7503e75..02e5e0cc07cb2 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 66b365bad872b..dc9c7d26898b7 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 5047b76433738..072e5ac34d605 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 2f7406d0d7bf4..fd0beccb28a77 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 288d01c185dbf..906ee3fd84fc2 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 55ef371ea0422..f0885d3d3e7d4 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 28a3783aa8829..d6d6e4adccdeb 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 01adb0af01e45..cd6fb6fa171ad 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 6b8819f8c3cbc..9b2511e0563c5 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 c89d6797581e9..45f673f73486a 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 97b87f9eba999..824a152a7764c 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 74ff81c2fec83..22532cdb18d13 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 6e19e4ab384e4..47be36572acad 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 007ee1659c3f5..e17e409e25e3f 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 bf65a34b06efa..ac9225303b426 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 d1ec26de879af..168ca818c9611 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 16bf60e686145..2568721f1ce16 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 9c7328cc3ff16..78e548e680669 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 532f6e950d6b0..4abe58873916a 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 bfa9fcf65469e..2eff1c59b12ec 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 9b18d4a125ed2..f20e3c5cfee21 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 3b86548d20a7e..6467164d6becb 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 3c7e5b6ad25c3..23fc7a5c2e65a 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 24f8653053af7..021a4ac75b3c7 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);