-
Notifications
You must be signed in to change notification settings - Fork 5
Add polyfills for System.Collections.Generic.CollectionExtensions #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Tyrrrz
merged 14 commits into
prime
from
copilot/add-polyfills-for-collectionextensions
Apr 17, 2026
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
92719d2
Add CollectionExtensions polyfills (TryAdd, Remove, AsReadOnly, ReadO…
Copilot db3b600
Optimize InsertRange to use List.InsertRange(IEnumerable) for O(n) co…
Copilot 75889a5
Fix CS0592: remove ExcludeFromCodeCoverage from IReadOnlySet interface
Copilot 17e7d6b
Move IsSubsetOf/IsSupersetOf assertions into first test
Copilot 47563f9
Update CollectionExtensionsTests.cs
Tyrrrz 369b309
Update CollectionExtensionsTests.cs
Tyrrrz 847bea2
Update CollectionExtensions.cs
Tyrrrz 9e5ffb3
Move AsReadOnly IList/IDictionary polyfills from Net50 to Net70
Copilot d56216a
Use act.Should().Throw() and explicit cast var patterns in tests
Copilot af72173
Use var with explicit cast pattern in all collection extension tests
Copilot b1b8c3d
Use var with explicit cast for ReadOnlySpan declarations in Net80 tests
Copilot 1ce4989
Remove unnecessary IList/IDictionary bridge extensions from Net100/Co…
Copilot f447da3
Use nested #if for NETFRAMEWORK guards; use target-typed new in arrow…
Copilot f3fa29e
Merge branch 'prime' into copilot/add-polyfills-for-collectionextensions
Tyrrrz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| using System.Collections.Generic; | ||
| using System.Collections.ObjectModel; | ||
| using FluentAssertions; | ||
| using Xunit; | ||
|
|
||
| namespace PolyShim.Tests.Net100; | ||
|
|
||
| public class CollectionExtensionsTests | ||
| { | ||
| [Fact] | ||
| public void AsReadOnly_ISet_Test() | ||
| { | ||
| // Arrange | ||
| var set = (ISet<int>)new HashSet<int> { 1, 2, 3 }; | ||
|
|
||
| // Act | ||
| var readOnly = set.AsReadOnly(); | ||
|
|
||
| // Assert | ||
| readOnly.Should().BeOfType<ReadOnlySet<int>>(); | ||
| readOnly.Count.Should().Be(3); | ||
| readOnly.Contains(1).Should().BeTrue(); | ||
| readOnly.Contains(4).Should().BeFalse(); | ||
| readOnly.IsSubsetOf(new[] { 1, 2, 3 }).Should().BeTrue(); | ||
| readOnly.IsSupersetOf(new[] { 1 }).Should().BeTrue(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void AsReadOnly_ISet_ReflectsChanges_Test() | ||
| { | ||
| // Arrange | ||
| var inner = new HashSet<int> { 1, 2, 3 }; | ||
| var set = (ISet<int>)inner; | ||
|
|
||
| // Act | ||
| var readOnly = set.AsReadOnly(); | ||
| inner.Add(4); | ||
|
|
||
| // Assert | ||
| readOnly.Count.Should().Be(4); | ||
| readOnly.Contains(4).Should().BeTrue(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| using System.Collections.Generic; | ||
| using System.Collections.ObjectModel; | ||
| using FluentAssertions; | ||
| using Xunit; | ||
|
|
||
| namespace PolyShim.Tests.Net70; | ||
|
|
||
| public class CollectionExtensionsTests | ||
| { | ||
| [Fact] | ||
| public void AsReadOnly_IList_Test() | ||
| { | ||
| // Arrange | ||
| var list = (IList<string>)new List<string> { "a", "b", "c" }; | ||
|
|
||
| // Act | ||
| var readOnly = list.AsReadOnly(); | ||
|
|
||
| // Assert | ||
| readOnly.Should().BeOfType<ReadOnlyCollection<string>>(); | ||
| readOnly.Should().Equal("a", "b", "c"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void AsReadOnly_IList_ReflectsChanges_Test() | ||
| { | ||
| // Arrange | ||
| var inner = new List<string> { "a", "b" }; | ||
| var list = (IList<string>)inner; | ||
|
|
||
| // Act | ||
| var readOnly = list.AsReadOnly(); | ||
| inner.Add("c"); | ||
|
|
||
| // Assert | ||
| readOnly.Should().Equal("a", "b", "c"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void AsReadOnly_IDictionary_Test() | ||
|
Tyrrrz marked this conversation as resolved.
|
||
| { | ||
| // Arrange | ||
| var dictionary = | ||
| (IDictionary<string, int>)new Dictionary<string, int> { ["one"] = 1, ["two"] = 2 }; | ||
|
|
||
| // Act | ||
| var readOnly = dictionary.AsReadOnly(); | ||
|
|
||
| // Assert | ||
| readOnly.Should().BeOfType<ReadOnlyDictionary<string, int>>(); | ||
| readOnly["one"].Should().Be(1); | ||
| readOnly["two"].Should().Be(2); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using FluentAssertions; | ||
| using Xunit; | ||
|
|
||
| namespace PolyShim.Tests.Net80; | ||
|
|
||
| public class CollectionExtensionsTests | ||
| { | ||
| [Fact] | ||
| public void AddRange_Test() | ||
| { | ||
| // Arrange | ||
| var list = new List<int> { 1, 2, 3 }; | ||
| var items = (ReadOnlySpan<int>)new[] { 4, 5, 6 }; | ||
|
|
||
| // Act | ||
| list.AddRange(items); | ||
|
|
||
| // Assert | ||
| list.Should().Equal(1, 2, 3, 4, 5, 6); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void AddRange_Empty_Test() | ||
| { | ||
| // Arrange | ||
| var list = new List<int> { 1, 2, 3 }; | ||
| var items = (ReadOnlySpan<int>)[]; | ||
|
|
||
| // Act | ||
| list.AddRange(items); | ||
|
|
||
| // Assert | ||
| list.Should().Equal(1, 2, 3); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void InsertRange_Test() | ||
| { | ||
| // Arrange | ||
| var list = new List<int> { 1, 2, 6 }; | ||
| var items = (ReadOnlySpan<int>)new[] { 3, 4, 5 }; | ||
|
|
||
| // Act | ||
| list.InsertRange(2, items); | ||
|
|
||
| // Assert | ||
| list.Should().Equal(1, 2, 3, 4, 5, 6); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CopyTo_Test() | ||
| { | ||
| // Arrange | ||
| var list = new List<int> { 1, 2, 3 }; | ||
| var destination = new int[5]; | ||
|
|
||
| // Act | ||
| list.CopyTo(destination.AsSpan()); | ||
|
|
||
| // Assert | ||
| destination[0].Should().Be(1); | ||
| destination[1].Should().Be(2); | ||
| destination[2].Should().Be(3); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CopyTo_TooSmall_Test() | ||
| { | ||
| // Arrange | ||
| var list = new List<int> { 1, 2, 3 }; | ||
| var destination = new int[2]; | ||
|
|
||
| // Act | ||
| var act = () => list.CopyTo(destination.AsSpan()); | ||
|
|
||
| // Assert | ||
| act.Should().Throw<ArgumentException>(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| #if (NETCOREAPP && !NET10_0_OR_GREATER) || (NETSTANDARD) || (NETFRAMEWORK) | ||
| #nullable enable | ||
| #pragma warning disable CS0436 | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Collections.ObjectModel; | ||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| #if !POLYSHIM_INCLUDE_COVERAGE | ||
| [ExcludeFromCodeCoverage] | ||
| #endif | ||
| internal static class MemberPolyfills_Net100_CollectionExtensions | ||
| { | ||
| #if !NETFRAMEWORK || NET45_OR_GREATER | ||
| // ReadOnlySet<T> is not available in .NET Framework below 4.5 | ||
| extension<T>(ISet<T> set) | ||
| { | ||
| // https://learn.microsoft.com/dotnet/api/system.collections.generic.collectionextensions.asreadonly#system-collections-generic-collectionextensions-asreadonly-1(system-collections-generic-iset((-0))) | ||
| public ReadOnlySet<T> AsReadOnly() => new(set); | ||
| } | ||
| #endif | ||
| } | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #if (NETCOREAPP && !NET5_0_OR_GREATER) || (NETSTANDARD) || (NETFRAMEWORK) | ||
| #nullable enable | ||
| #pragma warning disable CS0436 | ||
|
|
||
| namespace System.Collections.Generic; | ||
|
|
||
| #if !NETFRAMEWORK || NET45_OR_GREATER | ||
| // IReadOnlyCollection<T> (base interface) is not available in .NET Framework below 4.5 | ||
| // https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlyset-1 | ||
| internal interface IReadOnlySet<T> : IReadOnlyCollection<T> | ||
| { | ||
| bool Contains(T item); | ||
|
|
||
| bool IsProperSubsetOf(IEnumerable<T> other); | ||
|
|
||
| bool IsProperSupersetOf(IEnumerable<T> other); | ||
|
|
||
| bool IsSubsetOf(IEnumerable<T> other); | ||
|
|
||
| bool IsSupersetOf(IEnumerable<T> other); | ||
|
|
||
| bool Overlaps(IEnumerable<T> other); | ||
|
|
||
| bool SetEquals(IEnumerable<T> other); | ||
| } | ||
| #endif | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #if (NETCOREAPP && !NET7_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD) | ||
| #nullable enable | ||
| #pragma warning disable CS0436 | ||
|
|
||
| using System.Collections.ObjectModel; | ||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| namespace System.Collections.Generic; | ||
|
|
||
| #if !POLYSHIM_INCLUDE_COVERAGE | ||
| [ExcludeFromCodeCoverage] | ||
| #endif | ||
| internal static class MemberPolyfills_Net70_CollectionExtensions | ||
| { | ||
| extension<T>(IList<T> list) | ||
| { | ||
| // https://learn.microsoft.com/dotnet/api/system.collections.generic.collectionextensions.asreadonly#system-collections-generic-collectionextensions-asreadonly-1(system-collections-generic-ilist((-0))) | ||
| public ReadOnlyCollection<T> AsReadOnly() => new(list); | ||
| } | ||
|
|
||
| #if !NETFRAMEWORK || NET45_OR_GREATER | ||
| extension<TKey, TValue>(IDictionary<TKey, TValue> dictionary) | ||
| where TKey : notnull | ||
| { | ||
| // https://learn.microsoft.com/dotnet/api/system.collections.generic.collectionextensions.asreadonly#system-collections-generic-collectionextensions-asreadonly-2(system-collections-generic-idictionary((-0-1))) | ||
| public ReadOnlyDictionary<TKey, TValue> AsReadOnly() => new(dictionary); | ||
| } | ||
|
Tyrrrz marked this conversation as resolved.
Tyrrrz marked this conversation as resolved.
|
||
| #endif | ||
| } | ||
| #endif | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| #if (NETCOREAPP && !NET8_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD) | ||
| #nullable enable | ||
| #pragma warning disable CS0436 | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| #if !POLYSHIM_INCLUDE_COVERAGE | ||
| [ExcludeFromCodeCoverage] | ||
| #endif | ||
| internal static class MemberPolyfills_Net80_CollectionExtensions | ||
| { | ||
| extension<T>(List<T> list) | ||
| { | ||
| // https://learn.microsoft.com/dotnet/api/system.collections.generic.collectionextensions.addrange | ||
| public void AddRange(ReadOnlySpan<T> source) | ||
| { | ||
| foreach (var item in source) | ||
| list.Add(item); | ||
| } | ||
|
|
||
| // https://learn.microsoft.com/dotnet/api/system.collections.generic.collectionextensions.insertrange | ||
| public void InsertRange(int index, ReadOnlySpan<T> source) => | ||
| list.InsertRange(index, source.ToArray()); | ||
|
|
||
| // https://learn.microsoft.com/dotnet/api/system.collections.generic.collectionextensions.copyto | ||
| public void CopyTo(Span<T> destination) | ||
| { | ||
| if (destination.Length < list.Count) | ||
| throw new ArgumentException("Destination is too short.", nameof(destination)); | ||
|
|
||
| for (var i = 0; i < list.Count; i++) | ||
| destination[i] = list[i]; | ||
| } | ||
| } | ||
| } | ||
| #endif |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.