Add ReadOnly{Observable}Collection/Dictionary.Empty (#76764)
authorStephen Toub <stoub@microsoft.com>
Mon, 24 Oct 2022 11:57:45 +0000 (07:57 -0400)
committerGitHub <noreply@github.com>
Mon, 24 Oct 2022 11:57:45 +0000 (07:57 -0400)
* Add ReadOnly{Observable}Collection/Dictionary.Empty

* Revert XamlLoadPermission.cs change to fix downlevel build

37 files changed:
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/Helpers.cs
src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
src/libraries/System.ComponentModel.Composition/src/Microsoft/Internal/Collections/CollectionServices.cs
src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/CompositionException.cs
src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/Hosting/AggregateExportProvider.cs
src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/Hosting/CompositionContainer.cs
src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/Hosting/DirectoryCatalog.cs
src/libraries/System.Composition.AttributedModel/src/System/Composition/SharingBoundaryAttribute.cs
src/libraries/System.Linq.Expressions/src/System.Linq.Expressions.csproj
src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/CollectionExtensions.cs
src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/EmptyReadOnlyCollection.cs [deleted file]
src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/BlockExpression.cs
src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/HoistedLocals.cs
src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/InvocationExpression.cs
src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/LambdaExpression.cs
src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ListInitExpression.cs
src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs
src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/NewExpression.cs
src/libraries/System.Linq.Expressions/src/System/Runtime/CompilerServices/ReadOnlyCollectionBuilder.cs
src/libraries/System.Linq/tests/ToListTests.cs
src/libraries/System.ObjectModel/ref/System.ObjectModel.cs
src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ReadOnlyObservableCollection.cs
src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs
src/libraries/System.ObjectModel/tests/ReadOnlyObservableCollection/ReadOnlyObservableCollectionTests.cs
src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs
src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/ClassDataContract.cs
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContract.cs
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContractSerializer.cs
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/EnumDataContract.cs
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/DataContractJsonSerializer.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/CustomAttributes/CustomAttributeHelpers.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/CustomAttributes/Ecma/EcmaCustomAttributeHelpers.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/General/Helpers.cs
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/MethodBase/Ecma/EcmaMethodBody.cs
src/libraries/System.Runtime/ref/System.Runtime.cs
src/libraries/System.Runtime/tests/System/Collections/ObjectModel/ReadOnlyCollectionTests.cs

index 378d18d..62e6749 100644 (file)
@@ -83,7 +83,7 @@ namespace System.Reflection.Runtime.General
 
         public static ReadOnlyCollection<T> ToReadOnlyCollection<T>(this IEnumerable<T> enumeration)
         {
-            return new ReadOnlyCollection<T>(enumeration.ToArray());
+            return Array.AsReadOnly(enumeration.ToArray());
         }
 
         public static MethodInfo FilterAccessor(this MethodInfo accessor, bool nonPublic)
index e30e46a..8ad537c 100644 (file)
@@ -2068,8 +2068,7 @@ namespace System.Collections.Concurrent
 
                 if (count == 0)
                 {
-                    // TODO https://github.com/dotnet/runtime/issues/76028: Replace with ReadOnlyCollection<TKey>.Empty.
-                    return Array.Empty<TKey>().AsReadOnly();
+                    return ReadOnlyCollection<TKey>.Empty;
                 }
 
                 var keys = new TKey[count];
@@ -2110,8 +2109,7 @@ namespace System.Collections.Concurrent
 
                 if (count == 0)
                 {
-                    // TODO https://github.com/dotnet/runtime/pull/76097:  Replace with ReadOnlyCollection<TValue>.Empty.
-                    return Array.Empty<TValue>().AsReadOnly();
+                    return ReadOnlyCollection<TValue>.Empty;
                 }
 
                 var values = new TValue[count];
index 339c3ae..42dd929 100644 (file)
@@ -60,7 +60,7 @@ namespace Microsoft.Internal.Collections
         {
             ArgumentNullException.ThrowIfNull(source);
 
-            return new ReadOnlyCollection<T>(source.AsArray());
+            return Array.AsReadOnly(source.AsArray());
         }
 
         public static IEnumerable<T>? ConcatAllowingNull<T>(this IEnumerable<T>? source, IEnumerable<T>? second)
index 896c381..1a14098 100644 (file)
@@ -92,7 +92,7 @@ namespace System.ComponentModel.Composition
                     : base(message, innerException)
         {
             Requires.NullOrNotNullElements(errors, nameof(errors));
-            _errors = new ReadOnlyCollection<CompositionError>(errors == null ? Array.Empty<CompositionError>() : errors.ToArray<CompositionError>());
+            _errors = Array.AsReadOnly(errors == null ? Array.Empty<CompositionError>() : errors.ToArray<CompositionError>());
         }
 
         /// <summary>
index 98c0e4c..492683f 100644 (file)
@@ -62,7 +62,7 @@ namespace System.ComponentModel.Composition.Hosting
             }
 
             _providers = copiedProviders;
-            _readOnlyProviders = new ReadOnlyCollection<ExportProvider>(_providers);
+            _readOnlyProviders = Array.AsReadOnly(_providers);
         }
 
         /// <summary>
index 7f3b1b2..77dcec2 100644 (file)
@@ -223,7 +223,7 @@ namespace System.ComponentModel.Composition.Hosting
             _rootProvider.ExportsChanged += OnExportsChangedInternal;
             _rootProvider.ExportsChanging += OnExportsChangingInternal;
 
-            _providers = (providers != null) ? new ReadOnlyCollection<ExportProvider>((ExportProvider[])providers.Clone()) : EmptyProviders;
+            _providers = (providers != null) ? Array.AsReadOnly((ExportProvider[])providers.Clone()) : EmptyProviders;
         }
 
         internal CompositionOptions CompositionOptions
index 54dc5b1..0694fb8 100644 (file)
@@ -632,7 +632,7 @@ namespace System.ComponentModel.Composition.Hosting
                             _catalogCollection.Remove(catalogToRemove.Item2);
                         }
 
-                        _loadedFiles = afterFiles.ToReadOnlyCollection();
+                        _loadedFiles = Array.AsReadOnly(afterFiles);
 
                         // Lastly complete any changes added to the atomicComposition during the change event
                         atomicComposition.Complete();
@@ -756,7 +756,7 @@ namespace System.ComponentModel.Composition.Hosting
             _assemblyCatalogs = new Dictionary<string, AssemblyCatalog>();
             _catalogCollection = new ComposablePartCatalogCollection(null, null, null);
 
-            _loadedFiles = GetFiles().ToReadOnlyCollection();
+            _loadedFiles = Array.AsReadOnly(GetFiles());
 
             foreach (string file in _loadedFiles)
             {
index e3f9341..85dd545 100644 (file)
@@ -35,6 +35,6 @@ namespace System.Composition
         /// <summary>
         /// Boundaries implemented by the created ExportLifetimeContext{T}s.
         /// </summary>
-        public ReadOnlyCollection<string> SharingBoundaryNames => new ReadOnlyCollection<string>(_sharingBoundaryNames);
+        public ReadOnlyCollection<string> SharingBoundaryNames => Array.AsReadOnly(_sharingBoundaryNames);
     }
 }
index 3227a78..ce93213 100644 (file)
     <Compile Include="System\Runtime\CompilerServices\TrueReadOnlyCollection.cs" />
     <Compile Include="System\Dynamic\Utils\CachedReflectionInfo.cs" />
     <Compile Include="System\Dynamic\Utils\CollectionExtensions.cs" />
-    <Compile Include="System\Dynamic\Utils\EmptyReadOnlyCollection.cs" />
     <Compile Include="System\Dynamic\UpdateDelegates.Generated.cs">
       <AutoGen>True</AutoGen>
       <DesignTime>True</DesignTime>
index aa4793b..61b11a5 100644 (file)
@@ -57,25 +57,26 @@ namespace System.Dynamic.Utils
         /// </summary>
         public static ReadOnlyCollection<T> ToReadOnly<T>(this IEnumerable<T>? enumerable)
         {
-            if (enumerable == null)
+            if (enumerable != null && enumerable != ReadOnlyCollection<T>.Empty)
             {
-                return EmptyReadOnlyCollection<T>.Instance;
-            }
+                if (enumerable is TrueReadOnlyCollection<T> troc)
+                {
+                    return troc;
+                }
 
-            if (enumerable is TrueReadOnlyCollection<T> troc)
-            {
-                return troc;
-            }
+                if (enumerable is ReadOnlyCollectionBuilder<T> builder)
+                {
+                    return builder.ToReadOnlyCollection();
+                }
 
-            if (enumerable is ReadOnlyCollectionBuilder<T> builder)
-            {
-                return builder.ToReadOnlyCollection();
+                T[] array = enumerable.ToArray();
+                if (array.Length != 0)
+                {
+                    return new TrueReadOnlyCollection<T>(array);
+                }
             }
 
-            T[] array = enumerable.ToArray();
-            return array.Length == 0 ?
-                EmptyReadOnlyCollection<T>.Instance :
-                new TrueReadOnlyCollection<T>(array);
+            return ReadOnlyCollection<T>.Empty;
         }
 
         // We could probably improve the hashing here
diff --git a/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/EmptyReadOnlyCollection.cs b/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/EmptyReadOnlyCollection.cs
deleted file mode 100644 (file)
index 3c99825..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Collections.ObjectModel;
-using System.Runtime.CompilerServices;
-
-namespace System.Dynamic.Utils
-{
-    internal static class EmptyReadOnlyCollection<T>
-    {
-        public static readonly ReadOnlyCollection<T> Instance = new TrueReadOnlyCollection<T>(Array.Empty<T>());
-    }
-}
index d997789..d78ab30 100644 (file)
@@ -135,7 +135,7 @@ namespace System.Linq.Expressions
 
         internal virtual ReadOnlyCollection<ParameterExpression> GetOrMakeVariables()
         {
-            return EmptyReadOnlyCollection<ParameterExpression>.Instance;
+            return ReadOnlyCollection<ParameterExpression>.Empty;
         }
 
         /// <summary>
@@ -913,7 +913,7 @@ namespace System.Linq.Expressions
         /// <returns>The created <see cref="BlockExpression"/>.</returns>
         public static BlockExpression Block(IEnumerable<Expression> expressions)
         {
-            return Block(EmptyReadOnlyCollection<ParameterExpression>.Instance, expressions);
+            return Block(ReadOnlyCollection<ParameterExpression>.Empty, expressions);
         }
 
         /// <summary>
@@ -936,7 +936,7 @@ namespace System.Linq.Expressions
         /// <returns>The created <see cref="BlockExpression"/>.</returns>
         public static BlockExpression Block(Type type, IEnumerable<Expression> expressions)
         {
-            return Block(type, EmptyReadOnlyCollection<ParameterExpression>.Instance, expressions);
+            return Block(type, ReadOnlyCollection<ParameterExpression>.Empty, expressions);
         }
 
         /// <summary>
@@ -1089,7 +1089,7 @@ namespace System.Linq.Expressions
         {
             return expressions.Count switch
             {
-                0 => BlockCore(typeof(void), EmptyReadOnlyCollection<ParameterExpression>.Instance, EmptyReadOnlyCollection<Expression>.Instance),
+                0 => BlockCore(typeof(void), ReadOnlyCollection<ParameterExpression>.Empty, ReadOnlyCollection<Expression>.Empty),
                 2 => new Block2(expressions[0], expressions[1]),
                 3 => new Block3(expressions[0], expressions[1], expressions[2]),
                 4 => new Block4(expressions[0], expressions[1], expressions[2], expressions[3]),
index afa13bb..0ee30b9 100644 (file)
@@ -64,16 +64,24 @@ namespace System.Linq.Expressions.Compiler
                 vars = vars.AddFirst(parent.SelfVariable);
             }
 
-            Dictionary<Expression, int> indexes = new Dictionary<Expression, int>(vars.Count);
-            for (int i = 0; i < vars.Count; i++)
+            if (vars.Count != 0)
             {
-                indexes.Add(vars[i], i);
+                Dictionary<Expression, int> indexes = new Dictionary<Expression, int>(vars.Count);
+                for (int i = 0; i < vars.Count; i++)
+                {
+                    indexes.Add(vars[i], i);
+                }
+
+                Indexes = new ReadOnlyDictionary<Expression, int>(indexes);
+            }
+            else
+            {
+                Indexes = ReadOnlyDictionary<Expression, int>.Empty;
             }
 
-            SelfVariable = Expression.Variable(typeof(object[]), name: null);
             Parent = parent;
             Variables = vars;
-            Indexes = new ReadOnlyDictionary<Expression, int>(indexes);
+            SelfVariable = Expression.Variable(typeof(object[]), name: null);
         }
 
         internal ParameterExpression? ParentVariable => Parent?.SelfVariable;
index a77ff63..681a1b2 100644 (file)
@@ -159,7 +159,7 @@ namespace System.Linq.Expressions
 
         internal override ReadOnlyCollection<Expression> GetOrMakeArguments()
         {
-            return EmptyReadOnlyCollection<Expression>.Instance;
+            return ReadOnlyCollection<Expression>.Empty;
         }
 
         public override Expression GetArgument(int index)
index aa4128d..d8621b0 100644 (file)
@@ -370,7 +370,7 @@ namespace System.Linq.Expressions
             throw Error.ArgumentOutOfRange(nameof(index));
         }
 
-        internal override ReadOnlyCollection<ParameterExpression> GetOrMakeParameters() => EmptyReadOnlyCollection<ParameterExpression>.Instance;
+        internal override ReadOnlyCollection<ParameterExpression> GetOrMakeParameters() => ReadOnlyCollection<ParameterExpression>.Empty;
 
         internal override Expression<TDelegate> Rewrite(Expression body, ParameterExpression[]? parameters)
         {
index a786f05..42252e3 100644 (file)
@@ -125,7 +125,7 @@ namespace System.Linq.Expressions
             ReadOnlyCollection<Expression> initializerlist = initializers.ToReadOnly();
             if (initializerlist.Count == 0)
             {
-                return new ListInitExpression(newExpression, EmptyReadOnlyCollection<ElementInit>.Instance);
+                return new ListInitExpression(newExpression, ReadOnlyCollection<ElementInit>.Empty);
             }
 
             MethodInfo? addMethod = FindMethod(newExpression.Type, "Add", null, new Expression[] { initializerlist[0] }, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
index 731c1e4..edd4adc 100644 (file)
@@ -242,7 +242,7 @@ namespace System.Linq.Expressions
 
         internal override ReadOnlyCollection<Expression> GetOrMakeArguments()
         {
-            return EmptyReadOnlyCollection<Expression>.Instance;
+            return ReadOnlyCollection<Expression>.Empty;
         }
 
         internal override bool SameArguments(ICollection<Expression>? arguments) =>
@@ -622,7 +622,7 @@ namespace System.Linq.Expressions
 
         internal override ReadOnlyCollection<Expression> GetOrMakeArguments()
         {
-            return EmptyReadOnlyCollection<Expression>.Instance;
+            return ReadOnlyCollection<Expression>.Empty;
         }
 
         internal override bool SameArguments(ICollection<Expression>? arguments) =>
index ec414a0..eecc829 100644 (file)
@@ -213,7 +213,7 @@ namespace System.Linq.Expressions
             {
                 throw Error.TypeMissingDefaultConstructor(type, nameof(type));
             }
-            return new NewValueTypeExpression(type, EmptyReadOnlyCollection<Expression>.Instance, null);
+            return new NewValueTypeExpression(type, ReadOnlyCollection<Expression>.Empty, null);
         }
 
         [RequiresUnreferencedCode(PropertyFromAccessorRequiresUnreferencedCode)]
index ae74480..3ca5ed0 100644 (file)
@@ -412,6 +412,11 @@ namespace System.Runtime.CompilerServices
         /// <returns>A new instance of <see cref="ReadOnlyCollection{T}"/>.</returns>
         public ReadOnlyCollection<T> ToReadOnlyCollection()
         {
+            if (_size == 0)
+            {
+                return ReadOnlyCollection<T>.Empty;
+            }
+
             // Can we use the stored array?
             T[] items;
             if (_size == _items.Length)
index 2994cd5..9d6b49a 100644 (file)
@@ -158,19 +158,16 @@ namespace System.Linq.Tests
             var sourceList = new ReadOnlyCollection<int>(sourceIntegers);
             var convertedList = new ReadOnlyCollection<string>(convertedStrings);
 
-            var emptyIntegersList = new ReadOnlyCollection<int>(Array.Empty<int>());
-            var emptyStringsList = new ReadOnlyCollection<string>(Array.Empty<string>());
-
             Assert.Equal(convertedList, sourceList.Select(i => i.ToString()).ToList());
 
             Assert.Equal(sourceList, sourceList.Where(i => true).ToList());
-            Assert.Equal(emptyIntegersList, sourceList.Where(i => false).ToList());
+            Assert.Equal(ReadOnlyCollection<int>.Empty, sourceList.Where(i => false).ToList());
 
             Assert.Equal(convertedList, sourceList.Where(i => true).Select(i => i.ToString()).ToList());
-            Assert.Equal(emptyStringsList, sourceList.Where(i => false).Select(i => i.ToString()).ToList());
+            Assert.Equal(ReadOnlyCollection<string>.Empty, sourceList.Where(i => false).Select(i => i.ToString()).ToList());
 
             Assert.Equal(convertedList, sourceList.Select(i => i.ToString()).Where(s => s != null).ToList());
-            Assert.Equal(emptyStringsList, sourceList.Select(i => i.ToString()).Where(s => s == null).ToList());
+            Assert.Equal(ReadOnlyCollection<string>.Empty, sourceList.Select(i => i.ToString()).Where(s => s == null).ToList());
         }
 
         [Fact]
index 21cd3dc..c325d03 100644 (file)
@@ -46,6 +46,7 @@ namespace System.Collections.ObjectModel
     public partial class ReadOnlyObservableCollection<T> : System.Collections.ObjectModel.ReadOnlyCollection<T>, System.Collections.Specialized.INotifyCollectionChanged, System.ComponentModel.INotifyPropertyChanged
     {
         public ReadOnlyObservableCollection(System.Collections.ObjectModel.ObservableCollection<T> list) : base (default(System.Collections.Generic.IList<T>)) { }
+        public static new System.Collections.ObjectModel.ReadOnlyObservableCollection<T> Empty { get { throw null; } }
         protected virtual event System.Collections.Specialized.NotifyCollectionChangedEventHandler? CollectionChanged { add { } remove { } }
         protected virtual event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged { add { } remove { } }
         event System.Collections.Specialized.NotifyCollectionChangedEventHandler? System.Collections.Specialized.INotifyCollectionChanged.CollectionChanged { add { } remove { } }
index 0402b0a..e53c0cc 100644 (file)
@@ -28,6 +28,11 @@ namespace System.Collections.ObjectModel
             ((INotifyPropertyChanged)Items).PropertyChanged += new PropertyChangedEventHandler(HandlePropertyChanged);
         }
 
+        /// <summary>Gets an empty <see cref="ReadOnlyObservableCollection{T}"/>.</summary>
+        /// <value>An empty <see cref="ReadOnlyObservableCollection{T}"/>.</value>
+        /// <remarks>The returned instance is immutable and will always be empty.</remarks>
+        public static new ReadOnlyObservableCollection<T> Empty { get; } = new ReadOnlyObservableCollection<T>(new ObservableCollection<T>());
+
         /// <summary>
         /// CollectionChanged event (per <see cref="INotifyCollectionChanged" />).
         /// </summary>
index dc30790..5b6ee84 100644 (file)
@@ -64,6 +64,14 @@ namespace System.Collections.ObjectModel.Tests
             AssertExtensions.Throws<ArgumentNullException>("dictionary", () => new ReadOnlyDictionary<int, string>(null));
         }
 
+        [Fact]
+        public static void Empty_Idempotent()
+        {
+            Assert.NotNull(ReadOnlyDictionary<string, int>.Empty);
+            Assert.Equal(0, ReadOnlyDictionary<string, int>.Empty.Count);
+            Assert.Same(ReadOnlyDictionary<string, int>.Empty, ReadOnlyDictionary<string, int>.Empty);
+        }
+
         /// <summary>
         /// Tests that true is returned when the key exists in the dictionary
         /// and false otherwise.
index 981caa7..6e22851 100644 (file)
@@ -33,6 +33,14 @@ namespace System.Collections.ObjectModel.Tests
         }
 
         [Fact]
+        public static void Empty_Idempotent()
+        {
+            Assert.NotNull(ReadOnlyObservableCollection<int>.Empty);
+            Assert.Equal(0, ReadOnlyObservableCollection<int>.Empty.Count);
+            Assert.Same(ReadOnlyObservableCollection<int>.Empty, ReadOnlyObservableCollection<int>.Empty);
+        }
+
+        [Fact]
         public static void GetItemTests()
         {
             string[] anArray = new string[] { "one", "two", "three", "four", "five" };
index 1107dfa..4e4bb70 100644 (file)
@@ -23,10 +23,10 @@ namespace System.Collections.ObjectModel
             this.list = list;
         }
 
-        // TODO https://github.com/dotnet/runtime/issues/76028: Make this public.
         /// <summary>Gets an empty <see cref="ReadOnlyCollection{T}"/>.</summary>
+        /// <value>An empty <see cref="ReadOnlyCollection{T}"/>.</value>
         /// <remarks>The returned instance is immutable and will always be empty.</remarks>
-        internal static ReadOnlyCollection<T> Empty { get; } = new ReadOnlyCollection<T>(Array.Empty<T>());
+        public static ReadOnlyCollection<T> Empty { get; } = new ReadOnlyCollection<T>(Array.Empty<T>());
 
         public int Count => list.Count;
 
index a13af07..31fd226 100644 (file)
@@ -27,6 +27,11 @@ namespace System.Collections.ObjectModel
             m_dictionary = dictionary;
         }
 
+        /// <summary>Gets an empty <see cref="ReadOnlyDictionary{TKey, TValue}"/>.</summary>
+        /// <value>An empty <see cref="ReadOnlyDictionary{TKey, TValue}"/>.</value>
+        /// <remarks>The returned instance is immutable and will always be empty.</remarks>
+        public static ReadOnlyDictionary<TKey, TValue> Empty { get; } = new ReadOnlyDictionary<TKey, TValue>(new Dictionary<TKey, TValue>());
+
         protected IDictionary<TKey, TValue> Dictionary => m_dictionary;
 
         public KeyCollection Keys => _keys ??= new KeyCollection(m_dictionary.Keys);
index 923a138..28e992e 100644 (file)
@@ -73,7 +73,7 @@ namespace System.Runtime.Serialization.DataContracts
             set => _helper.Members = value;
         }
 
-        public override ReadOnlyCollection<DataMember> DataMembers => (Members == null) ? DataContract.s_emptyDataMemberList : Members.AsReadOnly();
+        public override ReadOnlyCollection<DataMember> DataMembers => (Members == null) ? ReadOnlyCollection<DataMember>.Empty : Members.AsReadOnly();
 
         internal XmlDictionaryString?[]? ChildElementNamespaces
         {
index 42c66a2..f5776be 100644 (file)
@@ -34,8 +34,6 @@ namespace System.Runtime.Serialization.DataContracts
             DynamicallyAccessedMemberTypes.PublicFields |
             DynamicallyAccessedMemberTypes.PublicProperties;
 
-        internal static ReadOnlyCollection<DataMember> s_emptyDataMemberList = new List<DataMember>().AsReadOnly();
-
         private XmlDictionaryString _name;
         private XmlDictionaryString _ns;
         private readonly DataContractCriticalHelper _helper;
@@ -270,7 +268,7 @@ namespace System.Runtime.Serialization.DataContracts
             return false;
         }
 
-        public virtual ReadOnlyCollection<DataMember> DataMembers => s_emptyDataMemberList;
+        public virtual ReadOnlyCollection<DataMember> DataMembers => ReadOnlyCollection<DataMember>.Empty;
 
         internal virtual void WriteRootElement(XmlWriterDelegator writer, XmlDictionaryString name, XmlDictionaryString? ns)
         {
index 5542f33..356ab2e 100644 (file)
@@ -152,18 +152,10 @@ namespace System.Runtime.Serialization
         {
             get
             {
-                if (_knownTypeCollection == null)
-                {
-                    if (_knownTypeList != null)
-                    {
-                        _knownTypeCollection = new ReadOnlyCollection<Type>(_knownTypeList);
-                    }
-                    else
-                    {
-                        _knownTypeCollection = new ReadOnlyCollection<Type>(Type.EmptyTypes);
-                    }
-                }
-                return _knownTypeCollection;
+                return _knownTypeCollection ??=
+                    _knownTypeList != null ?
+                        new ReadOnlyCollection<Type>(_knownTypeList) :
+                        ReadOnlyCollection<Type>.Empty;
             }
         }
 
index 7f7f6b0..8e382fe 100644 (file)
@@ -53,7 +53,7 @@ namespace System.Runtime.Serialization.DataContracts
             set => _helper.Members = value;
         }
 
-        public override ReadOnlyCollection<DataMember> DataMembers => (Members == null) ? DataContract.s_emptyDataMemberList : Members.AsReadOnly();
+        public override ReadOnlyCollection<DataMember> DataMembers => (Members == null) ? ReadOnlyCollection<DataMember>.Empty : Members.AsReadOnly();
 
         internal List<long>? Values
         {
index 36b17ab..7708ab6 100644 (file)
@@ -117,18 +117,10 @@ namespace System.Runtime.Serialization.Json
         {
             get
             {
-                if (_knownTypeCollection == null)
-                {
-                    if (knownTypeList != null)
-                    {
-                        _knownTypeCollection = new ReadOnlyCollection<Type>(knownTypeList);
-                    }
-                    else
-                    {
-                        _knownTypeCollection = new ReadOnlyCollection<Type>(Type.EmptyTypes);
-                    }
-                }
-                return _knownTypeCollection;
+                return _knownTypeCollection ??=
+                    knownTypeList != null ?
+                        new ReadOnlyCollection<Type>(knownTypeList) :
+                        ReadOnlyCollection<Type>.Empty;
             }
         }
 
index 1c65071..56d7bfb 100644 (file)
@@ -29,12 +29,12 @@ namespace System.Reflection.TypeLoading
         public static ReadOnlyCollection<CustomAttributeTypedArgument> CloneForApiReturn(this IList<CustomAttributeTypedArgument> cats)
         {
             int count = cats.Count;
-            CustomAttributeTypedArgument[] clones = new CustomAttributeTypedArgument[count];
+            CustomAttributeTypedArgument[] clones = count != 0 ? new CustomAttributeTypedArgument[count] : Array.Empty<CustomAttributeTypedArgument>();
             for (int i = 0; i < count; i++)
             {
                 clones[i] = cats[i].CloneForApiReturn();
             }
-            return clones.ToReadOnlyCollection();
+            return Array.AsReadOnly(clones);
         }
 
         /// <summary>
@@ -43,12 +43,12 @@ namespace System.Reflection.TypeLoading
         public static ReadOnlyCollection<CustomAttributeNamedArgument> CloneForApiReturn(this IList<CustomAttributeNamedArgument> cans)
         {
             int count = cans.Count;
-            CustomAttributeNamedArgument[] clones = new CustomAttributeNamedArgument[count];
+            CustomAttributeNamedArgument[] clones = count != 0 ? new CustomAttributeNamedArgument[count] : Array.Empty<CustomAttributeNamedArgument>();
             for (int i = 0; i < count; i++)
             {
                 clones[i] = cans[i].CloneForApiReturn();
             }
-            return clones.ToReadOnlyCollection();
+            return Array.AsReadOnly(clones);
         }
 
         /// <summary>
@@ -63,12 +63,12 @@ namespace System.Reflection.TypeLoading
                 return cat;
 
             int count = cats.Count;
-            CustomAttributeTypedArgument[] cads = new CustomAttributeTypedArgument[count];
+            CustomAttributeTypedArgument[] cads = count != 0 ? new CustomAttributeTypedArgument[count] : Array.Empty<CustomAttributeTypedArgument>();
             for (int i = 0; i < count; i++)
             {
                 cads[i] = cats[i].CloneForApiReturn();
             }
-            return new CustomAttributeTypedArgument(type, cads.ToReadOnlyCollection());
+            return new CustomAttributeTypedArgument(type, Array.AsReadOnly(cads));
         }
 
         /// <summary>
index b3ab9d6..23dbc98 100644 (file)
@@ -3,6 +3,7 @@
 
 using System.Diagnostics;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Reflection.Metadata;
 using System.Runtime.InteropServices;
 
@@ -105,12 +106,12 @@ namespace System.Reflection.TypeLoading.Ecma
         public static IList<CustomAttributeTypedArgument> ToApiForm(this IList<CustomAttributeTypedArgument<RoType>> catgs)
         {
             int count = catgs.Count;
-            CustomAttributeTypedArgument[] cats = new CustomAttributeTypedArgument[count];
+            CustomAttributeTypedArgument[] cats = count != 0 ? new CustomAttributeTypedArgument[count] : Array.Empty<CustomAttributeTypedArgument>();
             for (int i = 0; i < count; i++)
             {
                 cats[i] = catgs[i].ToApiForm();
             }
-            return cats.ToReadOnlyCollection();
+            return Array.AsReadOnly(cats);
         }
 
         /// <summary>
@@ -136,12 +137,12 @@ namespace System.Reflection.TypeLoading.Ecma
         public static IList<CustomAttributeNamedArgument> ToApiForm(this IList<CustomAttributeNamedArgument<RoType>> cangs, Type attributeType)
         {
             int count = cangs.Count;
-            CustomAttributeNamedArgument[] cans = new CustomAttributeNamedArgument[count];
+            CustomAttributeNamedArgument[] cans = count != 0 ? new CustomAttributeNamedArgument[count] : Array.Empty<CustomAttributeNamedArgument>();
             for (int i = 0; i < count; i++)
             {
                 cans[i] = cangs[i].ToApiForm(attributeType);
             }
-            return cans.ToReadOnlyCollection();
+            return Array.AsReadOnly(cans);
         }
 
         /// <summary>
index 0803703..04f2d4a 100644 (file)
@@ -29,9 +29,8 @@ namespace System.Reflection.TypeLoading
 
         public static ReadOnlyCollection<T> ToReadOnlyCollection<T>(this IEnumerable<T> enumeration)
         {
-            // todo: use IEnumerable<T> extension: return new ReadOnlyCollection<T>(enumeration.ToArray());
             List<T> list = new List<T>(enumeration);
-            return new ReadOnlyCollection<T>(list.ToArray());
+            return Array.AsReadOnly(list.ToArray());
         }
 
         public static int GetTokenRowNumber(this int token) => token & 0x00ffffff;
index 5d81222..a78deaa 100644 (file)
@@ -3,6 +3,7 @@
 
 using System.Diagnostics;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Collections.Immutable;
 using System.Reflection.Metadata;
 
@@ -37,7 +38,7 @@ namespace System.Reflection.TypeLoading.Ecma
 
                 ImmutableArray<RoType> sig = sigHandle.GetStandaloneSignature(reader).DecodeLocalSignature(typeProvider, TypeContext);
                 int count = sig.Length;
-                LocalVariableInfo[] lvis = new LocalVariableInfo[count];
+                LocalVariableInfo[] lvis = count != 0 ? new LocalVariableInfo[count] : Array.Empty<LocalVariableInfo>();
                 for (int i = 0; i < count; i++)
                 {
                     bool isPinned = false;
@@ -50,7 +51,7 @@ namespace System.Reflection.TypeLoading.Ecma
 
                     lvis[i] = new RoLocalVariableInfo(localIndex: i, isPinned: isPinned, localType: localType);
                 }
-                return lvis.ToReadOnlyCollection();
+                return Array.AsReadOnly(lvis);
             }
         }
 
@@ -60,7 +61,7 @@ namespace System.Reflection.TypeLoading.Ecma
             {
                 ImmutableArray<ExceptionRegion> regions = Block.ExceptionRegions;
                 int count = regions.Length;
-                ExceptionHandlingClause[] clauses = new ExceptionHandlingClause[count];
+                ExceptionHandlingClause[] clauses = count != 0 ? new ExceptionHandlingClause[count] : Array.Empty<ExceptionHandlingClause>();
                 for (int i = 0; i < count; i++)
                 {
                     EntityHandle catchTypeHandle = regions[i].CatchType;
@@ -75,7 +76,7 @@ namespace System.Reflection.TypeLoading.Ecma
                         handlerLength: regions[i].HandlerLength
                     );
                 }
-                return clauses.ToReadOnlyCollection();
+                return new ReadOnlyCollection<ExceptionHandlingClause>(clauses);
             }
         }
 
index 2944274..b505cac 100644 (file)
@@ -7531,6 +7531,7 @@ namespace System.Collections.ObjectModel
     {
         public ReadOnlyCollection(System.Collections.Generic.IList<T> list) { }
         public int Count { get { throw null; } }
+        public static System.Collections.ObjectModel.ReadOnlyCollection<T> Empty { get { throw null; } }
         public T this[int index] { get { throw null; } }
         protected System.Collections.Generic.IList<T> Items { get { throw null; } }
         bool System.Collections.Generic.ICollection<T>.IsReadOnly { get { throw null; } }
@@ -7564,6 +7565,7 @@ namespace System.Collections.ObjectModel
         public ReadOnlyDictionary(System.Collections.Generic.IDictionary<TKey, TValue> dictionary) { }
         public int Count { get { throw null; } }
         protected System.Collections.Generic.IDictionary<TKey, TValue> Dictionary { get { throw null; } }
+        public static System.Collections.ObjectModel.ReadOnlyDictionary<TKey, TValue> Empty { get { throw null; } }
         public TValue this[TKey key] { get { throw null; } }
         public System.Collections.ObjectModel.ReadOnlyDictionary<TKey, TValue>.KeyCollection Keys { get { throw null; } }
         bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>.IsReadOnly { get { throw null; } }
index 22ebc9c..d8b1467 100644 (file)
@@ -17,8 +17,6 @@ namespace System.Collections.ObjectModel.Tests
     /// </summary>
     public class ReadOnlyCollectionTests : CollectionTestBase
     {
-        private static readonly ReadOnlyCollection<int> s_empty = new ReadOnlyCollection<int>(new int[0]);
-
         [Fact]
         public static void Ctor_NullList_ThrowsArgumentNullException()
         {
@@ -37,7 +35,14 @@ namespace System.Collections.ObjectModel.Tests
         {
             var collection = new ReadOnlyCollection<int>(s_intArray);
             Assert.Equal(s_intArray.Length, collection.Count);
-            Assert.Equal(0, s_empty.Count);
+        }
+
+        [Fact]
+        public static void Empty_Idempotent()
+        {
+            Assert.NotNull(ReadOnlyCollection<int>.Empty);
+            Assert.Equal(0, ReadOnlyCollection<int>.Empty.Count);
+            Assert.Same(ReadOnlyCollection<int>.Empty, ReadOnlyCollection<int>.Empty);
         }
 
         [Fact]
@@ -56,7 +61,7 @@ namespace System.Collections.ObjectModel.Tests
             var collection = new Collection<int>(s_intArray);
             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => collection[-1]);
             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => collection[s_intArray.Length]);
-            AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => s_empty[0]);
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => ReadOnlyCollection<int>.Empty[0]);
         }
 
         [Fact]
@@ -65,8 +70,8 @@ namespace System.Collections.ObjectModel.Tests
             var collection = new ReadOnlyCollection<int>(s_intArray);
             Assert.True(((IList)collection).IsReadOnly);
             Assert.True(((IList<int>)collection).IsReadOnly);
-            Assert.True(((IList)s_empty).IsReadOnly);
-            Assert.True(((IList<int>)s_empty).IsReadOnly);
+            Assert.True(((IList)ReadOnlyCollection<int>.Empty).IsReadOnly);
+            Assert.True(((IList<int>)ReadOnlyCollection<int>.Empty).IsReadOnly);
         }
 
         [Fact]