Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions PolyShim/Net100/Random.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// ReSharper disable PartialTypeWithSinglePart

using System;
using System.Text;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;

[ExcludeFromCodeCoverage]
Expand All @@ -17,25 +17,39 @@ internal static class MemberPolyfills_Net100_Random
// https://learn.microsoft.com/dotnet/api/system.random.gethexstring#system-random-gethexstring(system-int32-system-boolean)
public string GetHexString(int stringLength, bool lowercase = false)
{
var bytes = new byte[(stringLength + 1) / 2];
random.NextBytes(bytes);
var byteCount = (stringLength + 1) / 2;
var bytes = ArrayPool<byte>.Shared.Rent(byteCount);
try
Comment thread
Tyrrrz marked this conversation as resolved.
{
random.NextBytes(bytes.AsSpan(0, byteCount));

var hex = lowercase
? Convert.ToHexStringLower(bytes, 0, byteCount)
: Convert.ToHexString(bytes, 0, byteCount);

var hex = lowercase ? Convert.ToHexStringLower(bytes) : Convert.ToHexString(bytes);
return hex.Substring(0, stringLength);
return hex.Length == stringLength ? hex : hex.Substring(0, stringLength);
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
}
finally
{
ArrayPool<byte>.Shared.Return(bytes);
}
}

// https://learn.microsoft.com/dotnet/api/system.random.getstring
public string GetString(ReadOnlySpan<char> choices, int length)
{
var buffer = new StringBuilder(length);
var chars = ArrayPool<char>.Shared.Rent(length);
try
{
for (var i = 0; i < length; i++)
chars[i] = choices[random.Next(choices.Length)];

for (var i = 0; i < length; i++)
return new string(chars, 0, length);
}
finally
{
var index = random.Next(choices.Length);
buffer.Append(choices[index]);
ArrayPool<char>.Shared.Return(chars);
}

return buffer.ToString();
}
}
}
Expand Down
38 changes: 26 additions & 12 deletions PolyShim/Net60/Random.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// ReSharper disable PartialTypeWithSinglePart

using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;

file static class RandomEx
Expand All @@ -32,15 +33,22 @@ public long NextInt64(long minValue, long maxValue)

var range = (ulong)(maxValue - minValue);

ulong ulongRand;
do
var buffer = ArrayPool<byte>.Shared.Rent(8);
try
{
var buffer = new byte[8];
random.NextBytes(buffer);
ulongRand = BitConverter.ToUInt64(buffer, 0);
} while (ulongRand > ulong.MaxValue - (ulong.MaxValue % range + 1) % range);
ulong ulongRand;
do
{
random.NextBytes(buffer);
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
ulongRand = BitConverter.ToUInt64(buffer, 0);
} while (ulongRand > ulong.MaxValue - (ulong.MaxValue % range + 1) % range);

return (long)(ulongRand % range) + minValue;
return (long)(ulongRand % range) + minValue;
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}

// https://learn.microsoft.com/dotnet/api/system.random.nextint64#system-random-nextint64(system-int64)
Expand All @@ -52,11 +60,17 @@ public long NextInt64(long minValue, long maxValue)
// https://learn.microsoft.com/dotnet/api/system.random.nextsingle
public float NextSingle()
{
var buffer = new byte[4];
random.NextBytes(buffer);
var uintValue = BitConverter.ToUInt32(buffer, 0);

return (uintValue >> 8) * (1.0f / (1u << 24));
var buffer = ArrayPool<byte>.Shared.Rent(4);
try
{
random.NextBytes(buffer);
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
var uintValue = BitConverter.ToUInt32(buffer, 0);
return (uintValue >> 8) * (1.0f / (1u << 24));
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}

// https://learn.microsoft.com/dotnet/api/system.random.shared
Expand Down
19 changes: 11 additions & 8 deletions PolyShim/Net80/RandomNumberGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

using System;
using System.Buffers;
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;

Expand Down Expand Up @@ -56,7 +55,8 @@ public static string GetHexString(int stringLength, bool lowercase = false)
var hex = lowercase
? Convert.ToHexStringLower(bytes, 0, byteCount)
: Convert.ToHexString(bytes, 0, byteCount);
return hex.Substring(0, stringLength);

return hex.Length == stringLength ? hex : hex.Substring(0, stringLength);
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
}
finally
{
Expand All @@ -67,15 +67,18 @@ public static string GetHexString(int stringLength, bool lowercase = false)
// https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.getstring
public static string GetString(ReadOnlySpan<char> choices, int length)
{
var buffer = new StringBuilder(length);
var chars = ArrayPool<char>.Shared.Rent(length);
try
{
for (var i = 0; i < length; i++)
chars[i] = choices[RandomNumberGenerator.GetInt32(choices.Length)];

for (var i = 0; i < length; i++)
return new string(chars, 0, length);
}
finally
{
var index = RandomNumberGenerator.GetInt32(choices.Length);
buffer.Append(choices[index]);
ArrayPool<char>.Shared.Return(chars, clearArray: true);
}

return buffer.ToString();
}
}
}
Expand Down
17 changes: 14 additions & 3 deletions PolyShim/NetCore21/Random.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// ReSharper disable PartialTypeWithSinglePart

using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;

[ExcludeFromCodeCoverage]
Expand All @@ -16,9 +17,19 @@ internal static class MemberPolyfills_NetCore21_Random
// https://learn.microsoft.com/dotnet/api/system.random.nextbytes#system-random-nextbytes(system-span((system-byte)))
public void NextBytes(Span<byte> buffer)
{
var bufferArray = buffer.ToArray();
random.NextBytes(bufferArray);
bufferArray.CopyTo(buffer);
if (buffer.IsEmpty)
return;

var bufferArray = ArrayPool<byte>.Shared.Rent(buffer.Length);
try
{
random.NextBytes(bufferArray);

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 can populate more than necessary

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 b84a204 — reverted to new byte[buffer.Length] instead of ArrayPool.Rent. Since Random.NextBytes(byte[]) fills the entire array and has no count-limited overload, using a rented buffer (which may be larger) would over-generate. The exact-size allocation avoids this.

bufferArray.AsSpan(0, buffer.Length).CopyTo(buffer);
}
finally
{
ArrayPool<byte>.Shared.Return(bufferArray);
}
}
}
}
Expand Down
31 changes: 17 additions & 14 deletions PolyShim/NetCore21/RandomNumberGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
// ReSharper disable PartialTypeWithSinglePart

using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;

file static class RandomNumberGeneratorEx
{
public static RandomNumberGenerator Instance { get; } = RandomNumberGenerator.Create();
}

[ExcludeFromCodeCoverage]
internal static class MemberPolyfills_NetCore21_RandomNumberGenerator
{
Expand All @@ -20,9 +26,16 @@ public void GetBytes(Span<byte> data)
if (data.Length == 0)
return;

var buffer = new byte[data.Length];
rng.GetBytes(buffer);
buffer.CopyTo(data);
var buffer = ArrayPool<byte>.Shared.Rent(data.Length);
try
{
rng.GetBytes(buffer, 0, data.Length);
buffer.AsSpan(0, data.Length).CopyTo(data);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
}
}

// https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.getnonzerobytes#system-security-cryptography-randomnumbergenerator-getnonzerobytes(system-span((system-byte)))
Expand All @@ -43,17 +56,7 @@ public static void Fill(Span<byte> data)
if (data.Length == 0)
return;

var rng = RandomNumberGenerator.Create();
try
{
rng.GetBytes(data);
}
finally
{
// Explicit cast needed for .NET Framework 3.5 where RandomNumberGenerator
// doesn't properly expose IDisposable for using statements.
((IDisposable)rng).Dispose();
}
RandomNumberGeneratorEx.Instance.GetBytes(data);
}
}
}
Expand Down
24 changes: 15 additions & 9 deletions PolyShim/NetCore30/RandomNumberGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
// ReSharper disable PartialTypeWithSinglePart

using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;

file static class RandomNumberGeneratorEx
{
public static RandomNumberGenerator Instance { get; } = RandomNumberGenerator.Create();
}

[ExcludeFromCodeCoverage]
internal static class MemberPolyfills_NetCore30_RandomNumberGenerator
{
Expand All @@ -18,27 +24,27 @@ internal static class MemberPolyfills_NetCore30_RandomNumberGenerator
public static int GetInt32(int fromInclusive, int toExclusive)
{
var range = (uint)(toExclusive - fromInclusive);
var rng = RandomNumberGenerator.Create();

// Reject values that would cause bias in the distribution.
// This ensures uniform distribution by rejecting values in the
// incomplete final bucket.
var rejectionThreshold = uint.MaxValue - (uint.MaxValue % range + 1) % range;

var buffer = ArrayPool<byte>.Shared.Rent(4);
try
{
// Reject values that would cause bias in the distribution.
// This ensures uniform distribution by rejecting values in the
// incomplete final bucket.
var rejectionThreshold = uint.MaxValue - (uint.MaxValue % range + 1) % range;

uint result;
do
{
var buffer = new byte[4];
rng.GetBytes(buffer);
RandomNumberGeneratorEx.Instance.GetBytes(buffer);
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
result = BitConverter.ToUInt32(buffer, 0);
} while (result > rejectionThreshold);

return (int)(result % range) + fromInclusive;
}
finally
{
((IDisposable)rng).Dispose();
ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
}
}

Expand Down