diff --git a/apiCount.include.md b/apiCount.include.md index aa024ac8..b7cda609 100644 --- a/apiCount.include.md +++ b/apiCount.include.md @@ -1,28 +1,28 @@ -**API count: 956** +**API count: 959** ### Per Target Framework | Target | APIs | | -- | -- | -| `net461` | 932 | -| `net462` | 932 | -| `net47` | 931 | -| `net471` | 930 | -| `net472` | 926 | -| `net48` | 926 | -| `net481` | 926 | -| `netstandard2.0` | 928 | -| `netstandard2.1` | 759 | -| `netcoreapp2.0` | 852 | -| `netcoreapp2.1` | 771 | -| `netcoreapp2.2` | 771 | -| `netcoreapp3.0` | 717 | -| `netcoreapp3.1` | 716 | -| `net5.0` | 588 | -| `net6.0` | 493 | -| `net7.0` | 340 | -| `net8.0` | 222 | -| `net9.0` | 146 | -| `net10.0` | 94 | -| `net11.0` | 57 | -| `uap10.0` | 918 | +| `net461` | 933 | +| `net462` | 933 | +| `net47` | 932 | +| `net471` | 931 | +| `net472` | 927 | +| `net48` | 927 | +| `net481` | 927 | +| `netstandard2.0` | 929 | +| `netstandard2.1` | 760 | +| `netcoreapp2.0` | 853 | +| `netcoreapp2.1` | 772 | +| `netcoreapp2.2` | 772 | +| `netcoreapp3.0` | 718 | +| `netcoreapp3.1` | 717 | +| `net5.0` | 589 | +| `net6.0` | 494 | +| `net7.0` | 341 | +| `net8.0` | 223 | +| `net9.0` | 147 | +| `net10.0` | 95 | +| `net11.0` | 58 | +| `uap10.0` | 919 | diff --git a/api_list.include.md b/api_list.include.md index 0b3be03c..166bd47c 100644 --- a/api_list.include.md +++ b/api_list.include.md @@ -1414,6 +1414,14 @@ * `KeyValuePair Create(TKey, TValue)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.keyvaluepair.create?view=net-11.0) +#### CollectionsMarshal + + * `Span AsSpan(List?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.collectionsmarshal.asspan?view=net-11.0) + * Note: Reads the list's private backing array via reflection on this target. + * `void SetCount(List, int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.collectionsmarshal.setcount?view=net-11.0) + * Note: When growing, new elements are default(T); the BCL exposes uninitialized data. + + #### TaskCompletionSource #### UnreachableException diff --git a/src/ApiBuilderTests/BuildApiTest.cs b/src/ApiBuilderTests/BuildApiTest.cs index c5510dc2..4cda17a5 100644 --- a/src/ApiBuilderTests/BuildApiTest.cs +++ b/src/ApiBuilderTests/BuildApiTest.cs @@ -18,6 +18,7 @@ public Task RunWithRoslyn() WriteHelper("Ensure*", writer, ref count); WriteHelper("Lock", writer, ref count); WriteHelper(nameof(KeyValuePair), writer, ref count); + WriteHelper("CollectionsMarshal", writer, ref count); WriteType(nameof(TaskCompletionSource), writer, ref count); WriteType(nameof(UnreachableException), writer, ref count); diff --git a/src/Consume/Consume.cs b/src/Consume/Consume.cs index d839bc64..356a32f0 100644 --- a/src/Consume/Consume.cs +++ b/src/Consume/Consume.cs @@ -128,6 +128,12 @@ class Consume type = typeof(MatchType); type = typeof(MatchCasing); + var collectionsMarshalList = new List {1, 2, 3}; + CollectionsMarshal.SetCount(collectionsMarshalList, 5); +#if FeatureMemory && !WINDOWS_UWP + var collectionsMarshalSpan = CollectionsMarshal.AsSpan(collectionsMarshalList); +#endif + var (key, value) = KeyValuePair.Create("a", "b"); #if NET6_0_OR_GREATER diff --git a/src/Polyfill/CollectionsMarshal.cs b/src/Polyfill/CollectionsMarshal.cs new file mode 100644 index 00000000..f61ea3c6 --- /dev/null +++ b/src/Polyfill/CollectionsMarshal.cs @@ -0,0 +1,91 @@ +#pragma warning disable + +#if !NET5_0_OR_GREATER + +namespace System.Runtime.InteropServices; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory && !WINDOWS_UWP + + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.collectionsmarshal.asspan?view=net-11.0 + //Note: Reads the list's private backing array via reflection on this target. + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } + +#endif + + /// + /// Sets the count of the to the specified value. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.collectionsmarshal.setcount?view=net-11.0 + //Note: When growing, new elements are default(T); the BCL exposes uninitialized data. + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} + +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] +#endif diff --git a/src/Polyfill/CollectionsMarshalSetCount.cs b/src/Polyfill/CollectionsMarshalSetCount.cs new file mode 100644 index 00000000..15048601 --- /dev/null +++ b/src/Polyfill/CollectionsMarshalSetCount.cs @@ -0,0 +1,57 @@ +#pragma warning disable + +// SetCount was added to the BCL CollectionsMarshal in net8.0, but the type itself +// exists from net5.0. For net5.0-net7.0 the type is present (so it cannot be +// recreated) and SetCount is added as a static extension member. For earlier +// targets SetCount lives on the recreated type in CollectionsMarshal.cs. +#if NET5_0_OR_GREATER && !NET8_0_OR_GREATER + +namespace Polyfills; + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +static partial class Polyfill +{ + extension(CollectionsMarshal) + { + /// + /// Sets the count of the to the specified value. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.collectionsmarshal.setcount?view=net-11.0 + //Note: When growing, new elements are default(T); the BCL exposes uninitialized data. + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } + } +} + +#endif diff --git a/src/Split/net10.0/TypeForwardeds.cs b/src/Split/net10.0/TypeForwardeds.cs index 88bafe15..dc347bc9 100644 --- a/src/Split/net10.0/TypeForwardeds.cs +++ b/src/Split/net10.0/TypeForwardeds.cs @@ -4,6 +4,7 @@ [assembly: TypeForwardedTo(typeof(System.Buffers.Text.Base64Url))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CollectionBuilderAttribute))] +[assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] diff --git a/src/Split/net11.0/TypeForwardeds.cs b/src/Split/net11.0/TypeForwardeds.cs index 6c10ba66..296cf844 100644 --- a/src/Split/net11.0/TypeForwardeds.cs +++ b/src/Split/net11.0/TypeForwardeds.cs @@ -4,6 +4,7 @@ [assembly: TypeForwardedTo(typeof(System.Buffers.Text.Base64Url))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CollectionBuilderAttribute))] +[assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] diff --git a/src/Split/net461/CollectionsMarshal.cs b/src/Split/net461/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/net461/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/net462/CollectionsMarshal.cs b/src/Split/net462/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/net462/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/net47/CollectionsMarshal.cs b/src/Split/net47/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/net47/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/net471/CollectionsMarshal.cs b/src/Split/net471/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/net471/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/net472/CollectionsMarshal.cs b/src/Split/net472/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/net472/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/net48/CollectionsMarshal.cs b/src/Split/net48/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/net48/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/net481/CollectionsMarshal.cs b/src/Split/net481/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/net481/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/net5.0/CollectionsMarshalSetCount.cs b/src/Split/net5.0/CollectionsMarshalSetCount.cs new file mode 100644 index 00000000..88d37845 --- /dev/null +++ b/src/Split/net5.0/CollectionsMarshalSetCount.cs @@ -0,0 +1,43 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +static partial class Polyfill +{ + extension(CollectionsMarshal) + { + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } + } +} diff --git a/src/Split/net5.0/TypeForwardeds.cs b/src/Split/net5.0/TypeForwardeds.cs index 3a1e4f27..d28d5c38 100644 --- a/src/Split/net5.0/TypeForwardeds.cs +++ b/src/Split/net5.0/TypeForwardeds.cs @@ -2,6 +2,7 @@ #pragma warning disable using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] +[assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] diff --git a/src/Split/net6.0/CollectionsMarshalSetCount.cs b/src/Split/net6.0/CollectionsMarshalSetCount.cs new file mode 100644 index 00000000..88d37845 --- /dev/null +++ b/src/Split/net6.0/CollectionsMarshalSetCount.cs @@ -0,0 +1,43 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +static partial class Polyfill +{ + extension(CollectionsMarshal) + { + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } + } +} diff --git a/src/Split/net6.0/TypeForwardeds.cs b/src/Split/net6.0/TypeForwardeds.cs index 9dc22686..00408b26 100644 --- a/src/Split/net6.0/TypeForwardeds.cs +++ b/src/Split/net6.0/TypeForwardeds.cs @@ -2,6 +2,7 @@ #pragma warning disable using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] +[assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] diff --git a/src/Split/net7.0/CollectionsMarshalSetCount.cs b/src/Split/net7.0/CollectionsMarshalSetCount.cs new file mode 100644 index 00000000..88d37845 --- /dev/null +++ b/src/Split/net7.0/CollectionsMarshalSetCount.cs @@ -0,0 +1,43 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +static partial class Polyfill +{ + extension(CollectionsMarshal) + { + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } + } +} diff --git a/src/Split/net7.0/TypeForwardeds.cs b/src/Split/net7.0/TypeForwardeds.cs index 4deb010f..acbb458e 100644 --- a/src/Split/net7.0/TypeForwardeds.cs +++ b/src/Split/net7.0/TypeForwardeds.cs @@ -2,6 +2,7 @@ #pragma warning disable using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] +[assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] diff --git a/src/Split/net8.0/TypeForwardeds.cs b/src/Split/net8.0/TypeForwardeds.cs index 1f278cd1..d3b8dcb1 100644 --- a/src/Split/net8.0/TypeForwardeds.cs +++ b/src/Split/net8.0/TypeForwardeds.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CollectionBuilderAttribute))] +[assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] diff --git a/src/Split/net9.0/TypeForwardeds.cs b/src/Split/net9.0/TypeForwardeds.cs index 88bafe15..dc347bc9 100644 --- a/src/Split/net9.0/TypeForwardeds.cs +++ b/src/Split/net9.0/TypeForwardeds.cs @@ -4,6 +4,7 @@ [assembly: TypeForwardedTo(typeof(System.Buffers.Text.Base64Url))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CollectionBuilderAttribute))] +[assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] diff --git a/src/Split/netcoreapp2.0/CollectionsMarshal.cs b/src/Split/netcoreapp2.0/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/netcoreapp2.0/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/netcoreapp2.1/CollectionsMarshal.cs b/src/Split/netcoreapp2.1/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/netcoreapp2.1/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/netcoreapp2.2/CollectionsMarshal.cs b/src/Split/netcoreapp2.2/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/netcoreapp2.2/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/netcoreapp3.0/CollectionsMarshal.cs b/src/Split/netcoreapp3.0/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/netcoreapp3.0/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/netcoreapp3.1/CollectionsMarshal.cs b/src/Split/netcoreapp3.1/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/netcoreapp3.1/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/netstandard2.0/CollectionsMarshal.cs b/src/Split/netstandard2.0/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/netstandard2.0/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/netstandard2.1/CollectionsMarshal.cs b/src/Split/netstandard2.1/CollectionsMarshal.cs new file mode 100644 index 00000000..b9d2378e --- /dev/null +++ b/src/Split/netstandard2.1/CollectionsMarshal.cs @@ -0,0 +1,72 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ +#if FeatureMemory + /// + /// Gets a view over the data in a list. Items should not be added or removed from the while the is in use. + /// + public static Span AsSpan(List? list) + { + if (list == null) + { + return default; + } + var items = (T[]) ItemsAccessor.Field.GetValue(list)!; + return new(items, 0, list.Count); + } + static class ItemsAccessor + { + public static readonly FieldInfo Field = typeof(List) + .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!; + } +#endif + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Split/uap10.0/CollectionsMarshal.cs b/src/Split/uap10.0/CollectionsMarshal.cs new file mode 100644 index 00000000..06555a18 --- /dev/null +++ b/src/Split/uap10.0/CollectionsMarshal.cs @@ -0,0 +1,53 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +/// +/// An unsafe class that provides a set of methods to access the underlying data representations of collections. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CollectionsMarshal +{ + /// + /// Sets the count of the to the specified value. + /// + public static void SetCount(List list, int count) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + var current = list.Count; + if (count < current) + { + list.RemoveRange(count, current - count); + } + else if (count > current) + { + if (list.Capacity < count) + { + list.Capacity = count; + } + for (var index = current; index < count; index++) + { + list.Add(default!); + } + } + } +} diff --git a/src/Tests/PolyfillTests_CollectionsMarshal.cs b/src/Tests/PolyfillTests_CollectionsMarshal.cs new file mode 100644 index 00000000..7a2a8213 --- /dev/null +++ b/src/Tests/PolyfillTests_CollectionsMarshal.cs @@ -0,0 +1,67 @@ +partial class PolyfillTests +{ +#if FeatureMemory && !WINDOWS_UWP + + [Test] + public async Task CollectionsMarshal_AsSpan() + { + var list = new List {1, 2, 3}; + var span = CollectionsMarshal.AsSpan(list); + var length = span.Length; + var first = span[0]; + var third = span[2]; + + // Writing through the span must mutate the backing list. + span[1] = 20; + + await Assert.That(length).IsEqualTo(3); + await Assert.That(first).IsEqualTo(1); + await Assert.That(third).IsEqualTo(3); + await Assert.That(list[1]).IsEqualTo(20); + } + + [Test] + public async Task CollectionsMarshal_AsSpan_Empty() + { + var length = CollectionsMarshal.AsSpan(new List()).Length; + await Assert.That(length).IsEqualTo(0); + } + + [Test] + public async Task CollectionsMarshal_AsSpan_Null() + { + var length = CollectionsMarshal.AsSpan(null).Length; + await Assert.That(length).IsEqualTo(0); + } + +#endif + + [Test] + public async Task CollectionsMarshal_SetCount_Grow() + { + var list = new List {1, 2}; + CollectionsMarshal.SetCount(list, 4); + await Assert.That(list.Count).IsEqualTo(4); + await Assert.That(list[0]).IsEqualTo(1); + await Assert.That(list[1]).IsEqualTo(2); + } + + [Test] + public async Task CollectionsMarshal_SetCount_Shrink() + { + var list = new List {1, 2, 3, 4}; + CollectionsMarshal.SetCount(list, 2); + await Assert.That(list.Count).IsEqualTo(2); + await Assert.That(list[0]).IsEqualTo(1); + await Assert.That(list[1]).IsEqualTo(2); + } + + [Test] + public async Task CollectionsMarshal_SetCount_Same() + { + var list = new List {1, 2, 3}; + CollectionsMarshal.SetCount(list, 3); + await Assert.That(list.Count).IsEqualTo(3); + await Assert.That(list[2]).IsEqualTo(3); + } +}