diff --git a/PolyShim.Tests/Net100/RandomTests.cs b/PolyShim.Tests/Net100/RandomTests.cs new file mode 100644 index 0000000..b486d20 --- /dev/null +++ b/PolyShim.Tests/Net100/RandomTests.cs @@ -0,0 +1,68 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace PolyShim.Tests.Net100; + +public class RandomTests +{ + [Fact] + public void GetHexString_Test() + { + // Arrange + var random = new Random(0); + + // Act & assert + for (var i = 0; i < 100; i++) + { + random.GetHexString(16).Should().MatchRegex("^[0-9A-F]{16}$"); + random.GetHexString(16, true).Should().MatchRegex("^[0-9a-f]{16}$"); + random.GetHexString(15).Should().MatchRegex("^[0-9A-F]{15}$"); + } + } + + [Fact] + public void GetString_Array_Test() + { + // Arrange + var random = new Random(0); + var choices = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".ToCharArray(); + + // Act + for (var i = 0; i < 100; i++) + { + var str = random.GetString(choices, 16); + + // Assert + str.Length.Should().Be(16); + + foreach (var ch in str) + { + choices.Should().Contain(ch); + } + } + } + + [Fact] + public void GetString_Span_Test() + { + // Arrange + var random = new Random(0); + var choices = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".AsSpan(); + + // Act + for (var i = 0; i < 100; i++) + { + var str = random.GetString(choices, 16); + + // Assert + str.Length.Should().Be(16); + + foreach (var ch in str) + { + choices.ToArray().Should().Contain(ch); + } + } + } +} diff --git a/PolyShim.Tests/Net60/RandomTests.cs b/PolyShim.Tests/Net60/RandomTests.cs index ac3de34..e45e5a5 100644 --- a/PolyShim.Tests/Net60/RandomTests.cs +++ b/PolyShim.Tests/Net60/RandomTests.cs @@ -9,10 +9,30 @@ public class RandomTests [Fact] public void Shared_Test() { - // Act - var value = Random.Shared.Next(1, 100); + // Act & assert + Random.Shared.Should().NotBeNull(); + Random.Shared.Next(1, 100).Should().BeInRange(1, 99); + } - // Assert - value.Should().BeInRange(1, 100); + [Fact] + public void NextInt64_Test() + { + // Act & assert + for (var i = 0; i < 100; i++) + { + Random.Shared.NextInt64(10, 20).Should().BeInRange(10, 19); + Random.Shared.NextInt64(20).Should().BeInRange(0, 19); + Random.Shared.NextInt64().Should().BeInRange(0, long.MaxValue - 1); + } + } + + [Fact] + public void NextSingle_Test() + { + // Act & assert + for (var i = 0; i < 100; i++) + { + Random.Shared.NextSingle().Should().BeInRange(0f, 1f); + } } } diff --git a/PolyShim.Tests/Net80/RandomTests.cs b/PolyShim.Tests/Net80/RandomTests.cs new file mode 100644 index 0000000..c4e93a0 --- /dev/null +++ b/PolyShim.Tests/Net80/RandomTests.cs @@ -0,0 +1,54 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace PolyShim.Tests.Net80; + +public class RandomTests +{ + [Fact] + public void GetItems_Test() + { + // Arrange + var random = new Random(0); + var choices = new[] { 1, 2, 3, 4, 5 }; + + // Act + for (var i = 0; i < 100; i++) + { + var items = random.GetItems(choices, 3); + + // Assert + items.Should().HaveCount(3); + + foreach (var item in items) + { + choices.Should().Contain(item); + } + } + } + + [Fact] + public void Shuffle_Test() + { + // Arrange + var random = new Random(0); + var originalItems = new[] { 1, 2, 3, 4, 5 }; + + // Act + for (var i = 0; i < 100; i++) + { + var items = (int[])originalItems.Clone(); + random.Shuffle(items); + + // Assert + items.Should().HaveCount(originalItems.Length); + items.Should().BeEquivalentTo(originalItems); + + foreach (var item in items) + { + originalItems.Should().Contain(item); + } + } + } +} diff --git a/PolyShim/Net100/Random.cs b/PolyShim/Net100/Random.cs new file mode 100644 index 0000000..1bf2bdc --- /dev/null +++ b/PolyShim/Net100/Random.cs @@ -0,0 +1,48 @@ +#if (NETCOREAPP && !NET10_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD) +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +using System; +using System.Collections.Generic; +using System.Text; + +internal static partial class PolyfillExtensions +{ + extension(Random 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 hex = lowercase ? Convert.ToHexStringLower(bytes) : Convert.ToHexString(bytes); + return hex.Substring(0, stringLength); + } + + // Signature-compatible replacement for GetString(ReadOnlySpan, int) + // https://learn.microsoft.com/dotnet/api/system.random.getstring + public string GetString(char[] choices, int length) + { + var buffer = new StringBuilder(); + + for (var i = 0; i < length; i++) + { + var index = random.Next(choices.Length); + buffer.Append(choices[index]); + } + + return buffer.ToString(); + } + +#if FEATURE_MEMORY + // https://learn.microsoft.com/dotnet/api/system.random.getstring + public string GetString(ReadOnlySpan choices, int length) => + random.GetString(choices.ToArray(), length); +#endif + } +} +#endif diff --git a/PolyShim/Net60/Random.cs b/PolyShim/Net60/Random.cs index ec317dc..1ecafe2 100644 --- a/PolyShim/Net60/Random.cs +++ b/PolyShim/Net60/Random.cs @@ -9,6 +9,49 @@ internal static partial class PolyfillExtensions { + extension(Random random) + { + // https://learn.microsoft.com/dotnet/api/system.random.nextint64#system-random-nextint64(system-int64-system-int64) + public long NextInt64(long minValue, long maxValue) + { + if (minValue >= maxValue) + { + throw new ArgumentOutOfRangeException( + nameof(minValue), + "minValue must be less than maxValue" + ); + } + + var range = (ulong)(maxValue - minValue); + + ulong ulongRand; + do + { + var buffer = new byte[8]; + random.NextBytes(buffer); + ulongRand = BitConverter.ToUInt64(buffer, 0); + } while (ulongRand > ulong.MaxValue - (ulong.MaxValue % range + 1) % range); + + return (long)(ulongRand % range) + minValue; + } + + // https://learn.microsoft.com/dotnet/api/system.random.nextint64#system-random-nextint64(system-int64) + public long NextInt64(long maxValue) => random.NextInt64(0, maxValue); + + // https://learn.microsoft.com/dotnet/api/system.random.nextint64#system-random-nextint64 + public long NextInt64() => random.NextInt64(0, 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)); + } + } + [ThreadStatic] private static Random? _threadRandom; diff --git a/PolyShim/Net80/Random.cs b/PolyShim/Net80/Random.cs new file mode 100644 index 0000000..7a2720e --- /dev/null +++ b/PolyShim/Net80/Random.cs @@ -0,0 +1,37 @@ +#if (NETCOREAPP && !NET8_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD) +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +using System; +using System.Collections.Generic; + +internal static partial class PolyfillExtensions +{ + extension(Random random) + { + // https://learn.microsoft.com/dotnet/api/system.random.getitems#system-random-getitems-1(-0()-system-int32) + public T[] GetItems(T[] choices, int length) + { + var result = new T[length]; + for (var i = 0; i < length; i++) + { + result[i] = choices[random.Next(choices.Length)]; + } + return result; + } + + // https://learn.microsoft.com/dotnet/api/system.random.shuffle#system-random-shuffle-1(-0()) + public void Shuffle(T[] items) + { + for (var i = items.Length - 1; i > 0; i--) + { + var j = random.Next(i + 1); + (items[i], items[j]) = (items[j], items[i]); + } + } + } +} +#endif diff --git a/PolyShim/Signatures.md b/PolyShim/Signatures.md index dd78c38..ac1f685 100644 --- a/PolyShim/Signatures.md +++ b/PolyShim/Signatures.md @@ -1,8 +1,8 @@ # Signatures -- **Total:** 264 +- **Total:** 273 - **Types:** 62 -- **Members:** 202 +- **Members:** 211 ___ @@ -226,8 +226,17 @@ ___ - [`bool TryDequeue(out T?)`](https://learn.microsoft.com/dotnet/api/system.collections.generic.queue-1.trydequeue) .NET Core 2.0 - [`bool TryPeek(out T?)`](https://learn.microsoft.com/dotnet/api/system.collections.generic.queue-1.trypeek) .NET Core 2.0 - `Random` + - [`float NextSingle()`](https://learn.microsoft.com/dotnet/api/system.random.nextsingle) .NET 6.0 + - [`long NextInt64()`](https://learn.microsoft.com/dotnet/api/system.random.nextint64#system-random-nextint64) .NET 6.0 + - [`long NextInt64(long, long)`](https://learn.microsoft.com/dotnet/api/system.random.nextint64#system-random-nextint64(system-int64-system-int64)) .NET 6.0 + - [`long NextInt64(long)`](https://learn.microsoft.com/dotnet/api/system.random.nextint64#system-random-nextint64(system-int64)) .NET 6.0 - [`Random Shared`](https://learn.microsoft.com/dotnet/api/system.random.shared) .NET 6.0 + - [`string GetHexString(int, bool)`](https://learn.microsoft.com/dotnet/api/system.random.gethexstring#system-random-gethexstring(system-int32-system-boolean)) .NET 10.0 + - [`string GetString(char[], int)`](https://learn.microsoft.com/dotnet/api/system.random.getstring) .NET 10.0 + - [`string GetString(ReadOnlySpan, int)`](https://learn.microsoft.com/dotnet/api/system.random.getstring) .NET 10.0 + - [`T[] GetItems(T[], int)`](https://learn.microsoft.com/dotnet/api/system.random.getitems#system-random-getitems-1(-0()-system-int32)) .NET 8.0 - [`void NextBytes(Span)`](https://learn.microsoft.com/dotnet/api/system.random.nextbytes#system-random-nextbytes(system-span((system-byte)))) .NET Core 2.1 + - [`void Shuffle(T[])`](https://learn.microsoft.com/dotnet/api/system.random.shuffle#system-random-shuffle-1(-0())) .NET 8.0 - `Range` - [**[struct]**](https://learn.microsoft.com/dotnet/api/system.range) .NET Core 3.0 - `Regex`