Add ImmutableCollectionsMarshal type and AsImmutableArray/AsArray APIs (#85526)
authorSergio Pedri <sergio0694@live.com>
Fri, 28 Apr 2023 16:38:49 +0000 (09:38 -0700)
committerGitHub <noreply@github.com>
Fri, 28 Apr 2023 16:38:49 +0000 (12:38 -0400)
* Add ImmutableCollectionsMarshal type

* Add ImmutableCollectionsMarshal to ref assembly

* Add ImmutableCollectionsMarshal unit tests

* Use ImmutableCollectionsMarshal in System.Reflection.Metadata

* Remove ImmutableArrayFactory and use new API

* Leverage internal members in ImmutableCollectionsMarshal

* Remove leftover unsafe modifier in ImmutableCollectionsMarshal

18 files changed:
src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs
src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/ImmutableArrayFactory.cs [deleted file]
src/libraries/System.Collections.Immutable/src/System/Runtime.InteropServices/ImmutableCollectionsMarshal.cs [new file with mode: 0644]
src/libraries/System.Collections.Immutable/tests/ImmutableCollectionsMarshal.cs [new file with mode: 0644]
src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj
src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj
src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/MemoryBlocks/ByteArrayMemoryProvider.cs
src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/ImmutableByteArrayInterop.cs [deleted file]
src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs
src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/IL/MethodBodyBlock.cs
src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/MetadataReader.cs
src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs
src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj
src/libraries/System.Reflection.Metadata/tests/TestUtilities/PinnedBlob.cs
src/libraries/System.Reflection.Metadata/tests/Utilities/ImmutableByteArrayInteropTest.cs [deleted file]

index bb9e96e..aef043f 100644 (file)
@@ -1257,3 +1257,11 @@ namespace System.Linq
         public static System.Collections.Generic.IEnumerable<T> Where<T>(this System.Collections.Immutable.ImmutableArray<T> immutableArray, System.Func<T, bool> predicate) { throw null; }
     }
 }
+namespace System.Runtime.InteropServices
+{
+    public static partial class ImmutableCollectionsMarshal
+    {
+        public static System.Collections.Immutable.ImmutableArray<T> AsImmutableArray<T>(T[]? array) { throw null; }
+        public static T[]? AsArray<T>(System.Collections.Immutable.ImmutableArray<T> array) { throw null; }
+    }
+}
index 64b075b..8ec05a3 100644 (file)
@@ -24,7 +24,6 @@ The System.Collections.Immutable library is built-in as part of the shared frame
     <Compile Include="System\Collections\Frozen\FrozenHashTable.cs" />
     <Compile Include="System\Collections\Frozen\FrozenSet.cs" />
     <Compile Include="System\Collections\Frozen\FrozenSetInternalBase.cs" />
-    <Compile Include="System\Collections\Frozen\ImmutableArrayFactory.cs" />
     <Compile Include="System\Collections\Frozen\ItemsFrozenSet.cs" />
     <Compile Include="System\Collections\Frozen\KeysAndValuesFrozenDictionary.cs" />
     <Compile Include="System\Collections\Frozen\SmallFrozenDictionary.cs" />
@@ -141,6 +140,7 @@ The System.Collections.Immutable library is built-in as part of the shared frame
     <Compile Include="System\Collections\Immutable\SortedInt32KeyNode.cs" />
     <Compile Include="System\Collections\Immutable\SortedInt32KeyNode.Enumerator.cs" />
     <Compile Include="System\Linq\ImmutableArrayExtensions.cs" />
+    <Compile Include="System\Runtime.InteropServices\ImmutableCollectionsMarshal.cs" />
     <Compile Include="Validation\Requires.cs" />
     <Compile Include="$(CommonPath)System\Runtime\Versioning\NonVersionableAttribute.cs" Link="Common\System\Runtime\Versioning\NonVersionableAttribute.cs" />
     <None Include="Interfaces.cd" />
index a1e5016..607284e 100644 (file)
@@ -7,6 +7,7 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 
 namespace System.Collections.Frozen
 {
@@ -338,7 +339,7 @@ namespace System.Collections.Frozen
         /// <remarks>
         /// The order of the keys in the dictionary is unspecified, but it is the same order as the associated values returned by the <see cref="Values"/> property.
         /// </remarks>
-        public ImmutableArray<TKey> Keys => ImmutableArrayFactory.Create(KeysCore);
+        public ImmutableArray<TKey> Keys => ImmutableCollectionsMarshal.AsImmutableArray(KeysCore);
 
         /// <inheritdoc cref="Keys" />
         private protected abstract TKey[] KeysCore { get; }
@@ -360,7 +361,7 @@ namespace System.Collections.Frozen
         /// <remarks>
         /// The order of the values in the dictionary is unspecified, but it is the same order as the associated keys returned by the <see cref="Keys"/> property.
         /// </remarks>
-        public ImmutableArray<TValue> Values => ImmutableArrayFactory.Create(ValuesCore);
+        public ImmutableArray<TValue> Values => ImmutableCollectionsMarshal.AsImmutableArray(ValuesCore);
 
         /// <inheritdoc cref="Values" />
         private protected abstract TValue[] ValuesCore { get; }
index df86732..7237e79 100644 (file)
@@ -7,6 +7,7 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Numerics;
+using System.Runtime.InteropServices;
 
 namespace System.Collections.Frozen
 {
@@ -274,7 +275,7 @@ namespace System.Collections.Frozen
 
         /// <summary>Gets a collection containing the values in the set.</summary>
         /// <remarks>The order of the values in the set is unspecified.</remarks>
-        public ImmutableArray<T> Items => ImmutableArrayFactory.Create(ItemsCore);
+        public ImmutableArray<T> Items => ImmutableCollectionsMarshal.AsImmutableArray(ItemsCore);
 
         /// <inheritdoc cref="Items" />
         private protected abstract T[] ItemsCore { get; }
diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/ImmutableArrayFactory.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/ImmutableArrayFactory.cs
deleted file mode 100644 (file)
index a1ad3b2..0000000
+++ /dev/null
@@ -1,19 +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.Immutable;
-
-namespace System.Collections.Frozen
-{
-    /// <summary>
-    /// Stubs to isolate the frozen collection code from the internal details of ImmutableArray
-    /// </summary>
-    /// <remarks>
-    /// This is intended to make it easier to use the frozen collections in environments/conditions
-    /// when only the public API of ImmutableArray is available.
-    /// </remarks>
-    internal static class ImmutableArrayFactory
-    {
-        public static ImmutableArray<T> Create<T>(T[] array) => new ImmutableArray<T>(array);
-    }
-}
diff --git a/src/libraries/System.Collections.Immutable/src/System/Runtime.InteropServices/ImmutableCollectionsMarshal.cs b/src/libraries/System.Collections.Immutable/src/System/Runtime.InteropServices/ImmutableCollectionsMarshal.cs
new file mode 100644 (file)
index 0000000..737a3a8
--- /dev/null
@@ -0,0 +1,58 @@
+// 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.Immutable;
+
+namespace System.Runtime.InteropServices
+{
+    /// <summary>
+    /// An unsafe class that provides a set of methods to access the underlying data representations of immutable collections.
+    /// </summary>
+    public static class ImmutableCollectionsMarshal
+    {
+        /// <summary>
+        /// Gets an <see cref="ImmutableArray{T}"/> value wrapping the input <typeparamref name="T"/> array.
+        /// </summary>
+        /// <typeparam name="T">The type of elements in the input array.</typeparam>
+        /// <param name="array">The input array to wrap in the returned <see cref="ImmutableArray{T}"/> value.</param>
+        /// <returns>An <see cref="ImmutableArray{T}"/> value wrapping <paramref name="array"/>.</returns>
+        /// <remarks>
+        /// <para>
+        /// When using this method, callers should take extra care to ensure that they're the sole owners of the input
+        /// array, and that it won't be modified once the returned <see cref="ImmutableArray{T}"/> value starts being
+        /// used. Doing so might cause undefined behavior in code paths which don't expect the contents of a given
+        /// <see cref="ImmutableArray{T}"/> values to change after its creation.
+        /// </para>
+        /// <para>
+        /// If <paramref name="array"/> is <see langword="null"/>, the returned <see cref="ImmutableArray{T}"/> value
+        /// will be uninitialized (ie. its <see cref="ImmutableArray{T}.IsDefault"/> property will be <see langword="true"/>).
+        /// </para>
+        /// </remarks>
+        public static ImmutableArray<T> AsImmutableArray<T>(T[]? array)
+        {
+            return new(array);
+        }
+
+        /// <summary>
+        /// Gets the underlying <typeparamref name="T"/> array for an input <see cref="ImmutableArray{T}"/> value.
+        /// </summary>
+        /// <typeparam name="T">The type of elements in the input <see cref="ImmutableArray{T}"/> value.</typeparam>
+        /// <param name="array">The input <see cref="ImmutableArray{T}"/> value to get the underlying <typeparamref name="T"/> array from.</param>
+        /// <returns>The underlying <typeparamref name="T"/> array for <paramref name="array"/>, if present.</returns>
+        /// <remarks>
+        /// <para>
+        /// When using this method, callers should make sure to not pass the resulting underlying array to methods that
+        /// might mutate it. Doing so might cause undefined behavior in code paths using <paramref name="array"/> which
+        /// don't expect the contents of the <see cref="ImmutableArray{T}"/> value to change.
+        /// </para>
+        /// <para>
+        /// If <paramref name="array"/> is uninitialized (ie. its <see cref="ImmutableArray{T}.IsDefault"/> property is
+        /// <see langword="true"/>), the resulting <typeparamref name="T"/> array will be <see langword="null"/>.
+        /// </para>
+        /// </remarks>
+        public static T[]? AsArray<T>(ImmutableArray<T> array)
+        {
+            return array.array;
+        }
+    }
+}
diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableCollectionsMarshal.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableCollectionsMarshal.cs
new file mode 100644 (file)
index 0000000..76ed476
--- /dev/null
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Xunit;
+
+namespace System.Collections.Immutable.Tests
+{
+    public class ImmutableCollectionsMarshalTest
+    {
+        [Fact]
+        public void AsImmutableArrayFromNullArray()
+        {
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<int>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<int?>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<Guid>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<Guid?>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<string>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<CustomClass>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<ManagedCustomStruct>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<ManagedCustomStruct?>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<UnmanagedCustomStruct>(null).IsDefault);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray<UnmanagedCustomStruct?>(null).IsDefault);
+        }
+
+        [Fact]
+        public void AsImmutableArrayFromEmptyArray()
+        {
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<int>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<int?>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<Guid>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<Guid?>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<string>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<CustomClass>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<ManagedCustomStruct>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<ManagedCustomStruct?>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<UnmanagedCustomStruct>()).IsEmpty);
+            Assert.True(ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<UnmanagedCustomStruct?>()).IsEmpty);
+        }
+
+        [Fact]
+        public void AsImmutableArrayFromExistingArray()
+        {
+            static void Test<T>()
+            {
+                T[] array = new T[17];
+                ImmutableArray<T> immutableArray = ImmutableCollectionsMarshal.AsImmutableArray(array);
+
+                Assert.False(immutableArray.IsDefault);
+                Assert.Equal(17, immutableArray.Length);
+
+                ref T expectedRef = ref array[0];
+                ref T actualRef = ref Unsafe.AsRef(in immutableArray.ItemRef(0));
+
+                Assert.True(Unsafe.AreSame(ref expectedRef, ref actualRef));
+            }
+
+            Test<int>();
+            Test<int?>();
+            Test<Guid>();
+            Test<Guid?>();
+            Test<string>();
+            Test<CustomClass>();
+            Test<ManagedCustomStruct>();
+            Test<ManagedCustomStruct?>();
+            Test<UnmanagedCustomStruct>();
+            Test<UnmanagedCustomStruct?>();
+        }
+
+        [Fact]
+        public void AsArrayFromDefaultImmutableArray()
+        {
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<int>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<int?>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<Guid>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<Guid?>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<string>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<CustomClass>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<ManagedCustomStruct>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<ManagedCustomStruct?>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<UnmanagedCustomStruct>(default));
+            Assert.Null(ImmutableCollectionsMarshal.AsArray<UnmanagedCustomStruct?>(default));
+        }
+
+        [Fact]
+        public void AsArrayFromEmptyImmutableArray()
+        {
+            static void Test<T>()
+            {
+                T[]? array = ImmutableCollectionsMarshal.AsArray(ImmutableArray<T>.Empty);
+
+                Assert.NotNull(array);
+                Assert.Empty(array);
+            }
+
+            Test<int>();
+            Test<int?>();
+            Test<Guid>();
+            Test<Guid?>();
+            Test<string>();
+            Test<CustomClass>();
+            Test<ManagedCustomStruct>();
+            Test<ManagedCustomStruct?>();
+            Test<UnmanagedCustomStruct>();
+            Test<UnmanagedCustomStruct?>();
+        }
+
+        [Fact]
+        public void AsArrayFromConstructedImmutableArray()
+        {
+            static void Test<T>()
+            {
+                ImmutableArray<T> immutableArray = ImmutableArray.Create(new T[17]);
+                T[]? array = ImmutableCollectionsMarshal.AsArray(immutableArray);
+
+                Assert.NotNull(array);
+                Assert.Equal(17, array.Length);
+
+                ref T expectedRef = ref Unsafe.AsRef(in immutableArray.ItemRef(0));
+                ref T actualRef = ref array[0];
+
+                Assert.True(Unsafe.AreSame(ref expectedRef, ref actualRef));
+            }
+
+            Test<int>();
+            Test<int?>();
+            Test<Guid>();
+            Test<Guid?>();
+            Test<string>();
+            Test<CustomClass>();
+            Test<ManagedCustomStruct>();
+            Test<ManagedCustomStruct?>();
+            Test<UnmanagedCustomStruct>();
+            Test<UnmanagedCustomStruct?>();
+        }
+
+        public class CustomClass
+        {
+            public object Foo;
+            public Guid Bar;
+        }
+
+        public struct ManagedCustomStruct
+        {
+            public object Foo;
+            public Guid Bar;
+        }
+
+        public struct UnmanagedCustomStruct
+        {
+            public Guid Foo;
+            public int Bar;
+        }
+    }
+}
index dfc053e..307c682 100644 (file)
@@ -21,6 +21,7 @@
     <Compile Include="Frozen\FrozenFromKnownValuesTests.cs" />
     <Compile Include="Frozen\KeyAnalyzerTests.cs" />
     <Compile Include="ImmutableArrayExtensionsTest.cs" />
+    <Compile Include="ImmutableCollectionsMarshal.cs" />
     <Compile Include="ImmutableArrayTest.cs" />
     <Compile Include="ImmutableArrayTest.netcoreapp.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
     <Compile Include="ImmutableArrayTestBase.cs" />
index ad67c99..d241f17 100644 (file)
@@ -110,7 +110,6 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo
     <Compile Include="System\Reflection\Internal\Utilities\StreamExtensions.netcoreapp.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
     <Compile Include="System\Reflection\Internal\Utilities\StreamExtensions.netstandard2.0.cs" Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
     <Compile Include="System\Reflection\Internal\Utilities\Hash.cs" />
-    <Compile Include="System\Reflection\Internal\Utilities\ImmutableByteArrayInterop.cs" />
     <Compile Include="System\Reflection\Internal\Utilities\ImmutableMemoryStream.cs" />
     <Compile Include="System\Reflection\Internal\Utilities\MemoryBlock.cs" />
     <Compile Include="System\Reflection\Internal\Utilities\PooledStringBuilder.cs" />
index fae4a71..040baf7 100644 (file)
@@ -4,6 +4,7 @@
 using System.Collections.Immutable;
 using System.Diagnostics;
 using System.IO;
+using System.Runtime.InteropServices;
 using System.Threading;
 
 namespace System.Reflection.Internal
@@ -45,7 +46,7 @@ namespace System.Reflection.Internal
             {
                 if (_pinned == null)
                 {
-                    var newPinned = new PinnedObject(ImmutableByteArrayInterop.DangerousGetUnderlyingArray(_array)!);
+                    var newPinned = new PinnedObject(ImmutableCollectionsMarshal.AsArray(_array)!);
 
                     if (Interlocked.CompareExchange(ref _pinned, newPinned, null) != null)
                     {
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/ImmutableByteArrayInterop.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/ImmutableByteArrayInterop.cs
deleted file mode 100644 (file)
index 0621334..0000000
+++ /dev/null
@@ -1,80 +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.Immutable;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-
-namespace System.Reflection.Internal
-{
-    /// <summary>
-    /// Provides tools for using <see cref="ImmutableArray{Byte}"/> in interop scenarios.
-    /// </summary>
-    /// <remarks>
-    /// *** WARNING ***
-    ///
-    /// If you decide to copy this code elsewhere, please retain the documentation here
-    /// and the Dangerous prefixes in the API names. This will help track down and audit
-    /// other places where this technique (with dangerous consequences when misused) may
-    /// be applied.
-    ///
-    /// A generic version of this API was once public in a pre-release of immutable
-    /// collections, but  it was deemed to be too subject to abuse when available publicly.
-    ///
-    /// This implementation is scoped to byte arrays as that is all that the metadata reader needs.
-    ///
-    /// Also, since we don't have access to immutable collection internals, we use
-    /// <see cref="Unsafe.As{TFrom, TTo}(ref TFrom)"/>.
-    ///
-    /// The fact that <see cref="ImmutableArray{Byte}"/> is backed by a single byte array
-    /// field is something inherent to the design of ImmutableArray in order to get its performance
-    /// characteristics and therefore something we (Microsoft) are comfortable defining as a contract that
-    /// can be depended upon as below.
-    /// </remarks>
-    internal static unsafe class ImmutableByteArrayInterop
-    {
-        /// <summary>
-        /// Creates a new instance of <see cref="ImmutableArray{Byte}"/> using a given mutable array as the backing
-        /// field, without creating a defensive copy. It is the responsibility of the caller to ensure no other mutable
-        /// references exist to the array.  Do not mutate the array after calling this method.
-        /// </summary>
-        /// <param name="array">The mutable array to use as the backing field. The incoming reference is set to null
-        /// since it should not be retained by the caller.</param>
-        /// <remarks>
-        /// Users of this method should take extra care to ensure that the mutable array given as a parameter
-        /// is never modified. The returned <see cref="ImmutableArray{T}"/> will use the given array as its backing
-        /// field without creating a defensive copy, so changes made to the given mutable array will be observable
-        /// on the returned <see cref="ImmutableArray{T}"/>.  Instance and static methods of <see cref="ImmutableArray{T}"/>
-        /// and <see cref="ImmutableArray"/> may malfunction if they operate on an <see cref="ImmutableArray{T}"/> instance
-        /// whose underlying backing field is modified.
-        /// </remarks>
-        /// <returns>An immutable array.</returns>
-        internal static ImmutableArray<byte> DangerousCreateFromUnderlyingArray(ref byte[]? array)
-        {
-            byte[] givenArray = array!;
-            array = null;
-
-            return Unsafe.As<byte[], ImmutableArray<byte>>(ref givenArray);
-        }
-
-        /// <summary>
-        /// Access the backing mutable array instance for the given <see cref="ImmutableArray{T}"/>, without
-        /// creating a defensive copy.  It is the responsibility of the caller to ensure the array is not modified
-        /// through the returned mutable reference.  Do not mutate the returned array.
-        /// </summary>
-        /// <param name="array">The <see cref="ImmutableArray{T}"/> from which to retrieve the backing field.</param>
-        /// <remarks>
-        /// Users of this method should take extra care to ensure that the returned mutable array is never modified.
-        /// The returned mutable array continues to be used as the backing field of the given <see cref="ImmutableArray{T}"/>
-        /// without creating a defensive copy, so changes made to the returned mutable array will be observable
-        /// on the given <see cref="ImmutableArray{T}"/>.  Instance and static methods of <see cref="ImmutableArray{T}"/>
-        /// and <see cref="ImmutableArray"/> may malfunction if they operate on an <see cref="ImmutableArray{T}"/> instance
-        /// whose underlying backing field is modified.
-        /// </remarks>
-        /// <returns>The underlying array, or null if <see cref="ImmutableArray{T}.IsDefault"/> is true.</returns>
-        internal static byte[]? DangerousGetUnderlyingArray(ImmutableArray<byte> array)
-        {
-            return Unsafe.As<ImmutableArray<byte>, byte[]>(ref array);
-        }
-    }
-}
index 98bda9e..9804b82 100644 (file)
@@ -316,7 +316,7 @@ namespace System.Reflection.Metadata
         public ImmutableArray<byte> ToImmutableArray(int start, int byteCount)
         {
             byte[]? array = ToArray(start, byteCount);
-            return ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref array);
+            return ImmutableCollectionsMarshal.AsImmutableArray(array);
         }
 
         internal bool TryGetSpan(out ReadOnlySpan<byte> buffer)
index 6e12e43..6085b95 100644 (file)
@@ -72,7 +72,7 @@ namespace System.Reflection.Metadata
         public ImmutableArray<byte> GetILContent()
         {
             byte[]? bytes = GetILBytes();
-            return ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref bytes);
+            return ImmutableCollectionsMarshal.AsImmutableArray(bytes);
         }
 
         public BlobReader GetILReader()
index 89a221b..a275a9b 100644 (file)
@@ -6,6 +6,7 @@ using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Reflection.Internal;
 using System.Reflection.Metadata.Ecma335;
+using System.Runtime.InteropServices;
 using System.Text;
 
 namespace System.Reflection.Metadata
@@ -551,7 +552,7 @@ namespace System.Reflection.Metadata
             externalTableRowCounts = ReadMetadataTableRowCounts(ref reader, externalTableMask);
 
             debugMetadataHeader = new DebugMetadataHeader(
-                ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref pdbId),
+                ImmutableCollectionsMarshal.AsImmutableArray(pdbId),
                 MethodDefinitionHandle.FromRowId(entryPointRowId),
                 idStartOffset: pdbStreamOffset);
         }
@@ -1081,7 +1082,7 @@ namespace System.Reflection.Metadata
         {
             // TODO: We can skip a copy for virtual blobs.
             byte[]? bytes = GetBlobBytes(handle);
-            return ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref bytes);
+            return ImmutableCollectionsMarshal.AsImmutableArray(bytes);
         }
 
         public BlobReader GetBlobReader(BlobHandle handle)
index d2e1083..042d9f9 100644 (file)
@@ -7,6 +7,7 @@ using System.IO;
 using System.Reflection.Internal;
 using System.Reflection.Metadata;
 using System.Runtime.ExceptionServices;
+using System.Runtime.InteropServices;
 using System.Threading;
 using ImmutableArrayExtensions = System.Linq.ImmutableArrayExtensions;
 
@@ -659,7 +660,7 @@ namespace System.Reflection.PortableExecutable
 
             return new PdbChecksumDebugDirectoryData(
                 algorithmName,
-                ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref checksum));
+                ImmutableCollectionsMarshal.AsImmutableArray(checksum));
         }
 
         /// <summary>
index c946c2e..60644e4 100644 (file)
@@ -87,7 +87,6 @@
     <Compile Include="Utilities\StringUtilsTests.cs" />
     <Compile Include="Utilities\BlobReaderTests.cs" />
     <Compile Include="Utilities\CompressedIntegerTests.cs" />
-    <Compile Include="Utilities\ImmutableByteArrayInteropTest.cs" />
     <Compile Include="Utilities\MemoryBlockTests.cs" />
     <Compile Include="Utilities\OrderByTests.cs" />
   </ItemGroup>
index 4fec831..6ac9f8b 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Immutable;
-using System.Reflection.Internal;
 using System.Runtime.InteropServices;
 
 namespace System.Reflection.Metadata.Tests
@@ -13,7 +12,7 @@ namespace System.Reflection.Metadata.Tests
         private readonly byte[] _blob;
 
         public PinnedBlob(ImmutableArray<byte> blob)
-            : this(ImmutableByteArrayInterop.DangerousGetUnderlyingArray(blob))
+            : this(ImmutableCollectionsMarshal.AsArray(blob))
         {
         }
 
diff --git a/src/libraries/System.Reflection.Metadata/tests/Utilities/ImmutableByteArrayInteropTest.cs b/src/libraries/System.Reflection.Metadata/tests/Utilities/ImmutableByteArrayInteropTest.cs
deleted file mode 100644 (file)
index 63b360b..0000000
+++ /dev/null
@@ -1,66 +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.Immutable;
-using System.Reflection.Internal;
-using Xunit;
-
-namespace System.Reflection.Metadata.Tests
-{
-    public class ImmutableByteArrayInteropTest
-    {
-        [Fact]
-        public void DangerousCreateFromUnderlyingArray()
-        {
-            byte[] array = new byte[3] { 1, 2, 3 };
-            byte[] arrayCopy = array;
-            ImmutableArray<byte> immutable = ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref array);
-
-            // DangerousCreateFromUnderlyingArray clears the given parameter as a signal that
-            // the mutable array should no longer be modified through mutable references.
-            Assert.Null(array);
-
-            Assert.Equal(3, immutable.Length);
-            Assert.Equal(1, immutable[0]);
-            Assert.Equal(2, immutable[1]);
-            Assert.Equal(3, immutable[2]);
-
-            arrayCopy[0] = 9;
-
-            Assert.Equal(9, immutable[0]);
-        }
-
-        [Fact]
-        public void DangerousCreateFromUnderlyingArrayNegativeTests()
-        {
-            byte[] array = null;
-            ImmutableArray<byte> immutable = ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref array);
-
-            Assert.True(immutable.IsDefault);
-        }
-
-        [Fact]
-        public void DangerousGetUnderlyingArray()
-        {
-            ImmutableArray<byte> immutable = ImmutableArray.Create<byte>(1, 2, 3);
-            byte[] array = ImmutableByteArrayInterop.DangerousGetUnderlyingArray(immutable);
-
-            Assert.Equal(3, array.Length);
-            Assert.Equal(1, array[0]);
-            Assert.Equal(2, array[1]);
-            Assert.Equal(3, array[2]);
-
-            array[0] = 9;
-
-            Assert.Equal(9, immutable[0]);
-        }
-
-        [Fact]
-        public void DangerousGetUnderlyingArrayNegativeTests()
-        {
-            ImmutableArray<byte> immutable = default(ImmutableArray<byte>);
-
-            Assert.Null(ImmutableByteArrayInterop.DangerousGetUnderlyingArray(immutable));
-        }
-    }
-}