From dcdc8502502f8a05a990efb8a8b87a879c4e3e02 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:45:24 +0900 Subject: [PATCH 1/6] Use ICollection check instead of IList in TryCopyTo This PR change the type check from IList to ICollection. With this change, collections that implement ICollection will have ICollection.CopyTo invoked. --- .../Immutable/ImmutableExtensions.Minimal.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs index 66b365bad872b7..a53beb818a53f9 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs @@ -102,8 +102,8 @@ internal static bool TryCopyTo(this IEnumerable sequence, T[] array, int a Debug.Assert(array != null); Debug.Assert(arrayIndex >= 0 && arrayIndex <= array.Length); - // IList is the GCD of what the following types implement. - if (sequence is IList) + // ICollection is the GCD of what the following types implement. + if (sequence is ICollection) { if (sequence is List list) { @@ -126,6 +126,18 @@ internal static bool TryCopyTo(this IEnumerable sequence, T[] array, int a Array.Copy(immutable.array!, 0, array, arrayIndex, immutable.Length); return true; } + +#if !NET + // On .NET Framework, ICollection.CopyTo may throw an ArrayTypeMismatchException if the underlying type of + // the destination array is not typeof(T[]), but is assignment-compatible with T[]. + if (sequence is Array) + { + return false; + } +#endif + + ((ICollection)sequence).CopyTo(array, arrayIndex); + return true; } return false; From 47540155f099ceae717c3601914bbb66872b8c0f Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sun, 8 Mar 2026 22:14:23 +0900 Subject: [PATCH 2/6] Add test data in ImmutableArrayTest.InsertRangeData --- .../System.Collections.Immutable/tests/ImmutableArrayTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 80a3ec1a37e6f0..96009cf1ae6d02 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1438,6 +1438,8 @@ public static IEnumerable InsertRangeData() yield return new object[] { s_empty, 0, new uint[] { 1, 2, 3 } }; yield return new object[] { s_manyElements, 0, new uint[] { 4, 5, 6 } }; yield return new object[] { s_manyElements, 3, new uint[] { 4, 5, 6 } }; + yield return new object[] { s_manyElements, 0, new Dictionary { [4] = 0, [5] = 0 }.Keys }; + yield return new object[] { s_manyElements, 3, new Dictionary { [4] = 0, [5] = 0 }.Keys }; } [Theory] From bc35c73facff43d705838d359e400a69b26530a1 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sun, 8 Mar 2026 22:15:06 +0900 Subject: [PATCH 3/6] Update src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Collections/Immutable/ImmutableExtensions.Minimal.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs index a53beb818a53f9..b2ed449de0e7fb 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs @@ -128,8 +128,8 @@ internal static bool TryCopyTo(this IEnumerable sequence, T[] array, int a } #if !NET - // On .NET Framework, ICollection.CopyTo may throw an ArrayTypeMismatchException if the underlying type of - // the destination array is not typeof(T[]), but is assignment-compatible with T[]. + // On .NET Framework, if 'sequence' is actually a covariant array (for example, string[] used as ICollection), + // its ICollection.CopyTo implementation may call Array.Copy and throw an ArrayTypeMismatchException when copying into a T[]. if (sequence is Array) { return false; From 4dff47d1f95f73d69e835eb0a235e646e68c7434 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sun, 8 Mar 2026 22:24:00 +0900 Subject: [PATCH 4/6] Fix comment --- .../System/Collections/Immutable/ImmutableExtensions.Minimal.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs index b2ed449de0e7fb..ab0e157a391221 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs @@ -102,7 +102,7 @@ internal static bool TryCopyTo(this IEnumerable sequence, T[] array, int a Debug.Assert(array != null); Debug.Assert(arrayIndex >= 0 && arrayIndex <= array.Length); - // ICollection is the GCD of what the following types implement. + // Optimized paths for well-known types, all other ICollection implementations use the generic CopyTo fallback. if (sequence is ICollection) { if (sequence is List list) From a088eaadbf51378cd878c5f4f30441978ae56045 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sat, 11 Apr 2026 08:05:04 +0900 Subject: [PATCH 5/6] Fix comment --- .../Collections/Immutable/ImmutableExtensions.Minimal.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs index ab0e157a391221..ebfc8c918939df 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs @@ -128,8 +128,9 @@ internal static bool TryCopyTo(this IEnumerable sequence, T[] array, int a } #if !NET - // On .NET Framework, if 'sequence' is actually a covariant array (for example, string[] used as ICollection), - // its ICollection.CopyTo implementation may call Array.Copy and throw an ArrayTypeMismatchException when copying into a T[]. + // On .NET Framework, if 'sequence' is actually a covariant array (for example, a string[] assigned to object[] and passed as ICollection), + // sequence.GetType() won't match typeof(T[]), so the fast path above is skipped. + // The array's ICollection.CopyTo implementation may call Array.Copy and throw an ArrayTypeMismatchException when copying into a T[]. if (sequence is Array) { return false; From 4eb1db5bcea27de7d580d847fb87bd50a8927e82 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:14:30 +0900 Subject: [PATCH 6/6] Store the result of ICollection cast to avoid redoing --- .../Immutable/ImmutableExtensions.Minimal.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs index ebfc8c918939df..1434fa3b633afb 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableExtensions.Minimal.cs @@ -103,9 +103,9 @@ internal static bool TryCopyTo(this IEnumerable sequence, T[] array, int a Debug.Assert(arrayIndex >= 0 && arrayIndex <= array.Length); // Optimized paths for well-known types, all other ICollection implementations use the generic CopyTo fallback. - if (sequence is ICollection) + if (sequence is ICollection collection) { - if (sequence is List list) + if (collection is List list) { list.CopyTo(array, arrayIndex); return true; @@ -114,14 +114,14 @@ internal static bool TryCopyTo(this IEnumerable sequence, T[] array, int a // Array.Copy can throw an ArrayTypeMismatchException if the underlying type of // the destination array is not typeof(T[]), but is assignment-compatible with T[]. // See https://github.com/dotnet/runtime/issues/14794 for more info. - if (sequence.GetType() == typeof(T[])) + if (collection.GetType() == typeof(T[])) { - var sourceArray = (T[])sequence; + var sourceArray = (T[])collection; Array.Copy(sourceArray, 0, array, arrayIndex, sourceArray.Length); return true; } - if (sequence is ImmutableArray immutable) + if (collection is ImmutableArray immutable) { Array.Copy(immutable.array!, 0, array, arrayIndex, immutable.Length); return true; @@ -131,13 +131,13 @@ internal static bool TryCopyTo(this IEnumerable sequence, T[] array, int a // On .NET Framework, if 'sequence' is actually a covariant array (for example, a string[] assigned to object[] and passed as ICollection), // sequence.GetType() won't match typeof(T[]), so the fast path above is skipped. // The array's ICollection.CopyTo implementation may call Array.Copy and throw an ArrayTypeMismatchException when copying into a T[]. - if (sequence is Array) + if (collection is Array) { return false; } #endif - ((ICollection)sequence).CopyTo(array, arrayIndex); + collection.CopyTo(array, arrayIndex); return true; }