Add missing RandomNumberGenerator polyfills for .NET Core 2.0-3.0, .NET 6.0-8.0#77
Add missing RandomNumberGenerator polyfills for .NET Core 2.0-3.0, .NET 6.0-8.0#77
Conversation
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
…to bias rejection logic Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
|
@copilot the TFM boundaries are incorrect, please re-verify. Also add a docs link to the RNG class polyfill, as it's not currently linked in the Signatures list. |
|
@copilot please also add polyfills for RNG:
Ignore any existing polyfills from this list |
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Fixed in bc91cee. Moved |
RandomNumberGenerator polyfills for .NET 6.0-9.0
RandomNumberGenerator polyfills for .NET 6.0-9.0…ZeroBytes, GetItems/Shuffle span overloads Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Added in 67aa26f. Implemented 8 of the 10 requested methods: Added:
Skipped:
All other requested methods ( |
|
@copilot GetBytes may have the same functionality as Fill but it seems both exist, according to Ms docs |
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Added in 7174671. |
| public void GetBytes_Static_Test() | ||
| { | ||
| // Act | ||
| var data = RandomNumberGenerator.GetBytes(16); |
There was a problem hiding this comment.
Should we add a 0-100 loop here too? As well as all random tests. The goal is to reduce non-determinism a bit.
There was a problem hiding this comment.
Fixed in 8d27fa8. Added 0-100 loop to GetBytes_Static_Test and all other random tests (NetCore20, NetCore21) to reduce non-determinism.
|
|
||
| // Assert | ||
| // First 5 bytes should be zero | ||
| for (var i = 0; i < 5; i++) |
There was a problem hiding this comment.
Use Linq in the before/after sections too, for simplicity
There was a problem hiding this comment.
Fixed in 8d27fa8. Replaced for-loops with Linq: data.Take(5) for first 5 bytes and data.Skip(15) for last 5 bytes.
| } | ||
| finally | ||
| { | ||
| // Explicit cast needed for .NET Framework 3.5 where RandomNumberGenerator |
There was a problem hiding this comment.
Fixed in 8d27fa8. The comment explaining the explicit cast for .NET Framework 3.5 has been restored - it was accidentally removed in the previous commit.
…re comment Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
| public void GetBytes_PartialArray_Test() | ||
| { | ||
| // Arrange | ||
| using var rng = RandomNumberGenerator.Create(); |
There was a problem hiding this comment.
Fixed in cb920a6. Added 0-100 loop to GetBytes_PartialArray_Test.
|
|
||
| // Assert | ||
| // First 5 bytes should be zero | ||
| data.Take(5).Should().AllBeEquivalentTo(0); |
There was a problem hiding this comment.
Fixed in cb920a6. Changed to OnlyContain(b => b == 0) which is available in FluentAssertions (AllBe doesn't exist in the version being used).
| rng.GetBytes(data, 5, 10); | ||
|
|
||
| // Assert | ||
| // First 5 bytes should be zero |
There was a problem hiding this comment.
Fixed in cb920a6. Removed descriptive comments from test assertions for cleaner code.
| extension(RandomNumberGenerator) | ||
| { | ||
| // https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.getitems#system-security-cryptography-randomnumbergenerator-getitems-1(system-readonlyspan((-0))-system-int32) | ||
| public static T[] GetItems<T>(ReadOnlySpan<T> choices, int length) |
There was a problem hiding this comment.
Can this reuse the other GetItems method? If so, do it and move it under
There was a problem hiding this comment.
Fixed in cb920a6. GetItems<T>(ReadOnlySpan<T>, int) now reuses GetItems<T>(ReadOnlySpan<T>, Span<T>) by calling it with result.AsSpan(). The Span overload is now placed first.
| // https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.getnonzerobytes#system-security-cryptography-randomnumbergenerator-getnonzerobytes(system-byte()) | ||
| public void GetNonZeroBytes(byte[] data) | ||
| { | ||
| var buffer = new byte[1]; |
There was a problem hiding this comment.
Can't we use GetInt32 with boundaries 0, 255?
There was a problem hiding this comment.
Fixed in cb920a6. GetNonZeroBytes now uses RandomNumberGenerator.GetInt32(1, 256) for much better performance - no more rejection sampling loop or buffer allocation.
| extension(RandomNumberGenerator) | ||
| { | ||
| // https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.getint32#system-security-cryptography-randomnumbergenerator-getint32(system-int32) | ||
| public static int GetInt32(int toExclusive) => |
There was a problem hiding this comment.
Fixed in cb920a6. Moved GetInt32(int toExclusive) below GetInt32(int fromInclusive, int toExclusive) so the main implementation is first.
…reuse GetItems, use GetInt32 for GetNonZeroBytes, reorder methods Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
| using var rng = RandomNumberGenerator.Create(); | ||
| var data = new byte[20]; |
| using var rng = RandomNumberGenerator.Create(); | ||
| var data = new byte[20]; | ||
|
|
||
| rng.GetBytes(data, 5, 10); |
| data.Take(5).Should().OnlyContain(b => b == 0); | ||
| data.Skip(5).Take(10).Should().Contain(b => b != 0); | ||
| data.Skip(15).Should().OnlyContain(b => b == 0); |
| using var rng = RandomNumberGenerator.Create(); | ||
| var data = new byte[10]; | ||
|
|
||
| // Act & Assert |
There was a problem hiding this comment.
Separate act & assert. They are on two separate lines below
| // Act & assert | ||
| for (var i = 0; i < 100; i++) | ||
| { | ||
| var data = RandomNumberGenerator.GetBytes(16); |
| [Fact] | ||
| public void GetBytes_Span_Test() | ||
| { | ||
| // Act & assert |
There was a problem hiding this comment.
Fix the sections, similar to previous comments
There was a problem hiding this comment.
Fixed in 031dc8a. All tests now follow proper AAA pattern with blank lines separating Arrange, Act, and Assert sections.
| [Fact] | ||
| public void GetNonZeroBytes_Span_Test() | ||
| { | ||
| // Act & assert |
| [Fact] | ||
| public void GetInt32_MaxValue_Test() | ||
| { | ||
| // Act & assert |
| { | ||
| for (var i = 0; i < data.Length; i++) | ||
| { | ||
| data[i] = (byte)RandomNumberGenerator.GetInt32(1, 256); |
| // https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.getnonzerobytes#system-security-cryptography-randomnumbergenerator-getnonzerobytes(system-span((system-byte))) | ||
| public void GetNonZeroBytes(Span<byte> data) | ||
| { | ||
| var buffer = new byte[1]; |
There was a problem hiding this comment.
Use the same approach here as in the other overload of this method
…ions, use GetInt32 in GetNonZeroBytes Span 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 #77 +/- ##
============================
============================
☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Add missing polyfills for Random and RandomNumberGenerator ✅
Based on inspection of Microsoft documentation and @Tyrrrz feedback, the following changes were implemented:
RandomNumberGenerator polyfills reorganized by correct TFM:
GetBytes(byte[], int, int)- partial array fill (instance method)GetNonZeroBytes(byte[])- instance method usingGetInt32(1, 256)for efficiencyGetBytes(Span<byte>)- instance method to fill span with random bytesGetNonZeroBytes(Span<byte>)- instance method usingGetInt32(1, 256)for efficiencyFill(Span<byte>)- static method (now uses GetBytes instance method)GetInt32(int, int)- random int with range (static method)GetInt32(int)- random int with max (static method, using arrow syntax) - placed after GetInt32(int, int)GetBytes(int)- static method to create and fill byte arrayGetItems<T>(ReadOnlySpan<T>, Span<T>)- fill span with random items (static method)GetItems<T>(ReadOnlySpan<T>, int)- get random items from span (static method) - reuses the Span overloadShuffle<T>(Span<T>)- shuffle span (static method)GetHexString(int, bool)- generate hex string (static method)GetString(ReadOnlySpan<char>, int)- generate random string (static method)Code Quality Improvements:
GetItems<T>(T[], int)- does not exist in BCLShuffle<T>(T[])- does not exist in BCLFill(Span<byte>)to useGetBytes(Span<byte>)instance methodGetNonZeroBytesby usingGetInt32(1, 256)- no buffer allocation or rejection samplingusingstatements instead of explicit Dispose calls in testsGetInt32methods - two-parameter overload first, single-parameter secondGetItems<T>(ReadOnlySpan<T>, Span<T>)in array-returning overload for code reuseGetInt32(1, 256)in bothGetNonZeroBytesmethods for better performanceOnlyContainassertion instead ofAllBefor better compatibilityTests:
Validation:
Summary
This PR successfully adds 14 missing polyfills to the RandomNumberGenerator class, correctly organized by TFM:
All polyfills follow .NET BCL semantics, use modern C# syntax where possible, maximize code reuse, and are placed in the correct TFM folders. Tests now include 0-100 loops to reduce non-determinism, use Linq consistently for array operations, and follow proper AAA pattern with blank lines separating each section.
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.