Skip to content

Add missing RandomNumberGenerator polyfills for .NET Core 2.0-3.0, .NET 6.0-8.0#77

Merged
Tyrrrz merged 13 commits intomasterfrom
copilot/add-polyfills-rng-random
Feb 17, 2026
Merged

Add missing RandomNumberGenerator polyfills for .NET Core 2.0-3.0, .NET 6.0-8.0#77
Tyrrrz merged 13 commits intomasterfrom
copilot/add-polyfills-rng-random

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 17, 2026

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:

  • NetCore20 (.NET Core 2.0, .NET Standard 2.0, .NET Framework 4.6):
    • GetBytes(byte[], int, int) - partial array fill (instance method)
    • GetNonZeroBytes(byte[]) - instance method using GetInt32(1, 256) for efficiency
  • NetCore21 (.NET Core 2.1, .NET Standard 2.1):
    • GetBytes(Span<byte>) - instance method to fill span with random bytes
    • GetNonZeroBytes(Span<byte>) - instance method using GetInt32(1, 256) for efficiency
    • Fill(Span<byte>) - static method (now uses GetBytes instance method)
  • NetCore30 (.NET Core 3.0, .NET Standard 2.1):
    • 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)
  • Net60 (.NET 6.0):
    • GetBytes(int) - static method to create and fill byte array
  • Net80 (.NET 8.0):
    • GetItems<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 overload
    • Shuffle<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:

  • Removed all validation guards from polyfills (per repository policy)
  • Removed GetItems<T>(T[], int) - does not exist in BCL
  • Removed Shuffle<T>(T[]) - does not exist in BCL
  • Updated Fill(Span<byte>) to use GetBytes(Span<byte>) instance method
  • Moved methods to correct TFM folders based on when they were introduced
  • Reduced GC pressure in GetNonZeroBytes by using GetInt32(1, 256) - no buffer allocation or rejection sampling
  • Simplified test names - removed parameter types for methods with single overload
  • Used Linq Skip/Take instead of Array.Copy in tests
  • Used using statements instead of explicit Dispose calls in tests
  • Used arrow methods (expression-bodied members) where applicable
  • Added 0-100 loops to all random tests to reduce non-determinism
  • Used Linq consistently for all array range checks (Take/Skip)
  • Restored .NET Framework 3.5 comment explaining explicit cast
  • Reordered GetInt32 methods - two-parameter overload first, single-parameter second
  • Reused GetItems<T>(ReadOnlySpan<T>, Span<T>) in array-returning overload for code reuse
  • Used GetInt32(1, 256) in both GetNonZeroBytes methods for better performance
  • Removed unnecessary comments from tests for cleaner code
  • Used OnlyContain assertion instead of AllBe for better compatibility
  • Fixed test AAA pattern - added proper blank lines between Arrange, Act, and Assert sections in all tests

Tests:

  • Add tests for NetCore20/RandomNumberGenerator (3 tests with loops and proper AAA)
  • Add tests for NetCore21/RandomNumberGenerator (5 tests with loops and proper AAA)
  • Add tests for NetCore30/RandomNumberGenerator (2 tests with loops and proper AAA)
  • Add tests for Net60/RandomNumberGenerator (1 test with loop and proper AAA)
  • Add tests for Net80/RandomNumberGenerator (5 tests with loops and proper AAA)

Validation:

  • Build project (successful, no warnings)
  • Run RandomNumberGenerator tests (16 tests passed with loops)

Summary

This PR successfully adds 14 missing polyfills to the RandomNumberGenerator class, correctly organized by TFM:

  • Byte generation methods (GetBytes with various overloads, GetNonZeroBytes with optimized implementation using GetInt32)
  • Integer generation methods (GetInt32 overloads properly ordered)
  • Collection manipulation methods (GetItems with code reuse, Shuffle with Span support only)
  • String generation methods (GetHexString, GetString)

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

Inspect polyfills for RandomNumberGenerator and Random. Some functionality exposed by Random should also be replicated by RNG, and vice versa. Consult with MS docs to figure out which members are missing and add polyfills for them.


💡 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.

@Tyrrrz Tyrrrz added the enhancement New feature or request label Feb 17, 2026
Copilot AI and others added 3 commits February 17, 2026 16:27
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 AI changed the title [WIP] Add missing polyfills for RandomNumberGenerator and Random Add missing RandomNumberGenerator polyfills for .NET 6.0-9.0 Feb 17, 2026
Copilot AI requested a review from Tyrrrz February 17, 2026 16:32
@Tyrrrz
Copy link
Copy Markdown
Owner

Tyrrrz commented Feb 17, 2026

@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.

@Tyrrrz
Copy link
Copy Markdown
Owner

Tyrrrz commented Feb 17, 2026

@copilot please also add polyfills for RNG:

  • GetBytes(Int32)
  • GetBytes(Span)
  • GetHexString(Int32, Boolean)
  • GetHexString(Span, Boolean)
  • GetItems(ReadOnlySpan, Int32)
  • GetItems(ReadOnlySpan, Span)
  • GetNonZeroBytes(Byte[])
  • GetNonZeroBytes(Span)
  • GetString(ReadOnlySpan, Int32)
  • Shuffle(Span)

Ignore any existing polyfills from this list

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 17, 2026

@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.

Fixed in bc91cee. Moved GetHexString and GetString from Net90 to Net80 (they were introduced in .NET 8.0, not 9.0). The RandomNumberGenerator class polyfill documentation link is already present in NetCore10/RandomNumberGenerator.cs and appears in Signatures.md.

@Tyrrrz Tyrrrz changed the title Add missing RandomNumberGenerator polyfills for .NET 6.0-9.0 Add missing RandomNumberGenerator polyfills for .NET 6.0-9.0 Feb 17, 2026
Copilot AI changed the title Add missing RandomNumberGenerator polyfills for .NET 6.0-9.0 Add missing RandomNumberGenerator polyfills for .NET 6.0-8.0 Feb 17, 2026
…ZeroBytes, GetItems/Shuffle span overloads

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
@Tyrrrz Tyrrrz marked this pull request as ready for review February 17, 2026 16:46
Copilot AI review requested due to automatic review settings February 17, 2026 16:46
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 17, 2026

@copilot please also add polyfills for RNG:

  • GetBytes(Int32)
  • GetBytes(Span)...

Added in 67aa26f. Implemented 8 of the 10 requested methods:

Added:

  • GetBytes(int) - .NET 6.0
  • GetNonZeroBytes(byte[]) and GetNonZeroBytes(Span<byte>) - .NET 6.0
  • GetItems<T>(ReadOnlySpan<T>, int) and GetItems<T>(ReadOnlySpan<T>, Span<T>) - .NET 8.0
  • Shuffle<T>(Span<T>) - .NET 8.0

Skipped:

  • GetBytes(Span<byte>) - Already exists as Fill(Span<byte>) in NetCore21 (same functionality)
  • GetHexString(Span<byte>, bool) - This API doesn't exist in .NET 10.0, likely a future version

All other requested methods (GetHexString(int, bool), GetString(ReadOnlySpan<char>, int)) were already implemented.

@Tyrrrz
Copy link
Copy Markdown
Owner

Tyrrrz commented Feb 17, 2026

@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>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 17, 2026

@copilot GetBytes may have the same functionality as Fill but it seems both exist, according to Ms docs

Added in 7174671. GetBytes(Span<byte>) is now implemented as an instance method in NetCore21, alongside the existing Fill(Span<byte>) static method. Both methods exist in the .NET BCL - Fill is static while GetBytes is an instance method on the RNG object.

Copy link
Copy Markdown
Owner

@Tyrrrz Tyrrrz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public void GetBytes_Static_Test()
{
// Act
var data = RandomNumberGenerator.GetBytes(16);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a 0-100 loop here too? As well as all random tests. The goal is to reduce non-determinism a bit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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++)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Linq in the before/after sections too, for simplicity

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this comment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copilot AI requested a review from Tyrrrz February 17, 2026 17:40
Copy link
Copy Markdown
Owner

@Tyrrrz Tyrrrz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public void GetBytes_PartialArray_Test()
{
// Arrange
using var rng = RandomNumberGenerator.Create();
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where loop

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in cb920a6. Added 0-100 loop to GetBytes_PartialArray_Test.


// Assert
// First 5 bytes should be zero
data.Take(5).Should().AllBeEquivalentTo(0);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not AllBe(0)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments can be dropped

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this reuse the other GetItems method? If so, do it and move it under

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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];
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use GetInt32 with boundaries 0, 255?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) =>
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this under the other method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copilot AI requested a review from Tyrrrz February 17, 2026 17:52
Copy link
Copy Markdown
Owner

@Tyrrrz Tyrrrz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +17 to +18
using var rng = RandomNumberGenerator.Create();
var data = new byte[20];
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is arrange

using var rng = RandomNumberGenerator.Create();
var data = new byte[20];

rng.GetBytes(data, 5, 10);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is act

Comment on lines +22 to +24
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);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is assert

using var rng = RandomNumberGenerator.Create();
var data = new byte[10];

// Act & Assert
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate act & assert. They are on two separate lines below

// Act & assert
for (var i = 0; i < 100; i++)
{
var data = RandomNumberGenerator.GetBytes(16);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is act

[Fact]
public void GetBytes_Span_Test()
{
// Act & assert
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the sections, similar to previous comments

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

[Fact]
public void GetInt32_MaxValue_Test()
{
// Act & assert
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here and in other tests

{
for (var i = 0; i < data.Length; i++)
{
data[i] = (byte)RandomNumberGenerator.GetInt32(1, 256);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Genius

// 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];
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copilot AI requested a review from Tyrrrz February 17, 2026 18:05
@Tyrrrz Tyrrrz merged commit 7f86257 into master Feb 17, 2026
5 checks passed
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (66a278d) to head (031dc8a).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@     Coverage Diff      @@
##   master   #77   +/-   ##
============================
============================

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants