Add polyfills for RandomNumberGenerator class and Fill(Span<byte>) method#76
Add polyfills for RandomNumberGenerator class and Fill(Span<byte>) method#76
RandomNumberGenerator class and Fill(Span<byte>) method#76Conversation
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
…empty span test Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
…rtion Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
| // Verify that not all bytes are zero (cryptographic RNG should produce non-zero data) | ||
| // Note: Theoretically, a crypto RNG could produce all zeros (probability: 2^-128), | ||
| // but this is astronomically unlikely in practice |
| // Verify that not all bytes are zero (cryptographic RNG should produce non-zero data) | ||
| // Note: Theoretically, a crypto RNG could produce all zeros (probability: 2^-128), | ||
| // but this is astronomically unlikely in practice | ||
| buffer.Should().NotBeEquivalentTo(new byte[16]); |
There was a problem hiding this comment.
I think it's cleaner to check directly for zeroes
There was a problem hiding this comment.
Changed to span.ToArray().Should().Contain(b => b != 0) in 981b9ca
| var buffer = new byte[16]; | ||
| var span = new Span<byte>(buffer); |
There was a problem hiding this comment.
First expression can be inlined into the second since we don't use the buffer array separately
| { | ||
| // Arrange | ||
| var buffer = new byte[0]; | ||
| var span = new Span<byte>(buffer); |
| @@ -0,0 +1,33 @@ | |||
| #if (NETCOREAPP && !NETCOREAPP2_1_OR_GREATER) || (NETFRAMEWORK && NET40_OR_GREATER) || (NETSTANDARD && NETSTANDARD1_3_OR_GREATER && !NETSTANDARD2_1_OR_GREATER) | |||
There was a problem hiding this comment.
Why the .net std 1.3 limitation? If it's because the class itself doesn't exist, we can create it.
There was a problem hiding this comment.
Removed the NETSTANDARD1_3 limitation and used nested #if !NETSTANDARD || NETSTANDARD1_3_OR_GREATER to conditionally include when the class exists, matching the pattern in Path.cs. In 981b9ca
| return; | ||
|
|
||
| var buffer = new byte[data.Length]; | ||
| using (var rng = RandomNumberGenerator.Create()) |
There was a problem hiding this comment.
Why not an implicit scoped using here?
There was a problem hiding this comment.
Changed to try-finally with explicit Dispose() for compatibility with net35 where RandomNumberGenerator doesn't work properly with using statements. In 981b9ca
…ions, use try-finally for disposal Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
|
|
||
| var buffer = new byte[data.Length]; | ||
| var rng = RandomNumberGenerator.Create(); | ||
| try |
There was a problem hiding this comment.
Oh I meant a using statement that ends with a semicolon and doesn't use curly braces.
There was a problem hiding this comment.
Changed to using var scoped declaration with conditional compilation to handle net35 compatibility in 1bf1f0c
There was a problem hiding this comment.
Why the net35 exclusion? It should work the same everywhere @copilot
There was a problem hiding this comment.
Removed the NET35 conditional by explicitly casting to IDisposable in the using statement, which works across all frameworks in 5c023b4
… for net35 Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a polyfill for RandomNumberGenerator.Fill(Span<byte>) to provide compatibility with .NET Framework and older .NET Standard versions. The implementation creates a temporary byte array, fills it with random data using the existing GetBytes method, and copies the result to the target span.
Changes:
- Added
RandomNumberGenerator.Fill(Span<byte>)polyfill with proper compilation guards and platform-specific disposal patterns - Updated signature documentation to reflect the new member
- Added comprehensive tests for both non-empty and empty span scenarios
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| PolyShim/Signatures.md | Updated totals and added documentation entry for the new Fill method |
| PolyShim/NetCore21/RandomNumberGenerator.cs | Implemented the Fill polyfill with empty span fast-path and NET35-compatible disposal |
| PolyShim.Tests/NetCore21/RandomNumberGeneratorTests.cs | Added tests validating Fill behavior for both empty and non-empty spans |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,46 @@ | |||
| #if (NETCOREAPP && !NETCOREAPP2_1_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD && !NETSTANDARD2_1_OR_GREATER) | |||
There was a problem hiding this comment.
How do we get away with these boundaries if the class doesn't exist on some older targets?
There was a problem hiding this comment.
The nested #if !NETSTANDARD || NETSTANDARD1_3_OR_GREATER on line 10 excludes the entire class definition for netstandard < 1.3 where RandomNumberGenerator doesn't exist. For those targets, this file compiles to empty content, so the polyfill simply isn't available (which is correct since both the class and Span don't exist there).
There was a problem hiding this comment.
We provide Span for all targets, even net std 1.0. can we just create a polyfill for the Random number generator class for net std <1.3? It can just contain the bare minimum required functionality to work with our Fill method.
There was a problem hiding this comment.
Added RandomNumberGenerator polyfill for netstandard < 1.3 that wraps RNGCryptoServiceProvider using reflection in 0d9a36a
RandomNumberGenerator.Fill(Span<byte>)
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
| namespace System.Security.Cryptography; | ||
|
|
||
| [ExcludeFromCodeCoverage] | ||
| internal abstract class RandomNumberGenerator : IDisposable |
There was a problem hiding this comment.
Move this into its own appropriate file. This class wasn't introduced in net core 2.1.
There was a problem hiding this comment.
Moved to PolyShim/NetCore10/RandomNumberGenerator.cs in 1207292
|
|
||
| protected virtual void Dispose(bool disposing) { } | ||
|
|
||
| private sealed class RNGCryptoServiceProviderWrapper : RandomNumberGenerator |
There was a problem hiding this comment.
Don't use reflection. If there's no way to recreate the functionality of RNGCryptoServiceProvider, just wrap Random
| } | ||
| finally | ||
| { | ||
| ((IDisposable)rng).Dispose(); |
There was a problem hiding this comment.
Explain in a comment why this cast is necessary
There was a problem hiding this comment.
Added comment explaining it's needed for .NET Framework 3.5 compatibility in 1207292
…apper, add IDisposable cast comment Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
|
|
||
| public override void GetBytes(byte[] data) | ||
| { | ||
| _random.NextBytes(data); |
| GC.SuppressFinalize(this); | ||
| } | ||
|
|
||
| protected virtual void Dispose(bool disposing) { } |
There was a problem hiding this comment.
Move this above the other Dispose
PolyShim/Signatures.md
Outdated
|
|
||
| - **Total:** 340 | ||
| - **Total:** 341 | ||
| - **Types:** 74 |
There was a problem hiding this comment.
The RandomNumberGenerator class polyfill is only active for netstandard < 1.3 (conditional compilation), so the signature generation script doesn't detect it when running on netstandard2.0 target where it generates the Signatures.md file.
There was a problem hiding this comment.
That makes no sense. The script scans files as text it doesn't take conditional compilation into account. Are you sure you re-ran the build? Please investigate
There was a problem hiding this comment.
That makes no sense. The script scans files as text it doesn't take conditional compilation into account. Are you sure you re-ran the build? Please investigate
There was a problem hiding this comment.
You were right - the script does scan as text. The issue was the regex pattern internal\s+(?:(?:readonly|partial|static|sealed|ref)\s+)*(class|...) was missing abstract as an optional modifier. Fixed in a0ca669. The Types count now correctly shows 76 (up from 74) - it detected both RandomNumberGenerator and OSPlatformAttribute which are both abstract classes.
There was a problem hiding this comment.
You were right - the script does scan as text. The issue was the regex pattern was missing abstract as an optional modifier. Fixed in a0ca669. Types count increased from 74 to 76 (RandomNumberGenerator and OSPlatformAttribute).
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
| finally | ||
| { | ||
| // Explicit cast needed for .NET Framework 3.5 where RandomNumberGenerator | ||
| // doesn't properly expose IDisposable for using statements |
| if (data.Length == 0) | ||
| return; | ||
|
|
||
| var buffer = new byte[data.Length]; |
PolyShim/Signatures.md
Outdated
|
|
||
| - **Total:** 340 | ||
| - **Total:** 341 | ||
| - **Types:** 74 |
There was a problem hiding this comment.
That makes no sense. The script scans files as text it doesn't take conditional compilation into account. Are you sure you re-ran the build? Please investigate
PolyShim/Signatures.md
Outdated
|
|
||
| - **Total:** 340 | ||
| - **Total:** 341 | ||
| - **Types:** 74 |
There was a problem hiding this comment.
That makes no sense. The script scans files as text it doesn't take conditional compilation into account. Are you sure you re-ran the build? Please investigate
…re script to detect abstract classes Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #76 +/- ##
============================
============================
☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Implementation Details
This PR adds polyfills to enable
RandomNumberGenerator.Fill(Span<byte>)functionality across all target frameworks, including older .NET Standard versions where theRandomNumberGeneratorclass doesn't exist.RandomNumberGenerator Class Polyfill (NetCore10)
For .NET Standard < 1.3, a complete polyfill of the
RandomNumberGeneratorabstract class is provided inPolyShim/NetCore10/RandomNumberGenerator.csthat:Randomfor compatibility (not cryptographically secure on these platforms as System.Security.Cryptography types are not available)IDisposablepatternFill Method Polyfill (NetCore21)
The
Fill(Span<byte>)method polyfill inPolyShim/NetCore21/RandomNumberGenerator.cs:GetBytesmethodSignature Generation Fix
Fixed the signature generation script (
List-Signatures.ps1) to includeabstractas an optional class modifier in the regex pattern. This bug was preventing abstract classes from being detected, which affected bothRandomNumberGeneratorandOSPlatformAttribute. The Types count inSignatures.mdnow correctly reflects 76 types (increased from 74).This enables modern Span-based random number generation APIs on all .NET Standard versions, even where the infrastructure didn't originally exist. Note that on .NET Standard < 1.3, the random number generation uses
Randomrather than cryptographically secure methods due to platform limitations.Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.