From 90dedf011b6e2226332d62b7791d455aa18db1d5 Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Wed, 3 Nov 2021 17:41:59 +0100 Subject: [PATCH 01/10] added AsReadOnly extension methods for IDictionary and IList (not compiling) --- .../src/MatchingRefApiCompatBaseline.txt | 3 ++- .../Generic/CollectionExtensions.cs | 11 ++++++++++ .../Generic/CollectionExtensionsTests.cs | 21 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Collections/src/MatchingRefApiCompatBaseline.txt b/src/libraries/System.Collections/src/MatchingRefApiCompatBaseline.txt index a8dcfef40cbf89..84fd49cd91f56b 100644 --- a/src/libraries/System.Collections/src/MatchingRefApiCompatBaseline.txt +++ b/src/libraries/System.Collections/src/MatchingRefApiCompatBaseline.txt @@ -5,4 +5,5 @@ TypesMustExist : Type 'System.Collections.Generic.SortedDictionary TypesMustExist : Type 'System.Collections.Generic.SortedList.KeyList' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.Collections.Generic.SortedList.ValueList' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.Collections.Generic.TreeSet' does not exist in the reference but it does exist in the implementation. -Total Issues: 6 +MembersMustExist : Member 'public System.Collections.ObjectModel.ReadOnlyCollection System.Collections.Generic.CollectionExtensions.AsReadOnly(System.Collections.Generic.IList)' does not exist in the reference but it does exist in the implementation. +Total Issues: 7 diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs b/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs index 7a5bce9b0a1d4e..46d0eb635da65b 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs @@ -1,6 +1,7 @@ // 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.ObjectModel; using System.Diagnostics.CodeAnalysis; namespace System.Collections.Generic @@ -55,5 +56,15 @@ public static bool Remove(this IDictionary dictionar value = default; return false; } + + public static ReadOnlyCollection AsReadOnly(this IList list) + { + return new ReadOnlyCollection(list); + } + + public static ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) + { + return new ReadOnlyDictionary(dictionary); + } } } diff --git a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs index 153a3111907685..ad5a1f99e70a20 100644 --- a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs +++ b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Collections.ObjectModel; using Xunit; namespace System.Collections.Tests @@ -104,5 +105,25 @@ public void Remove_KeyDoesntExistInIDictionary_ReturnsFalse() Assert.False(dictionary.Remove("key", out var value)); Assert.Equal(default(string), value); } + + [Fact] + public void AsReadOnly_TurnsIListIntoReadOnlyCollection() + { + IList list = new List { "A", "B" }; + ReadOnlyCollection readOnlyCollection = list.AsReadOnly(); + Assert.NotNull(readOnlyCollection); + CollectionAsserts.Equal(list, readOnlyCollection); + } + + [Fact] + public void AsReadOnly_TurnsIDictionaryIntoReadOnlyCollection() + { + IDictionary dictionary = new Dictionary { ["key1"] = "value1", ["key2"] = "value2" }; + ReadOnlyDictionary readOnlyDictionary = dicticonary.AsReadOnly(); + Assert.NotNull(readOnlyDictionary); + Assert.Equal(dictionary["key1"], readOnlyDictionary["key1"]); + Assert.Equal(dictionary["key2"], readOnlyDictionary["key2"]); + Assert.Equal(dictionary.Count, readOnlyDictionary.Count); + } } } From 4520fd0fcbf967c23411d07fc3e685aee36ce31f Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Wed, 3 Nov 2021 18:46:21 +0100 Subject: [PATCH 02/10] added AsReadOnly methods to ref assembly --- src/libraries/System.Collections/ref/System.Collections.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Collections/ref/System.Collections.cs b/src/libraries/System.Collections/ref/System.Collections.cs index 028b1637eb4825..efe51b97fa6e51 100644 --- a/src/libraries/System.Collections/ref/System.Collections.cs +++ b/src/libraries/System.Collections/ref/System.Collections.cs @@ -47,6 +47,8 @@ public static partial class CollectionExtensions public static TValue GetValueOrDefault(this System.Collections.Generic.IReadOnlyDictionary dictionary, TKey key, TValue defaultValue) { throw null; } public static bool Remove(this System.Collections.Generic.IDictionary dictionary, TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; } public static bool TryAdd(this System.Collections.Generic.IDictionary dictionary, TKey key, TValue value) { throw null; } + public static System.Collections.ObjectModel.ReadOnlyCollection AsReadOnly(this IList list) { throw null; } + public static System.Collections.ObjectModel.ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) { throw null; } } public abstract partial class Comparer : System.Collections.Generic.IComparer, System.Collections.IComparer { From 6c20e632fba5c58f49199f8c6906c3467bddb6db Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Wed, 3 Nov 2021 18:57:25 +0100 Subject: [PATCH 03/10] revert changes in MatchingRefApiCompatBaseline.txt --- .../System.Collections/src/MatchingRefApiCompatBaseline.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libraries/System.Collections/src/MatchingRefApiCompatBaseline.txt b/src/libraries/System.Collections/src/MatchingRefApiCompatBaseline.txt index 84fd49cd91f56b..a8dcfef40cbf89 100644 --- a/src/libraries/System.Collections/src/MatchingRefApiCompatBaseline.txt +++ b/src/libraries/System.Collections/src/MatchingRefApiCompatBaseline.txt @@ -5,5 +5,4 @@ TypesMustExist : Type 'System.Collections.Generic.SortedDictionary TypesMustExist : Type 'System.Collections.Generic.SortedList.KeyList' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.Collections.Generic.SortedList.ValueList' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.Collections.Generic.TreeSet' does not exist in the reference but it does exist in the implementation. -MembersMustExist : Member 'public System.Collections.ObjectModel.ReadOnlyCollection System.Collections.Generic.CollectionExtensions.AsReadOnly(System.Collections.Generic.IList)' does not exist in the reference but it does exist in the implementation. -Total Issues: 7 +Total Issues: 6 From 2d081c7e525e2439d08cfa3cb1a00135b62f350a Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Wed, 3 Nov 2021 18:58:50 +0100 Subject: [PATCH 04/10] fixed spelling --- .../tests/Generic/CollectionExtensionsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs index ad5a1f99e70a20..50ebe3470351e3 100644 --- a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs +++ b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs @@ -116,7 +116,7 @@ public void AsReadOnly_TurnsIListIntoReadOnlyCollection() } [Fact] - public void AsReadOnly_TurnsIDictionaryIntoReadOnlyCollection() + public void AsReadOnly_TurnsIDictionaryIntoReadOnlyDictionary() { IDictionary dictionary = new Dictionary { ["key1"] = "value1", ["key2"] = "value2" }; ReadOnlyDictionary readOnlyDictionary = dicticonary.AsReadOnly(); From a3de0cd48e72840aae294f3e4f77e1d8a75af38a Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Wed, 3 Nov 2021 20:35:30 +0100 Subject: [PATCH 05/10] added notnull constraint for TKey on AsReadOnly on IDictionary, fixed typo --- src/libraries/System.Collections/ref/System.Collections.cs | 2 +- .../src/System/Collections/Generic/CollectionExtensions.cs | 2 +- .../tests/Generic/CollectionExtensionsTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections/ref/System.Collections.cs b/src/libraries/System.Collections/ref/System.Collections.cs index efe51b97fa6e51..7a15ca70fb3e51 100644 --- a/src/libraries/System.Collections/ref/System.Collections.cs +++ b/src/libraries/System.Collections/ref/System.Collections.cs @@ -48,7 +48,7 @@ public static partial class CollectionExtensions public static bool Remove(this System.Collections.Generic.IDictionary dictionary, TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; } public static bool TryAdd(this System.Collections.Generic.IDictionary dictionary, TKey key, TValue value) { throw null; } public static System.Collections.ObjectModel.ReadOnlyCollection AsReadOnly(this IList list) { throw null; } - public static System.Collections.ObjectModel.ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) { throw null; } + public static System.Collections.ObjectModel.ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) where TKey : notnull { throw null; } } public abstract partial class Comparer : System.Collections.Generic.IComparer, System.Collections.IComparer { diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs b/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs index 46d0eb635da65b..5baf3880e3a7ac 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs @@ -62,7 +62,7 @@ public static ReadOnlyCollection AsReadOnly(this IList list) return new ReadOnlyCollection(list); } - public static ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) + public static ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) where TKey : notnull { return new ReadOnlyDictionary(dictionary); } diff --git a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs index 50ebe3470351e3..3d1025c193fd8f 100644 --- a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs +++ b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs @@ -119,7 +119,7 @@ public void AsReadOnly_TurnsIListIntoReadOnlyCollection() public void AsReadOnly_TurnsIDictionaryIntoReadOnlyDictionary() { IDictionary dictionary = new Dictionary { ["key1"] = "value1", ["key2"] = "value2" }; - ReadOnlyDictionary readOnlyDictionary = dicticonary.AsReadOnly(); + ReadOnlyDictionary readOnlyDictionary = dictionary.AsReadOnly(); Assert.NotNull(readOnlyDictionary); Assert.Equal(dictionary["key1"], readOnlyDictionary["key1"]); Assert.Equal(dictionary["key2"], readOnlyDictionary["key2"]); From 2114f8c4896e9b51f2d0dfdf3ee9224aaf562981 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 4 Nov 2021 11:44:05 +0000 Subject: [PATCH 06/10] Move ReadOnlyDictionary from System.ObjectModel to System.Private.CoreLib --- .../ref/System.ObjectModel.cs | 71 ------------------ .../src/System.ObjectModel.csproj | 1 - .../System/Collections/CollectionHelpers.cs | 35 --------- .../System/Collections/Generic/DebugView.cs | 21 ------ .../System.Private.CoreLib.Shared.projitems | 2 + .../ObjectModel/CollectionHelpers.cs | 73 +++++++++++++++++++ .../ObjectModel/ReadOnlyDictionary.cs | 6 +- .../System.Runtime/ref/System.Runtime.cs | 71 ++++++++++++++++++ .../ApiCompatBaseline.PreviousNetCoreApp.txt | 4 +- ...iCompatBaseline.netcoreapp.netstandard.txt | 3 +- 10 files changed, 154 insertions(+), 133 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/CollectionHelpers.cs rename src/libraries/{System.ObjectModel => System.Private.CoreLib}/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs (98%) diff --git a/src/libraries/System.ObjectModel/ref/System.ObjectModel.cs b/src/libraries/System.ObjectModel/ref/System.ObjectModel.cs index 744576e587d067..21cd3dc855f118 100644 --- a/src/libraries/System.ObjectModel/ref/System.ObjectModel.cs +++ b/src/libraries/System.ObjectModel/ref/System.ObjectModel.cs @@ -43,77 +43,6 @@ protected virtual void OnPropertyChanged(System.ComponentModel.PropertyChangedEv protected override void RemoveItem(int index) { } protected override void SetItem(int index, T item) { } } - public partial class ReadOnlyDictionary : System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.Generic.IReadOnlyCollection>, System.Collections.Generic.IReadOnlyDictionary, System.Collections.ICollection, System.Collections.IDictionary, System.Collections.IEnumerable where TKey : notnull - { - public ReadOnlyDictionary(System.Collections.Generic.IDictionary dictionary) { } - public int Count { get { throw null; } } - protected System.Collections.Generic.IDictionary Dictionary { get { throw null; } } - public TValue this[TKey key] { get { throw null; } } - public System.Collections.ObjectModel.ReadOnlyDictionary.KeyCollection Keys { get { throw null; } } - bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } - TValue System.Collections.Generic.IDictionary.this[TKey key] { get { throw null; } set { } } - System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Keys { get { throw null; } } - System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Values { get { throw null; } } - System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } } - System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } } - bool System.Collections.ICollection.IsSynchronized { get { throw null; } } - object System.Collections.ICollection.SyncRoot { get { throw null; } } - bool System.Collections.IDictionary.IsFixedSize { get { throw null; } } - bool System.Collections.IDictionary.IsReadOnly { get { throw null; } } - object? System.Collections.IDictionary.this[object key] { get { throw null; } set { } } - System.Collections.ICollection System.Collections.IDictionary.Keys { get { throw null; } } - System.Collections.ICollection System.Collections.IDictionary.Values { get { throw null; } } - public System.Collections.ObjectModel.ReadOnlyDictionary.ValueCollection Values { get { throw null; } } - public bool ContainsKey(TKey key) { throw null; } - public System.Collections.Generic.IEnumerator> GetEnumerator() { throw null; } - void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) { } - void System.Collections.Generic.ICollection>.Clear() { } - bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) { throw null; } - void System.Collections.Generic.ICollection>.CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { } - bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair item) { throw null; } - void System.Collections.Generic.IDictionary.Add(TKey key, TValue value) { } - bool System.Collections.Generic.IDictionary.Remove(TKey key) { throw null; } - void System.Collections.ICollection.CopyTo(System.Array array, int index) { } - void System.Collections.IDictionary.Add(object key, object? value) { } - void System.Collections.IDictionary.Clear() { } - bool System.Collections.IDictionary.Contains(object key) { throw null; } - System.Collections.IDictionaryEnumerator System.Collections.IDictionary.GetEnumerator() { throw null; } - void System.Collections.IDictionary.Remove(object key) { } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - public bool TryGetValue(TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; } - public sealed partial class KeyCollection : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.ICollection, System.Collections.IEnumerable - { - internal KeyCollection() { } - public int Count { get { throw null; } } - bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } - bool System.Collections.ICollection.IsSynchronized { get { throw null; } } - object System.Collections.ICollection.SyncRoot { get { throw null; } } - public void CopyTo(TKey[] array, int arrayIndex) { } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - void System.Collections.Generic.ICollection.Add(TKey item) { } - void System.Collections.Generic.ICollection.Clear() { } - bool System.Collections.Generic.ICollection.Contains(TKey item) { throw null; } - bool System.Collections.Generic.ICollection.Remove(TKey item) { throw null; } - void System.Collections.ICollection.CopyTo(System.Array array, int index) { } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - } - public sealed partial class ValueCollection : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.ICollection, System.Collections.IEnumerable - { - internal ValueCollection() { } - public int Count { get { throw null; } } - bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } - bool System.Collections.ICollection.IsSynchronized { get { throw null; } } - object System.Collections.ICollection.SyncRoot { get { throw null; } } - public void CopyTo(TValue[] array, int arrayIndex) { } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - void System.Collections.Generic.ICollection.Add(TValue item) { } - void System.Collections.Generic.ICollection.Clear() { } - bool System.Collections.Generic.ICollection.Contains(TValue item) { throw null; } - bool System.Collections.Generic.ICollection.Remove(TValue item) { throw null; } - void System.Collections.ICollection.CopyTo(System.Array array, int index) { } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - } - } public partial class ReadOnlyObservableCollection : System.Collections.ObjectModel.ReadOnlyCollection, System.Collections.Specialized.INotifyCollectionChanged, System.ComponentModel.INotifyPropertyChanged { public ReadOnlyObservableCollection(System.Collections.ObjectModel.ObservableCollection list) : base (default(System.Collections.Generic.IList)) { } diff --git a/src/libraries/System.ObjectModel/src/System.ObjectModel.csproj b/src/libraries/System.ObjectModel/src/System.ObjectModel.csproj index 66c53d0dfb08b4..61ba9c2b81c6a1 100644 --- a/src/libraries/System.ObjectModel/src/System.ObjectModel.csproj +++ b/src/libraries/System.ObjectModel/src/System.ObjectModel.csproj @@ -12,7 +12,6 @@ - diff --git a/src/libraries/System.ObjectModel/src/System/Collections/CollectionHelpers.cs b/src/libraries/System.ObjectModel/src/System/Collections/CollectionHelpers.cs index 86058208873282..bff227728ecff7 100644 --- a/src/libraries/System.ObjectModel/src/System/Collections/CollectionHelpers.cs +++ b/src/libraries/System.ObjectModel/src/System/Collections/CollectionHelpers.cs @@ -34,40 +34,5 @@ internal static void ValidateCopyToArguments(int sourceCount, Array array, int i throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); } } - - internal static void CopyTo(ICollection collection, Array array, int index) - { - ValidateCopyToArguments(collection.Count, array, index); - - if (collection is ICollection nonGenericCollection) - { - // Easy out if the ICollection implements the non-generic ICollection - nonGenericCollection.CopyTo(array, index); - } - else if (array is T[] items) - { - collection.CopyTo(items, index); - } - else - { - // We can't cast array of value type to object[], so we don't support widening of primitive types here. - if (array is not object?[] objects) - { - throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array)); - } - - try - { - foreach (T item in collection) - { - objects[index++] = item; - } - } - catch (ArrayTypeMismatchException) - { - throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array)); - } - } - } } } diff --git a/src/libraries/System.ObjectModel/src/System/Collections/Generic/DebugView.cs b/src/libraries/System.ObjectModel/src/System/Collections/Generic/DebugView.cs index 2233d2518fe36c..3ab6a770ca772b 100644 --- a/src/libraries/System.ObjectModel/src/System/Collections/Generic/DebugView.cs +++ b/src/libraries/System.ObjectModel/src/System/Collections/Generic/DebugView.cs @@ -25,25 +25,4 @@ public T[] Items } } } - - internal sealed class DictionaryDebugView where K: notnull - { - private readonly IDictionary _dict; - - public DictionaryDebugView(IDictionary dictionary) - { - _dict = dictionary ?? throw new ArgumentNullException(nameof(dictionary)); - } - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePair[] Items - { - get - { - KeyValuePair[] items = new KeyValuePair[_dict.Count]; - _dict.CopyTo(items, 0); - return items; - } - } - } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 01fd0776225fa6..9723fc8954385a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -228,7 +228,9 @@ + + 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 new file mode 100644 index 00000000000000..89af10ba7109f4 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/CollectionHelpers.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.ObjectModel +{ + internal static class CollectionHelpers + { + internal static void ValidateCopyToArguments(int sourceCount, Array array, int index) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (array.Rank != 1) + { + throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array)); + } + + if (array.GetLowerBound(0) != 0) + { + throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array)); + } + + if (index < 0 || index > array.Length) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < sourceCount) + { + throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); + } + } + + internal static void CopyTo(ICollection collection, Array array, int index) + { + ValidateCopyToArguments(collection.Count, array, index); + + if (collection is ICollection nonGenericCollection) + { + // Easy out if the ICollection implements the non-generic ICollection + nonGenericCollection.CopyTo(array, index); + } + else if (array is T[] items) + { + collection.CopyTo(items, index); + } + else + { + // We can't cast array of value type to object[], so we don't support widening of primitive types here. + if (array is not object?[] objects) + { + throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array)); + } + + try + { + foreach (T item in collection) + { + objects[index++] = item; + } + } + catch (ArrayTypeMismatchException) + { + throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array)); + } + } + } + } +} diff --git a/src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs similarity index 98% rename from src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs rename to src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs index 428b0663d263ce..57c6b4604dc1d8 100644 --- a/src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs @@ -8,7 +8,7 @@ namespace System.Collections.ObjectModel { [Serializable] - [DebuggerTypeProxy(typeof(DictionaryDebugView<,>))] + [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))] [DebuggerDisplay("Count = {Count}")] [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public class ReadOnlyDictionary : IDictionary, IDictionary, IReadOnlyDictionary where TKey : notnull @@ -248,7 +248,7 @@ public DictionaryEntry Entry IEnumerable IReadOnlyDictionary.Values => Values; - [DebuggerTypeProxy(typeof(CollectionDebugView<>))] + [DebuggerTypeProxy(typeof(ICollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] public sealed class KeyCollection : ICollection, ICollection, IReadOnlyCollection { @@ -302,7 +302,7 @@ void ICollection.CopyTo(Array array, int index) object ICollection.SyncRoot => (_collection is ICollection coll) ? coll.SyncRoot : this; } - [DebuggerTypeProxy(typeof(CollectionDebugView<>))] + [DebuggerTypeProxy(typeof(ICollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] public sealed class ValueCollection : ICollection, ICollection, IReadOnlyCollection { diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 7157e359ccd540..04b63cbf2a8070 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -8657,6 +8657,77 @@ void System.Collections.IList.Insert(int index, object? value) { } void System.Collections.IList.Remove(object? value) { } void System.Collections.IList.RemoveAt(int index) { } } + public partial class ReadOnlyDictionary : System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.Generic.IReadOnlyCollection>, System.Collections.Generic.IReadOnlyDictionary, System.Collections.ICollection, System.Collections.IDictionary, System.Collections.IEnumerable where TKey : notnull + { + public ReadOnlyDictionary(System.Collections.Generic.IDictionary dictionary) { } + public int Count { get { throw null; } } + protected System.Collections.Generic.IDictionary Dictionary { get { throw null; } } + public TValue this[TKey key] { get { throw null; } } + public System.Collections.ObjectModel.ReadOnlyDictionary.KeyCollection Keys { get { throw null; } } + bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } + TValue System.Collections.Generic.IDictionary.this[TKey key] { get { throw null; } set { } } + System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Keys { get { throw null; } } + System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Values { get { throw null; } } + System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } } + System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } } + bool System.Collections.ICollection.IsSynchronized { get { throw null; } } + object System.Collections.ICollection.SyncRoot { get { throw null; } } + bool System.Collections.IDictionary.IsFixedSize { get { throw null; } } + bool System.Collections.IDictionary.IsReadOnly { get { throw null; } } + object? System.Collections.IDictionary.this[object key] { get { throw null; } set { } } + System.Collections.ICollection System.Collections.IDictionary.Keys { get { throw null; } } + System.Collections.ICollection System.Collections.IDictionary.Values { get { throw null; } } + public System.Collections.ObjectModel.ReadOnlyDictionary.ValueCollection Values { get { throw null; } } + public bool ContainsKey(TKey key) { throw null; } + public System.Collections.Generic.IEnumerator> GetEnumerator() { throw null; } + void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) { } + void System.Collections.Generic.ICollection>.Clear() { } + bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) { throw null; } + void System.Collections.Generic.ICollection>.CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { } + bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair item) { throw null; } + void System.Collections.Generic.IDictionary.Add(TKey key, TValue value) { } + bool System.Collections.Generic.IDictionary.Remove(TKey key) { throw null; } + void System.Collections.ICollection.CopyTo(System.Array array, int index) { } + void System.Collections.IDictionary.Add(object key, object? value) { } + void System.Collections.IDictionary.Clear() { } + bool System.Collections.IDictionary.Contains(object key) { throw null; } + System.Collections.IDictionaryEnumerator System.Collections.IDictionary.GetEnumerator() { throw null; } + void System.Collections.IDictionary.Remove(object key) { } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + public bool TryGetValue(TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; } + public sealed partial class KeyCollection : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.ICollection, System.Collections.IEnumerable + { + internal KeyCollection() { } + public int Count { get { throw null; } } + bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } + bool System.Collections.ICollection.IsSynchronized { get { throw null; } } + object System.Collections.ICollection.SyncRoot { get { throw null; } } + public void CopyTo(TKey[] array, int arrayIndex) { } + public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } + void System.Collections.Generic.ICollection.Add(TKey item) { } + void System.Collections.Generic.ICollection.Clear() { } + bool System.Collections.Generic.ICollection.Contains(TKey item) { throw null; } + bool System.Collections.Generic.ICollection.Remove(TKey item) { throw null; } + void System.Collections.ICollection.CopyTo(System.Array array, int index) { } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } + public sealed partial class ValueCollection : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.ICollection, System.Collections.IEnumerable + { + internal ValueCollection() { } + public int Count { get { throw null; } } + bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } + bool System.Collections.ICollection.IsSynchronized { get { throw null; } } + object System.Collections.ICollection.SyncRoot { get { throw null; } } + public void CopyTo(TValue[] array, int arrayIndex) { } + public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } + void System.Collections.Generic.ICollection.Add(TValue item) { } + void System.Collections.Generic.ICollection.Clear() { } + bool System.Collections.Generic.ICollection.Contains(TValue item) { throw null; } + bool System.Collections.Generic.ICollection.Remove(TValue item) { throw null; } + void System.Collections.ICollection.CopyTo(System.Array array, int index) { } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } + } } namespace System.ComponentModel { diff --git a/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt b/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt index 32beb555e41775..0fdeb001ef9d87 100644 --- a/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt +++ b/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt @@ -226,6 +226,8 @@ CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAtt CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAttribute' exists on 'System.Numerics.Vector.ToString(System.String, System.IFormatProvider)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAttribute' exists on 'System.Numerics.Vector.TryCopyTo(System.Span)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAttribute' exists on 'System.Numerics.Vector.TryCopyTo(System.Span)' in the contract but not the implementation. +Compat issues with assembly System.ObjectModel: +TypesMustExist : Type 'System.Collections.ObjectModel.ReadOnlyDictionary' does not exist in the implementation but it does exist in the contract. Compat issues with assembly System.Reflection.Emit: CannotChangeAttribute : Attribute 'System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute' on 'System.Reflection.Emit.EnumBuilder.GetMember(System.String, System.Reflection.MemberTypes, System.Reflection.BindingFlags)' changed from '[DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes.All)]' in the contract to '[DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.PublicProperties)]' in the implementation. CannotChangeAttribute : Attribute 'System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute' on 'System.Reflection.Emit.EnumBuilder.GetMembers(System.Reflection.BindingFlags)' changed from '[DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes.All)]' in the contract to '[DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.PublicProperties)]' in the implementation. @@ -303,4 +305,4 @@ CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.DynamicallyAc CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Text.Json.Serialization.JsonConverterAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple=false)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple=false)]' in the implementation. Compat issues with assembly System.Threading.Tasks.Extensions: CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Runtime.CompilerServices.AsyncMethodBuilderAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Struct, Inherited=false, AllowMultiple=false)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Struct, Inherited=false, AllowMultiple=false)]' in the implementation. -Total Issues: 286 +Total Issues: 287 diff --git a/src/libraries/shims/ApiCompatBaseline.netcoreapp.netstandard.txt b/src/libraries/shims/ApiCompatBaseline.netcoreapp.netstandard.txt index 1d4a30c30f10ea..40ec257771a8bc 100644 --- a/src/libraries/shims/ApiCompatBaseline.netcoreapp.netstandard.txt +++ b/src/libraries/shims/ApiCompatBaseline.netcoreapp.netstandard.txt @@ -630,4 +630,5 @@ CannotSealType : Type 'System.Xml.Schema.XmlSchemaGroupBase' is effectively (has MembersMustExist : Member 'protected void System.Xml.Schema.XmlSchemaGroupBase..ctor()' does not exist in the implementation but it does exist in the contract. CannotMakeMemberNonVirtual : Member 'public System.Xml.Schema.XmlSchemaObjectCollection System.Xml.Schema.XmlSchemaGroupBase.Items' is non-virtual in the implementation but is virtual in the contract. CannotMakeMemberNonVirtual : Member 'public System.Xml.Schema.XmlSchemaObjectCollection System.Xml.Schema.XmlSchemaGroupBase.Items.get()' is non-virtual in the implementation but is virtual in the contract. -Total Issues: 617 +TypesMustExist : Type 'System.Collections.ObjectModel.ReadOnlyDictionary' does not exist in the implementation but it does exist in the contract. +Total Issues: 618 From 71ee4d4f5b486e764601b8d411e09b0fa75db212 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 11 Nov 2021 18:23:16 +0000 Subject: [PATCH 07/10] Add TypeForwards --- .../System.ObjectModel/ref/System.ObjectModel.Forwards.cs | 4 ++++ .../System.ObjectModel/ref/System.ObjectModel.csproj | 1 + .../System.ObjectModel/src/System.ObjectModel.Forwards.cs | 4 ++++ .../System.ObjectModel/src/System.ObjectModel.csproj | 1 + 4 files changed, 10 insertions(+) create mode 100644 src/libraries/System.ObjectModel/ref/System.ObjectModel.Forwards.cs create mode 100644 src/libraries/System.ObjectModel/src/System.ObjectModel.Forwards.cs diff --git a/src/libraries/System.ObjectModel/ref/System.ObjectModel.Forwards.cs b/src/libraries/System.ObjectModel/ref/System.ObjectModel.Forwards.cs new file mode 100644 index 00000000000000..df45eb9a68a6ee --- /dev/null +++ b/src/libraries/System.ObjectModel/ref/System.ObjectModel.Forwards.cs @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.ObjectModel.ReadOnlyDictionary<,>))] diff --git a/src/libraries/System.ObjectModel/ref/System.ObjectModel.csproj b/src/libraries/System.ObjectModel/ref/System.ObjectModel.csproj index 9b427746cd8afa..623c85ec556705 100644 --- a/src/libraries/System.ObjectModel/ref/System.ObjectModel.csproj +++ b/src/libraries/System.ObjectModel/ref/System.ObjectModel.csproj @@ -5,6 +5,7 @@ + diff --git a/src/libraries/System.ObjectModel/src/System.ObjectModel.Forwards.cs b/src/libraries/System.ObjectModel/src/System.ObjectModel.Forwards.cs new file mode 100644 index 00000000000000..deb07efe4f82cb --- /dev/null +++ b/src/libraries/System.ObjectModel/src/System.ObjectModel.Forwards.cs @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.ObjectModel.ReadOnlyDictionary<,>))] diff --git a/src/libraries/System.ObjectModel/src/System.ObjectModel.csproj b/src/libraries/System.ObjectModel/src/System.ObjectModel.csproj index 61ba9c2b81c6a1..ecf4f2a77fe9e2 100644 --- a/src/libraries/System.ObjectModel/src/System.ObjectModel.csproj +++ b/src/libraries/System.ObjectModel/src/System.ObjectModel.csproj @@ -5,6 +5,7 @@ enable + From a72b3f9615c425da5173c9fb08c078b906c36b7f Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Mon, 15 Nov 2021 17:24:30 +0100 Subject: [PATCH 08/10] Added XML-Doc comments --- .../Collections/Generic/CollectionExtensions.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs b/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs index 5baf3880e3a7ac..4f5c545603dbbe 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs @@ -57,11 +57,28 @@ public static bool Remove(this IDictionary dictionar return false; } + /// + /// Returns a read-only wrapper + /// for the specified list. + /// + /// The type of elements in the collection. + /// The list to wrap. + /// An object that acts as a read-only wrapper around the current . + /// is null. public static ReadOnlyCollection AsReadOnly(this IList list) { return new ReadOnlyCollection(list); } + /// + /// Returns a read-only wrapper + /// for the current dictionary. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// The dictionary to wrap. + /// An object that acts as a read-only wrapper around the current . + /// is null. public static ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) where TKey : notnull { return new ReadOnlyDictionary(dictionary); From 090374ebce713d174526f82965bc43c33f8e29f3 Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Mon, 15 Nov 2021 18:54:51 +0100 Subject: [PATCH 09/10] added tests for null arguments to AsReadOnly extensions --- .../tests/Generic/CollectionExtensionsTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs index 3d1025c193fd8f..1830954254488d 100644 --- a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs +++ b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs @@ -125,5 +125,19 @@ public void AsReadOnly_TurnsIDictionaryIntoReadOnlyDictionary() Assert.Equal(dictionary["key2"], readOnlyDictionary["key2"]); Assert.Equal(dictionary.Count, readOnlyDictionary.Count); } + + [Fact] + public void AsReadOnly_NullIList_ThrowsArgumentNullException() + { + IList list = null; + Assert.Throws(() => list.AsReadOnly()); + } + + [Fact] + public void AsReadOnly_NullIDictionary_ThrowsArgumentNullException() + { + IDictionary dictionary = null; + Assert.Throws(() => dictionary.AsReadOnly()); + } } } From d88abd776502010a07f5f40ac0a997d1446e2820 Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Mon, 15 Nov 2021 19:00:08 +0100 Subject: [PATCH 10/10] added parameter names to null argument tests --- .../tests/Generic/CollectionExtensionsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs index 1830954254488d..213d9d6faca996 100644 --- a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs +++ b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs @@ -130,14 +130,14 @@ public void AsReadOnly_TurnsIDictionaryIntoReadOnlyDictionary() public void AsReadOnly_NullIList_ThrowsArgumentNullException() { IList list = null; - Assert.Throws(() => list.AsReadOnly()); + Assert.Throws("list", () => list.AsReadOnly()); } [Fact] public void AsReadOnly_NullIDictionary_ThrowsArgumentNullException() { IDictionary dictionary = null; - Assert.Throws(() => dictionary.AsReadOnly()); + Assert.Throws("dictionary", () => dictionary.AsReadOnly()); } } }