From b16e5a9de9bfdcecf7ee23fa8e6a576f42705ac6 Mon Sep 17 00:00:00 2001 From: Oliver Booth <1129769+oliverbooth@users.noreply.github.com> Date: Sat, 30 Apr 2022 14:13:16 +0100 Subject: [PATCH] 3.0.0 Update (#43) For a full list of changes, see [CHANGELOG.md](CHANGELOG.md) --- .config/dotnet-tools.json | 12 + .editorconfig | 12 +- .github/workflows/dotnet.yml | 33 + .github/workflows/dotnetcore.yml | 25 - .github/workflows/nightly.yml | 41 + .github/workflows/prerelease.yml | 46 + .github/workflows/release.yml | 46 + .github/workflows/source_validator.yml | 36 + .gitignore | 349 +----- CHANGELOG.md | 253 ++++- CONTRIBUTING.md | 26 +- LICENSE.md | 2 +- README.md | 267 +---- X10D.SourceValidator/Program.cs | 64 ++ .../X10D.SourceValidator.csproj | 14 + X10D.Tests/1000primes.txt | 1000 +++++++++++++++++ X10D.Tests/X10D.Tests.csproj | 36 +- X10D.Tests/src/Assembly.cs | 4 +- X10D.Tests/src/Collections/ArrayTests.cs | 48 + X10D.Tests/src/Collections/BoolListTests.cs | 56 + X10D.Tests/src/Collections/ByteTests.cs | 57 + X10D.Tests/src/Collections/DictionaryTests.cs | 189 ++++ X10D.Tests/src/Collections/EnumerableTests.cs | 26 + X10D.Tests/src/Collections/Int16Tests.cs | 67 ++ X10D.Tests/src/Collections/Int32Tests.cs | 67 ++ X10D.Tests/src/Collections/Int64Tests.cs | 71 ++ X10D.Tests/src/Collections/ListTests.cs | 113 ++ X10D.Tests/src/Core/BooleanTests.cs | 214 ---- X10D.Tests/src/Core/ByteTests.cs | 103 -- X10D.Tests/src/Core/CharTests.cs | 35 - X10D.Tests/src/Core/ComparableTests.cs | 22 - X10D.Tests/src/Core/ConvertibleTests.cs | 58 - X10D.Tests/src/Core/CoreTests.cs | 76 ++ X10D.Tests/src/Core/DateTimeTests.cs | 107 -- X10D.Tests/src/Core/DictionaryTests.cs | 42 - X10D.Tests/src/Core/DoubleTests.cs | 84 -- X10D.Tests/src/Core/EnumTests.cs | 62 + X10D.Tests/src/Core/EnumerableTests.cs | 67 +- X10D.Tests/src/Core/RandomTests.cs | 245 ++++ X10D.Tests/src/Core/ReflectionTests.cs | 70 -- X10D.Tests/src/Core/StringTests.cs | 81 -- X10D.Tests/src/Core/TimeSpanParserTests.cs | 26 - X10D.Tests/src/Drawing/RandomTests.cs | 37 + X10D.Tests/src/IO/BooleanTests.cs | 32 + X10D.Tests/src/IO/ByteTests.cs | 32 + X10D.Tests/src/IO/DoubleTests.cs | 66 ++ X10D.Tests/src/IO/FileInfoTests.cs | 93 ++ X10D.Tests/src/IO/Int16Tests.cs | 62 + X10D.Tests/src/IO/Int32Tests.cs | 62 + X10D.Tests/src/IO/Int64Tests.cs | 66 ++ X10D.Tests/src/IO/ListOfByteTests.cs | 168 +++ X10D.Tests/src/IO/SByteTests.cs | 33 + X10D.Tests/src/IO/SingleTests.cs | 66 ++ X10D.Tests/src/IO/StreamTests.cs | 526 +++++++++ X10D.Tests/src/IO/UInt16Tests.cs | 63 ++ X10D.Tests/src/IO/UInt32Tests.cs | 63 ++ X10D.Tests/src/IO/UInt64Tests.cs | 67 ++ X10D.Tests/src/Linq/ByteTests.cs | 96 ++ X10D.Tests/src/Linq/DecimalTests.cs | 44 + X10D.Tests/src/Linq/DoubleTests.cs | 44 + X10D.Tests/src/Linq/Int16Tests.cs | 85 ++ X10D.Tests/src/Linq/Int32Tests.cs | 73 ++ X10D.Tests/src/Linq/Int64Tests.cs | 59 + X10D.Tests/src/Linq/ReadOnlySpanTests.cs | 58 + X10D.Tests/src/Linq/SByteExtensions.cs | 37 + X10D.Tests/src/Linq/SingleTests.cs | 44 + X10D.Tests/src/Linq/SpanTests.cs | 58 + X10D.Tests/src/Linq/UInt16Tests.cs | 43 + X10D.Tests/src/Linq/UInt32Tests.cs | 45 + X10D.Tests/src/Linq/UInt64Tests.cs | 45 + X10D.Tests/src/Math/ByteTests.cs | 62 + X10D.Tests/src/Math/ComparableTests.cs | 201 ++++ X10D.Tests/src/Math/DecimalTests.cs | 122 ++ X10D.Tests/src/Math/DoubleTests.cs | 242 ++++ X10D.Tests/src/Math/Int16Tests.cs | 80 ++ X10D.Tests/src/Math/Int32Tests.cs | 83 ++ X10D.Tests/src/Math/Int64Tests.cs | 85 ++ X10D.Tests/src/Math/IsPrimeTests.cs | 154 +++ X10D.Tests/src/Math/SByteTests.cs | 79 ++ X10D.Tests/src/Math/SingleTests.cs | 242 ++++ X10D.Tests/src/Math/UInt16Tests.cs | 65 ++ X10D.Tests/src/Math/UInt32Tests.cs | 69 ++ X10D.Tests/src/Math/UInt64Tests.cs | 74 ++ X10D.Tests/src/Net/EndPointTests.cs | 76 ++ X10D.Tests/src/Net/IPAddressTests.cs | 43 + X10D.Tests/src/Net/Int16Tests.cs | 26 + X10D.Tests/src/Net/Int32Tests.cs | 26 + X10D.Tests/src/Net/Int64Tests.cs | 26 + X10D.Tests/src/Numerics/ByteTests.cs | 42 + X10D.Tests/src/Numerics/Int16Tests.cs | 44 + X10D.Tests/src/Numerics/Int32Tests.cs | 42 + X10D.Tests/src/Numerics/Int64Tests.cs | 42 + X10D.Tests/src/Numerics/RandomTests.cs | 68 ++ X10D.Tests/src/Numerics/SByteTests.cs | 43 + X10D.Tests/src/Numerics/UInt16Tests.cs | 45 + X10D.Tests/src/Numerics/UInt32Tests.cs | 43 + X10D.Tests/src/Numerics/UInt64Tests.cs | 43 + X10D.Tests/src/Reflection/MemberInfoTests.cs | 78 ++ X10D.Tests/src/Reflection/TypeTests.cs | 59 + X10D.Tests/src/Text/CharTests.cs | 37 + X10D.Tests/src/Text/CoreTests.cs | 26 + X10D.Tests/src/Text/RuneTests.cs | 39 + .../src/Text/StringBuilderReaderTests.cs | 271 +++++ X10D.Tests/src/Text/StringTests.cs | 449 ++++++++ X10D.Tests/src/Time/ByteTests.cs | 73 ++ X10D.Tests/src/Time/DateTimeOffsetTests.cs | 147 +++ X10D.Tests/src/Time/DateTimeTests.cs | 163 +++ X10D.Tests/src/Time/DecimalTests.cs | 41 + X10D.Tests/src/Time/DoubleTests.cs | 53 + X10D.Tests/src/Time/HalfTests.cs | 41 + X10D.Tests/src/Time/Int16Tests.cs | 90 ++ X10D.Tests/src/Time/Int32Tests.cs | 90 ++ X10D.Tests/src/Time/Int64Tests.cs | 90 ++ X10D.Tests/src/Time/SByteTests.cs | 86 ++ X10D.Tests/src/Time/SingleTests.cs | 41 + X10D.Tests/src/Time/StringTests.cs | 38 + X10D.Tests/src/Time/TimeSpanParserTests.cs | 15 + X10D.Tests/src/Time/TimeSpanTests.cs | 42 + X10D.Tests/src/Time/UInt16Tests.cs | 75 ++ X10D.Tests/src/Time/UInt32Tests.cs | 75 ++ X10D.Tests/src/Time/UInt64Tests.cs | 75 ++ X10D.Tests/src/Unity/Vector3Tests.cs | 93 -- X10D.Unity/X10D.Unity.csproj | 72 -- X10D.Unity/src/BetterBehavior.cs | 107 -- X10D.Unity/src/GameObjectExtensions.cs | 64 -- X10D.Unity/src/GameTime.cs | 54 - X10D.Unity/src/TransformExtensions.cs | 37 - X10D.Unity/src/Vector3Extensions.cs | 111 -- X10D.sln | 16 +- X10D/README.md | 27 - X10D/X10D.csproj | 73 +- X10D/X10D.csproj.DotSettings | 27 + X10D/src/Assembly.cs | 4 +- X10D/src/BooleanExtensions.cs | 145 --- X10D/src/ByteExtensions.cs | 116 -- X10D/src/CharExtensions.cs | 90 -- X10D/src/Collections/ArrayExtensions.cs | 92 ++ X10D/src/Collections/BoolListExtensions.cs | 130 +++ X10D/src/Collections/ByteExtensions.cs | 43 + X10D/src/Collections/DictionaryExtensions.cs | 435 +++++++ X10D/src/Collections/EnumerableExtensions.cs | 23 + X10D/src/Collections/Int16Extensions.cs | 43 + X10D/src/Collections/Int32Extensions.cs | 43 + X10D/src/Collections/Int64Extensions.cs | 43 + X10D/src/Collections/ListExtensions.cs | 136 +++ X10D/src/ComparableExtensions.cs | 27 - X10D/src/ConvertibleExtensions.cs | 161 --- X10D/src/Core/EnumExtensions.cs | 89 ++ X10D/src/Core/Extensions.cs | 59 + X10D/src/Core/RandomExtensions.cs | 418 +++++++ X10D/src/DateTimeExtensions.cs | 124 -- X10D/src/DictionaryExtensions.cs | 101 -- X10D/src/DoubleExtensions.cs | 93 -- X10D/src/Drawing/RandomExtensions.cs | 43 + X10D/src/EndPointExtensions.cs | 43 - X10D/src/Endianness.cs | 19 + X10D/src/EnumerableExtensions.cs | 33 - X10D/src/ExceptionMessages.Designer.cs | 198 ++++ X10D/src/ExceptionMessages.resx | 71 ++ X10D/src/IO/BooleanExtensions.cs | 31 + X10D/src/IO/ByteExtensions.cs | 38 + X10D/src/IO/DoubleExtensions.cs | 62 + X10D/src/IO/FileInfoExtensions.cs | 76 ++ X10D/src/IO/Int16Extensions.cs | 62 + X10D/src/IO/Int32Extensions.cs | 62 + X10D/src/IO/Int64Extensions.cs | 62 + X10D/src/IO/ListOfByteExtensions.cs | 290 +++++ X10D/src/IO/SByteExtensions.cs | 40 + X10D/src/IO/SingleExtensions.cs | 62 + X10D/src/IO/StreamExtensions.cs | 877 +++++++++++++++ X10D/src/IO/UInt16Extensions.cs | 63 ++ X10D/src/IO/UInt32Extensions.cs | 63 ++ X10D/src/IO/UInt64Extensions.cs | 63 ++ X10D/src/Int16Extensions.cs | 189 ---- X10D/src/Int32Extensions.cs | 175 --- X10D/src/Int64Extensions.cs | 241 ---- X10D/src/Linq/ByteExtensions.cs | 137 +++ X10D/src/Linq/DecimalExtensions.cs | 30 + X10D/src/Linq/DoubleExtensions.cs | 30 + X10D/src/Linq/Int16Extensions.cs | 117 ++ X10D/src/Linq/Int32Extensions.cs | 97 ++ X10D/src/Linq/Int64Extensions.cs | 77 ++ X10D/src/Linq/ReadOnlySpanExtensions.cs | 79 ++ X10D/src/Linq/SingleExtensions.cs | 30 + X10D/src/Linq/SpanExtensions.cs | 79 ++ X10D/src/ListExtensions.cs | 104 -- X10D/src/Math/ByteExtensions.cs | 109 ++ X10D/src/Math/ComparableExtensions.cs | 321 ++++++ X10D/src/Math/DecimalExtensions.cs | 173 +++ X10D/src/Math/DoubleExtensions.cs | 420 +++++++ X10D/src/Math/InclusiveOptions.cs | 28 + X10D/src/Math/Int16Extensions.cs | 171 +++ X10D/src/Math/Int32Extensions.cs | 171 +++ X10D/src/Math/Int64Extensions.cs | 227 ++++ X10D/src/Math/MathUtility.cs | 46 + X10D/src/Math/SByteExtensions.cs | 172 +++ X10D/src/Math/SingleExtensions.cs | 419 +++++++ X10D/src/Math/UInt16Extensions.cs | 110 ++ X10D/src/Math/UInt32Extensions.cs | 110 ++ X10D/src/Math/UInt64Extensions.cs | 166 +++ X10D/src/Net/EndPointExtensions.cs | 69 ++ X10D/src/Net/IPAddressExtensions.cs | 40 + X10D/src/Net/Int16Extensions.cs | 35 + X10D/src/Net/Int32Extensions.cs | 35 + X10D/src/Net/Int64Extensions.cs | 35 + X10D/src/Numerics/ByteExtensions.cs | 43 + X10D/src/Numerics/Int16Extensions.cs | 42 + X10D/src/Numerics/Int32Extensions.cs | 42 + X10D/src/Numerics/Int64Extensions.cs | 42 + X10D/src/Numerics/RandomExtensions.cs | 117 ++ X10D/src/Numerics/SByteExtensions.cs | 43 + X10D/src/Numerics/UInt16Extensions.cs | 42 + X10D/src/Numerics/UInt32Extensions.cs | 42 + X10D/src/Numerics/UInt64Extensions.cs | 42 + X10D/src/RandomExtensions.cs | 76 -- X10D/src/Reflection/MemberInfoExtensions.cs | 125 +++ X10D/src/Reflection/TypeExtensions.cs | 114 ++ X10D/src/ReflectionExtensions.cs | 123 -- X10D/src/SingleExtensions.cs | 93 -- X10D/src/StreamExtensions.cs | 32 - X10D/src/StringExtensions.cs | 409 ------- X10D/src/StructExtensions.cs | 56 - X10D/src/Text/CharExtensions.cs | 31 + X10D/src/Text/Extensions.cs | 25 + X10D/src/Text/RuneExtensions.cs | 46 + X10D/src/Text/StringBuilderReader.cs | 221 ++++ X10D/src/Text/StringExtensions.cs | 551 +++++++++ X10D/src/Time/ByteExtensions.cs | 166 +++ X10D/src/Time/DateTimeExtensions.cs | 110 ++ X10D/src/Time/DateTimeOffsetExtensions.cs | 137 +++ X10D/src/Time/DecimalExtensions.cs | 92 ++ X10D/src/Time/DoubleExtensions.cs | 92 ++ X10D/src/Time/HalfExtensions.cs | 92 ++ X10D/src/Time/Int16Extensions.cs | 172 +++ X10D/src/Time/Int32Extensions.cs | 172 +++ X10D/src/Time/Int64Extensions.cs | 172 +++ X10D/src/Time/SByteExtensions.cs | 173 +++ X10D/src/Time/SingleExtensions.cs | 92 ++ X10D/src/Time/StringExtensions.cs | 72 ++ X10D/src/Time/TimeSpanExtensions.cs | 38 + X10D/src/Time/TimeSpanParser.cs | 133 +++ X10D/src/Time/UInt16Extensions.cs | 167 +++ X10D/src/Time/UInt32Extensions.cs | 167 +++ X10D/src/Time/UInt64Extensions.cs | 167 +++ X10D/src/TimeSpanParser.cs | 77 -- X10D/src/WaitHandleExtensions.cs | 21 - banner.png | Bin 0 -> 18090 bytes global.json | 7 + icon.png | Bin 32695 -> 7709 bytes 249 files changed, 20349 insertions(+), 4703 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 .github/workflows/dotnet.yml delete mode 100644 .github/workflows/dotnetcore.yml create mode 100644 .github/workflows/nightly.yml create mode 100644 .github/workflows/prerelease.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/source_validator.yml create mode 100644 X10D.SourceValidator/Program.cs create mode 100644 X10D.SourceValidator/X10D.SourceValidator.csproj create mode 100644 X10D.Tests/1000primes.txt create mode 100644 X10D.Tests/src/Collections/ArrayTests.cs create mode 100644 X10D.Tests/src/Collections/BoolListTests.cs create mode 100644 X10D.Tests/src/Collections/ByteTests.cs create mode 100644 X10D.Tests/src/Collections/DictionaryTests.cs create mode 100644 X10D.Tests/src/Collections/EnumerableTests.cs create mode 100644 X10D.Tests/src/Collections/Int16Tests.cs create mode 100644 X10D.Tests/src/Collections/Int32Tests.cs create mode 100644 X10D.Tests/src/Collections/Int64Tests.cs create mode 100644 X10D.Tests/src/Collections/ListTests.cs delete mode 100644 X10D.Tests/src/Core/BooleanTests.cs delete mode 100644 X10D.Tests/src/Core/ByteTests.cs delete mode 100644 X10D.Tests/src/Core/CharTests.cs delete mode 100644 X10D.Tests/src/Core/ComparableTests.cs delete mode 100644 X10D.Tests/src/Core/ConvertibleTests.cs create mode 100644 X10D.Tests/src/Core/CoreTests.cs delete mode 100644 X10D.Tests/src/Core/DateTimeTests.cs delete mode 100644 X10D.Tests/src/Core/DictionaryTests.cs delete mode 100644 X10D.Tests/src/Core/DoubleTests.cs create mode 100644 X10D.Tests/src/Core/EnumTests.cs create mode 100644 X10D.Tests/src/Core/RandomTests.cs delete mode 100644 X10D.Tests/src/Core/ReflectionTests.cs delete mode 100644 X10D.Tests/src/Core/StringTests.cs delete mode 100644 X10D.Tests/src/Core/TimeSpanParserTests.cs create mode 100644 X10D.Tests/src/Drawing/RandomTests.cs create mode 100644 X10D.Tests/src/IO/BooleanTests.cs create mode 100644 X10D.Tests/src/IO/ByteTests.cs create mode 100644 X10D.Tests/src/IO/DoubleTests.cs create mode 100644 X10D.Tests/src/IO/FileInfoTests.cs create mode 100644 X10D.Tests/src/IO/Int16Tests.cs create mode 100644 X10D.Tests/src/IO/Int32Tests.cs create mode 100644 X10D.Tests/src/IO/Int64Tests.cs create mode 100644 X10D.Tests/src/IO/ListOfByteTests.cs create mode 100644 X10D.Tests/src/IO/SByteTests.cs create mode 100644 X10D.Tests/src/IO/SingleTests.cs create mode 100644 X10D.Tests/src/IO/StreamTests.cs create mode 100644 X10D.Tests/src/IO/UInt16Tests.cs create mode 100644 X10D.Tests/src/IO/UInt32Tests.cs create mode 100644 X10D.Tests/src/IO/UInt64Tests.cs create mode 100644 X10D.Tests/src/Linq/ByteTests.cs create mode 100644 X10D.Tests/src/Linq/DecimalTests.cs create mode 100644 X10D.Tests/src/Linq/DoubleTests.cs create mode 100644 X10D.Tests/src/Linq/Int16Tests.cs create mode 100644 X10D.Tests/src/Linq/Int32Tests.cs create mode 100644 X10D.Tests/src/Linq/Int64Tests.cs create mode 100644 X10D.Tests/src/Linq/ReadOnlySpanTests.cs create mode 100644 X10D.Tests/src/Linq/SByteExtensions.cs create mode 100644 X10D.Tests/src/Linq/SingleTests.cs create mode 100644 X10D.Tests/src/Linq/SpanTests.cs create mode 100644 X10D.Tests/src/Linq/UInt16Tests.cs create mode 100644 X10D.Tests/src/Linq/UInt32Tests.cs create mode 100644 X10D.Tests/src/Linq/UInt64Tests.cs create mode 100644 X10D.Tests/src/Math/ByteTests.cs create mode 100644 X10D.Tests/src/Math/ComparableTests.cs create mode 100644 X10D.Tests/src/Math/DecimalTests.cs create mode 100644 X10D.Tests/src/Math/DoubleTests.cs create mode 100644 X10D.Tests/src/Math/Int16Tests.cs create mode 100644 X10D.Tests/src/Math/Int32Tests.cs create mode 100644 X10D.Tests/src/Math/Int64Tests.cs create mode 100644 X10D.Tests/src/Math/IsPrimeTests.cs create mode 100644 X10D.Tests/src/Math/SByteTests.cs create mode 100644 X10D.Tests/src/Math/SingleTests.cs create mode 100644 X10D.Tests/src/Math/UInt16Tests.cs create mode 100644 X10D.Tests/src/Math/UInt32Tests.cs create mode 100644 X10D.Tests/src/Math/UInt64Tests.cs create mode 100644 X10D.Tests/src/Net/EndPointTests.cs create mode 100644 X10D.Tests/src/Net/IPAddressTests.cs create mode 100644 X10D.Tests/src/Net/Int16Tests.cs create mode 100644 X10D.Tests/src/Net/Int32Tests.cs create mode 100644 X10D.Tests/src/Net/Int64Tests.cs create mode 100644 X10D.Tests/src/Numerics/ByteTests.cs create mode 100644 X10D.Tests/src/Numerics/Int16Tests.cs create mode 100644 X10D.Tests/src/Numerics/Int32Tests.cs create mode 100644 X10D.Tests/src/Numerics/Int64Tests.cs create mode 100644 X10D.Tests/src/Numerics/RandomTests.cs create mode 100644 X10D.Tests/src/Numerics/SByteTests.cs create mode 100644 X10D.Tests/src/Numerics/UInt16Tests.cs create mode 100644 X10D.Tests/src/Numerics/UInt32Tests.cs create mode 100644 X10D.Tests/src/Numerics/UInt64Tests.cs create mode 100644 X10D.Tests/src/Reflection/MemberInfoTests.cs create mode 100644 X10D.Tests/src/Reflection/TypeTests.cs create mode 100644 X10D.Tests/src/Text/CharTests.cs create mode 100644 X10D.Tests/src/Text/CoreTests.cs create mode 100644 X10D.Tests/src/Text/RuneTests.cs create mode 100644 X10D.Tests/src/Text/StringBuilderReaderTests.cs create mode 100644 X10D.Tests/src/Text/StringTests.cs create mode 100644 X10D.Tests/src/Time/ByteTests.cs create mode 100644 X10D.Tests/src/Time/DateTimeOffsetTests.cs create mode 100644 X10D.Tests/src/Time/DateTimeTests.cs create mode 100644 X10D.Tests/src/Time/DecimalTests.cs create mode 100644 X10D.Tests/src/Time/DoubleTests.cs create mode 100644 X10D.Tests/src/Time/HalfTests.cs create mode 100644 X10D.Tests/src/Time/Int16Tests.cs create mode 100644 X10D.Tests/src/Time/Int32Tests.cs create mode 100644 X10D.Tests/src/Time/Int64Tests.cs create mode 100644 X10D.Tests/src/Time/SByteTests.cs create mode 100644 X10D.Tests/src/Time/SingleTests.cs create mode 100644 X10D.Tests/src/Time/StringTests.cs create mode 100644 X10D.Tests/src/Time/TimeSpanParserTests.cs create mode 100644 X10D.Tests/src/Time/TimeSpanTests.cs create mode 100644 X10D.Tests/src/Time/UInt16Tests.cs create mode 100644 X10D.Tests/src/Time/UInt32Tests.cs create mode 100644 X10D.Tests/src/Time/UInt64Tests.cs delete mode 100644 X10D.Tests/src/Unity/Vector3Tests.cs delete mode 100644 X10D.Unity/X10D.Unity.csproj delete mode 100644 X10D.Unity/src/BetterBehavior.cs delete mode 100644 X10D.Unity/src/GameObjectExtensions.cs delete mode 100644 X10D.Unity/src/GameTime.cs delete mode 100644 X10D.Unity/src/TransformExtensions.cs delete mode 100644 X10D.Unity/src/Vector3Extensions.cs delete mode 100644 X10D/README.md create mode 100644 X10D/X10D.csproj.DotSettings delete mode 100644 X10D/src/BooleanExtensions.cs delete mode 100644 X10D/src/ByteExtensions.cs delete mode 100644 X10D/src/CharExtensions.cs create mode 100644 X10D/src/Collections/ArrayExtensions.cs create mode 100644 X10D/src/Collections/BoolListExtensions.cs create mode 100644 X10D/src/Collections/ByteExtensions.cs create mode 100644 X10D/src/Collections/DictionaryExtensions.cs create mode 100644 X10D/src/Collections/EnumerableExtensions.cs create mode 100644 X10D/src/Collections/Int16Extensions.cs create mode 100644 X10D/src/Collections/Int32Extensions.cs create mode 100644 X10D/src/Collections/Int64Extensions.cs create mode 100644 X10D/src/Collections/ListExtensions.cs delete mode 100644 X10D/src/ComparableExtensions.cs delete mode 100644 X10D/src/ConvertibleExtensions.cs create mode 100644 X10D/src/Core/EnumExtensions.cs create mode 100644 X10D/src/Core/Extensions.cs create mode 100644 X10D/src/Core/RandomExtensions.cs delete mode 100644 X10D/src/DateTimeExtensions.cs delete mode 100644 X10D/src/DictionaryExtensions.cs delete mode 100644 X10D/src/DoubleExtensions.cs create mode 100644 X10D/src/Drawing/RandomExtensions.cs delete mode 100644 X10D/src/EndPointExtensions.cs create mode 100644 X10D/src/Endianness.cs delete mode 100644 X10D/src/EnumerableExtensions.cs create mode 100644 X10D/src/ExceptionMessages.Designer.cs create mode 100644 X10D/src/ExceptionMessages.resx create mode 100644 X10D/src/IO/BooleanExtensions.cs create mode 100644 X10D/src/IO/ByteExtensions.cs create mode 100644 X10D/src/IO/DoubleExtensions.cs create mode 100644 X10D/src/IO/FileInfoExtensions.cs create mode 100644 X10D/src/IO/Int16Extensions.cs create mode 100644 X10D/src/IO/Int32Extensions.cs create mode 100644 X10D/src/IO/Int64Extensions.cs create mode 100644 X10D/src/IO/ListOfByteExtensions.cs create mode 100644 X10D/src/IO/SByteExtensions.cs create mode 100644 X10D/src/IO/SingleExtensions.cs create mode 100644 X10D/src/IO/StreamExtensions.cs create mode 100644 X10D/src/IO/UInt16Extensions.cs create mode 100644 X10D/src/IO/UInt32Extensions.cs create mode 100644 X10D/src/IO/UInt64Extensions.cs delete mode 100644 X10D/src/Int16Extensions.cs delete mode 100644 X10D/src/Int32Extensions.cs delete mode 100644 X10D/src/Int64Extensions.cs create mode 100644 X10D/src/Linq/ByteExtensions.cs create mode 100644 X10D/src/Linq/DecimalExtensions.cs create mode 100644 X10D/src/Linq/DoubleExtensions.cs create mode 100644 X10D/src/Linq/Int16Extensions.cs create mode 100644 X10D/src/Linq/Int32Extensions.cs create mode 100644 X10D/src/Linq/Int64Extensions.cs create mode 100644 X10D/src/Linq/ReadOnlySpanExtensions.cs create mode 100644 X10D/src/Linq/SingleExtensions.cs create mode 100644 X10D/src/Linq/SpanExtensions.cs delete mode 100644 X10D/src/ListExtensions.cs create mode 100644 X10D/src/Math/ByteExtensions.cs create mode 100644 X10D/src/Math/ComparableExtensions.cs create mode 100644 X10D/src/Math/DecimalExtensions.cs create mode 100644 X10D/src/Math/DoubleExtensions.cs create mode 100644 X10D/src/Math/InclusiveOptions.cs create mode 100644 X10D/src/Math/Int16Extensions.cs create mode 100644 X10D/src/Math/Int32Extensions.cs create mode 100644 X10D/src/Math/Int64Extensions.cs create mode 100644 X10D/src/Math/MathUtility.cs create mode 100644 X10D/src/Math/SByteExtensions.cs create mode 100644 X10D/src/Math/SingleExtensions.cs create mode 100644 X10D/src/Math/UInt16Extensions.cs create mode 100644 X10D/src/Math/UInt32Extensions.cs create mode 100644 X10D/src/Math/UInt64Extensions.cs create mode 100644 X10D/src/Net/EndPointExtensions.cs create mode 100644 X10D/src/Net/IPAddressExtensions.cs create mode 100644 X10D/src/Net/Int16Extensions.cs create mode 100644 X10D/src/Net/Int32Extensions.cs create mode 100644 X10D/src/Net/Int64Extensions.cs create mode 100644 X10D/src/Numerics/ByteExtensions.cs create mode 100644 X10D/src/Numerics/Int16Extensions.cs create mode 100644 X10D/src/Numerics/Int32Extensions.cs create mode 100644 X10D/src/Numerics/Int64Extensions.cs create mode 100644 X10D/src/Numerics/RandomExtensions.cs create mode 100644 X10D/src/Numerics/SByteExtensions.cs create mode 100644 X10D/src/Numerics/UInt16Extensions.cs create mode 100644 X10D/src/Numerics/UInt32Extensions.cs create mode 100644 X10D/src/Numerics/UInt64Extensions.cs delete mode 100644 X10D/src/RandomExtensions.cs create mode 100644 X10D/src/Reflection/MemberInfoExtensions.cs create mode 100644 X10D/src/Reflection/TypeExtensions.cs delete mode 100644 X10D/src/ReflectionExtensions.cs delete mode 100644 X10D/src/SingleExtensions.cs delete mode 100644 X10D/src/StreamExtensions.cs delete mode 100644 X10D/src/StringExtensions.cs delete mode 100644 X10D/src/StructExtensions.cs create mode 100644 X10D/src/Text/CharExtensions.cs create mode 100644 X10D/src/Text/Extensions.cs create mode 100644 X10D/src/Text/RuneExtensions.cs create mode 100644 X10D/src/Text/StringBuilderReader.cs create mode 100644 X10D/src/Text/StringExtensions.cs create mode 100644 X10D/src/Time/ByteExtensions.cs create mode 100644 X10D/src/Time/DateTimeExtensions.cs create mode 100644 X10D/src/Time/DateTimeOffsetExtensions.cs create mode 100644 X10D/src/Time/DecimalExtensions.cs create mode 100644 X10D/src/Time/DoubleExtensions.cs create mode 100644 X10D/src/Time/HalfExtensions.cs create mode 100644 X10D/src/Time/Int16Extensions.cs create mode 100644 X10D/src/Time/Int32Extensions.cs create mode 100644 X10D/src/Time/Int64Extensions.cs create mode 100644 X10D/src/Time/SByteExtensions.cs create mode 100644 X10D/src/Time/SingleExtensions.cs create mode 100644 X10D/src/Time/StringExtensions.cs create mode 100644 X10D/src/Time/TimeSpanExtensions.cs create mode 100644 X10D/src/Time/TimeSpanParser.cs create mode 100644 X10D/src/Time/UInt16Extensions.cs create mode 100644 X10D/src/Time/UInt32Extensions.cs create mode 100644 X10D/src/Time/UInt64Extensions.cs delete mode 100644 X10D/src/TimeSpanParser.cs delete mode 100644 X10D/src/WaitHandleExtensions.cs create mode 100644 banner.png create mode 100644 global.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..f3d2ce3 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-stryker": { + "version": "1.3.1", + "commands": [ + "dotnet-stryker" + ] + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index ae973bf..125f2c1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,10 +23,10 @@ dotnet_separate_import_directive_groups=false dotnet_sort_system_directives_first=true # this. and Me. preferences -dotnet_style_qualification_for_event=true:suggestion -dotnet_style_qualification_for_field=true:suggestion -dotnet_style_qualification_for_method=true:suggestion -dotnet_style_qualification_for_property=true:suggestion +dotnet_style_qualification_for_event=false:warning +dotnet_style_qualification_for_field=false:warning +dotnet_style_qualification_for_method=false:warning +dotnet_style_qualification_for_property=false:warning # Language keywords vs BCL types preferences dotnet_style_predefined_type_for_locals_parameters_members=true:warning @@ -65,9 +65,7 @@ dotnet_code_quality_unused_parameters=all:suggestion #### C# Coding Conventions #### # var preferences -csharp_style_var_elsewhere=true:warning csharp_style_var_when_type_is_apparent=true:warning -csharp_style_var_for_built_in_types=true:warning # Expression-bodied members csharp_style_expression_bodied_accessors=true:suggestion @@ -107,7 +105,7 @@ csharp_style_unused_value_assignment_preference=discard_variable:suggestion csharp_style_unused_value_expression_statement_preference=discard_variable:silent # 'using' directive preferences -csharp_using_directive_placement=inside_namespace:suggestion +csharp_using_directive_placement=outside_namespace:error #### C# Formatting Rules #### diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..0948312 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,33 @@ +name: .NET + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + name: "Build & Test" + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration Release + + - name: Test + run: dotnet test --no-build --verbosity normal --configuration Release diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml deleted file mode 100644 index f734ea7..0000000 --- a/.github/workflows/dotnetcore.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: .NET Core - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.101 - - name: Install dependencies - run: dotnet restore - - name: Build - run: dotnet build --configuration Release --no-restore - - name: Test - run: dotnet test --no-restore --verbosity normal diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..8291cbb --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,41 @@ +name: Publish Nightly + +on: + push: + branches: + - master + - develop + +jobs: + nightly: + runs-on: ubuntu-latest + if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add GitHub NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build -c Debug + + - name: Build NuGet package + run: | + mkdir build + dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }} + + - name: Push NuGet Package to GitHub + run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate + + - name: Push NuGet Package to nuget.org + run: dotnet nuget push "build/*" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml new file mode 100644 index 0000000..1f2cb8d --- /dev/null +++ b/.github/workflows/prerelease.yml @@ -0,0 +1,46 @@ +name: Tagged Pre-Release + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+-*" + +jobs: + prerelease: + name: "Tagged Pre-Release" + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add GitHub NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Publish + run: dotnet publish -c Release -r linux-x64 --self-contained true + + - name: Build NuGet package + run: | + mkdir build + dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }} + + - name: Push NuGet Package to GitHub + run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate + + - name: Push NuGet Package to nuget.org + run: dotnet nuget push "build/*" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate + + - name: Create Release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..85ed581 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Tagged Release + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + +jobs: + release: + name: "Tagged Release" + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add GitHub NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Publish + run: dotnet publish -c Release -r linux-x64 --self-contained true + + - name: Build NuGet package + run: | + mkdir build + dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build + + - name: Push NuGet Package to GitHub + run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate + + - name: Push NuGet Package to nuget.org + run: dotnet nuget push "build/*" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate + + - name: Create Release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: false diff --git a/.github/workflows/source_validator.yml b/.github/workflows/source_validator.yml new file mode 100644 index 0000000..4c2d038 --- /dev/null +++ b/.github/workflows/source_validator.yml @@ -0,0 +1,36 @@ +name: Source Validator + +on: + push: + branches: + - master + - develop + pull_request: + types: [opened, synchronize, reopened] + +jobs: + source_validator: + runs-on: ubuntu-latest + if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add GitHub NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build -c Debug + + - name: Run Source Validation + run: dotnet run --project X10D.SourceValidator ./X10D/src + diff --git a/.gitignore b/.gitignore index 16fe8be..a437a65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,22 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +*.swp +*.*~ +project.lock.json +.DS_Store +*.pyc +nupkg/ + +# Visual Studio Code +.vscode + +# Rider +.idea # User-specific files -*.idea -*.rsuser *.suo *.user *.userosscache *.sln.docstates -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -24,327 +24,14 @@ mono_crash.* [Rr]eleases/ x64/ x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ +build/ bld/ [Bb]in/ [Oo]bj/ -[Ll]og/ +[Oo]ut/ +msbuild.log +msbuild.err +msbuild.wrn -# Visual Studio 2015/2017 cache/options directory +# Visual Studio 2015 .vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d92d1..ed96dd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,57 +1,229 @@ # Changelog -## [2.6.0] - 2020-10-20 -# Added -- Add `string.AsNullIfEmpty()` - - Returns the current string, or `null` if the current string is null or empty. -- Add `string.AsNullIfWhiteSpace()` - - Returns the current string, or `null` if the current string is null, empty, or consists of only whitespace. -- Add `string.Reverse()` - - Reverses the current string -- Add `string.WithAlternative()` - - Returns the current string, or an alternative value if the current string is null or empty, or optionally if the current string consists of only whitespace. +## [3.0.0] + +In the midst of writing these release notes, I may have missed some important changes. If you notice an API change that is not documented here, +please [open an issue](https://github.com/oliverbooth/X10D/issues)! + +### Added +- Added `T.AsArrayValue()` +- Added `T.AsEnumerableValue()` +- Added `T.RepeatValue(int)` +- Added `T.ToJson([JsonSerializerOptions])` +- Added `string.FromJson([JsonSerializerOptions])` +- Added `T[].AsReadOnly()` +- Added `T[].Clear([Range])` and `T[].Clear(int, int)` +- Added `DateTime.IsLeapYear()` +- Added `DateTimeOffset.IsLeapYear()` +- Added `Endianness` enum +- Added `FileInfo.GetHash()` +- Added `FileInfo.TryWriteHash(Span, out int)` +- Added `IComparable.Clamp(T, T)` - this supersedes `Clamp` defined for hard-coded numeric types (#24) +- Added `IComparable.GreaterThan(T2)` (#22) +- Added `IComparable.GreaterThanOrEqualTo(T2)` (#22) +- Added `IComparable.LessThan(T2)` (#22) +- Added `IComparable.LessThanOrEqualTo(T2)` (#22) +- Added `IComparable.Max(T)` (#23) +- Added `IComparable.Min(T)` (#23) +- Added `IDictionary.AddOrUpdate()` +- Added `IEnumerable.Product()` and `IEnumerable.Product(Func, TResult)` for all built-in numeric types, computing the product of all (optionally transformed) elements +- Added `IList.Fill(T)` and `IList.Fill(T, int, int)` +- Added `IPAddress.IsIPv4()` and `IPAddress.IsIPv6()` +- Added `IReadOnlyList.Pack8Bit()` +- Added `IReadOnlyList.Pack16Bit()` +- Added `IReadOnlyList.Pack32Bit()` +- Added `IReadOnlyList.Pack64Bit()` +- Added `MemberInfo.HasCustomAttribute()` and `MemberInfo.HasCustomAttribute(Type)` +- Added `defaultValue` overload for `MemberInfo.SelectFromCustomAttribute()` +- Added `Random.Next()` (returns a random field from a specified enum type) +- Added `Random.NextByte([byte[, byte]])` +- Added `Random.NextColorArgb()`, returning a random color in RGB space, including random alpha +- Added `Random.NextColorRgb()`, returning a random color in RGB space with alpha 255 +- Added `Random.NextDouble(double[, double])` +- Added `Random.NextInt16([short[, short]])` +- Added `Random.NextSingle(float[, float])` (#34) +- Added `Random.NextUnitVector2()` +- Added `Random.NextUnitVector3()` +- Added `Random.NextRotation()` +- Added `Rune.Repeat(int)` +- Added `byte.IsEven()` +- Added `byte.IsOdd()` +- Added `byte.IsPrime()` +- Added `byte.UnpackBits()` +- Added `byte.RangeTo(byte)`, `byte.RangeTo(short)`, `byte.RangeTo(int)`, and `byte.RangeTo(long)` +- Added `int.Mod(int)` +- Added `int.RangeTo(int)`, and `int.RangeTo(long)` +- Added `int.UnpackBits()` +- Added `long.Mod(long)` +- Added `long.RangeTo(long)` +- Added `long.UnpackBits()` +- Added `sbyte.IsEven()` +- Added `sbyte.IsOdd()` +- Added `sbyte.IsPrime()` +- Added `sbyte.Mod(sbyte)` +- Added `short.IsEven()` +- Added `short.IsOdd()` +- Added `short.Mod(short)` +- Added `short.RangeTo(short)`, `short.RangeTo(int)`, and `short.RangeTo(long)` +- Added `short.UnpackBits()` +- Added `string.IsPalindrome()` +- Added `Stream.GetHash()` +- Added `Stream.TryWriteHash(Span, out int)` +- Added `Stream.ReadInt16([Endian])` +- Added `Stream.ReadInt32([Endian])` +- Added `Stream.ReadInt64([Endian])` +- Added `Stream.ReadUInt16([Endian])` +- Added `Stream.ReadUInt32([Endian])` +- Added `Stream.ReadUInt64([Endian])` +- Added `Stream.Write(short, [Endian])` +- Added `Stream.Write(int, [Endian])` +- Added `Stream.Write(long, [Endian])` +- Added `Stream.Write(ushort, [Endian])` +- Added `Stream.Write(uint, [Endian])` +- Added `Stream.Write(ulong, [Endian])` +- Added `TimeSpan.Ago()` +- Added `TimeSpan.FromNow()` +- Added `TimeSpanParser.TryParse` which supersedes `TimeSpanParser.Parse` +- Added `Sqrt()` and `ComplexSqrt()` for all built-in decimal types +- Added `All()` and `Any()` for `Span` and `ReadOnlySpan`, mimicking the corresponding methods in `System.Linq` +- Added `Sign()` for built-in numeric types. For unsigned types, this never returns -1 +- Added `Type.Implements()` and `Type.Implements(Type)` (#25) +- Added `Type.Inherits()` and `Type.Inherits(Type)` (#25) +- Added `DigitalRoot` function for built-in integer types +- Added `Factorial` function for built-in integer types +- Added `HostToNetworkOrder` and `NetworkToHostOrder` for `short`, `int`, and `long` +- Added `IsLeapYear` function for `DateTime` and `DateTimeOffset`, as well as built-in numeric types +- Added `MultiplicativePersistence` function for built-in integer types +- Added `RotateLeft` and `RotateRight` for built-in integer types +- Added trigonometric functions for built-in numeric types, including `Acos()`, `Asin()`, `Atan()`, `Atan2()`, `Cos()`, `Sin()`, `Tan()` (#49) +- Added time-related extension methods for built-in numeric types, including `Milliseconds()`, `Seconds()`, `Minutes()`, `Hours()`, `Days()`, and `Weeks()`. `Ticks()` is also available, but only for integers; not floating point +- Added `StringBuilderReader` (inheriting `TextReader`) which allows reading from a `StringBuilder` without consuming the underlying string +- Added extension methods for `System.Decimal` ### Changed +- Updated to .NET 6 (#45) +- Methods defined to accept `byte[]` have been changed accept `IReadOnlyList` +- Extension methods are now defined in appropriate child namespaces to reduce the risk of name collisions (#7) +- `char[].Random(int)`, `char[].Random(int, int)`, `IEnumerable.Random(int)`, and `IEnumerable.Random(int, int)` have been redefined as `Random.NextString(IReadOnlyList, int)` +- `IComparable.Between(T, T)` has been redefined as `IComparable.Between(T2, T3, [InclusiveOptions])` to allow comparison of disparate yet comparable types, and also offers inclusivity options +- `DateTime` extensions now wrap `DateTimeOffset` extensions +- `DateTime.ToUnixTimestamp([bool])` has been redefined as `DateTime.ToUnixTimeMilliseconds()` and `DateTime.ToUnixTimeSeconds()` +- `Dictionary.ToGetParameters()`, `IDictionary.ToGetParameters()`, and `IReadOnlyDictionary.ToGetParameters()`, has been redefined as `IEnumerable>.ToGetParameters()` +- `Dictionary.ToConnectionString()`, `IDictionary.ToConnectionString()`, and `IReadOnlyDictionary.ToConnectionString()`, has been redefined as `IEnumerable>.ToConnectionString()` +- `IList.OneOf([Random])` and `IEnumerable.OneOf([Random])` have been redefined as `Random.NextFrom(IEnumerable)` to fall in line with the naming convention of `System.Random` (#21) +- `IList.Shuffle([Random])` now uses a Fisher-Yates shuffle implementation +- `IList.Shuffle([Random])` has been repurposed to shuffle the list in place, rather than returning a new enumerable + - `IEnumerable.Shuffle([Random])` has been renamed to `IEnumerable.Shuffled([Random])` to avoid confusion with `IList.Shuffle([Random])` +- `Random.CoinToss()` has been redefined as `Random.NextBoolean()` to fall in line with the naming convention of `System.Random` +- `Random.OneOf(T[])` and `Random.OneOf(IList)` have been redefined as `Random.NextFrom(IEnumerable)` to fall in line with the naming convention of `System.Random` +- `Enum.Next([bool])` and `Enum.Previous([bool])` have been redefined as `Enum.Next()`, `Enum.Previous()`, `Enum.NextUnchecked()`, `Enum.PreviousUnchecked()`. The `Unchecked` variants of these methods do not perform index validation, and will throw `IndexOutOfRangeException` when attempting to access an invalid index. The checked variants will perform a modulo to wrap the index +- Seperated `string.WithAlternative(string, [bool])` to `string.WithEmptyAlternative(string)` and `string.WithWhiteSpaceAlternative(string)` +- `string.AsNullIfEmpty()` and `string.AsNullIfWhiteSpace()` now accept a nullable `string` +- `IEnumerable.GetString([Encoding])` has been renamed to `IReadOnlyList.ToString` and its `Encoding` parameter has + been made non-optional +- Fixed a bug where `IEnumerable>.ToConnectionString()` would not sanitize types with custom `ToString()` + implementation (#20) + Renamed `string.Random(int[, Random])` to `string.Randomize(int[, Random])` +- Redefined `char[].Random(int)`, `char[].Random(int, Random)`, `IEnumerable.Random(int)`, and `IEnumerable.Random(int, Random)`, as `Random.NextString(IReadOnlyList, int)` +- Improved performance for: + - `string.IsLower()` + - `string.IsUpper()` + - `string.Reverse()` + - `TimeSpanParser` + +### Removed +- Indefinitely suspended Unity support, until such a time that Unity can be updated to a newer version of .NET. See: https://forum.unity.com/threads/unity-future-net-development-status.1092205/ +- Removed `bool.And(bool)` +- Removed `bool.NAnd(bool)` +- Removed `bool.NOr(bool)` +- Removed `bool.Not(bool)` +- Removed `bool.Or(bool)` +- Removed `bool.ToByte()` +- Removed `bool.ToInt16()` +- Removed `bool.ToInt64()` +- Removed `bool.XNOr()` +- Removed `bool.XOr()` +- Removed `IConvertible.To([IFormatProvider])` (#13) +- Removed `IConvertible.ToOrDefault([IFormatProvider])` (#13) +- Removed `IConvertible.ToOrDefault(out T, [IFormatProvider])` (#13) +- Removed `IConvertible.ToOrNull([IFormatProvider])` (#13) +- Removed `IConvertible.ToOrNull(out T, [IFormatProvider])` (#13) +- Removed `IConvertible.ToOrOther(T, [IFormatProvider])` (#13) +- Removed `IConvertible.ToOrOther(out T, T, [IFormatProvider])` (#13) +- Removed `IEnumerable.Split(int)` - this functionality is now provided by .NET in the form of the `Chunk` method. See: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.chunk?view=net-6.0 +- Removed `MemberInfo.GetDefaultValue()` (use `SelectFromCustomAttribute()` instead) +- Removed `MemberInfo.GetDescription()` (use `SelectFromCustomAttribute()` instead) +- Removed `int.ToBoolean()` +- Removed `long.ToBoolean()` +- Removed `short.ToBoolean()` +- Removed `uint.ToBoolean()` +- Removed `ushort.ToBoolean()` +- Removed `ulong.ToBoolean()` +- Removed `WaitHandle.WaitOneAsync()`. For suspensions of execution during asynchronous operations, favour `TaskCompletionSource` or `TaskCompletionSource`. See: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource?view=net-6.0 and https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1?view=net-6.0 + +## [2.6.0] - 2020-10-20 + +### Added + +- Add `string.AsNullIfEmpty()` + - Returns the current string, or `null` if the current string is null or empty. +- Add `string.AsNullIfWhiteSpace()` + - Returns the current string, or `null` if the current string is null, empty, or consists of only whitespace. +- Add `string.Reverse()` + - Reverses the current string +- Add `string.WithAlternative()` + - Returns the current string, or an alternative value if the current string is null or empty, or optionally if the current + string consists of only whitespace. + +### Changed + - n/a ### Removed + - n/a ## [2.5.0] - 2020-07-15 + ### Added + - `WaitHandle.WaitOneAsync()` - - Wraps `WaitHandle.WaitOne` as a `Task` + - Wraps `WaitHandle.WaitOne` as a `Task` - Add support for Unity 2019.4.3f1 - - Add `GameObject.LookAt(GameObject)` - - Rotates the Transform on the current GameObject so that it faces the Transform on another GameObject - - Add `GameObject.LookAt(Transform)` - - Rotates the Transform on the current GameObject so that it faces another transform - - Add `Transform.LookAt(GameObject)` - - Rotates the current Transform so that it faces the Transform on another GameObject - - Add `Vector3.Round([float])` - - Returns a rounded Vector3 by calling `float.Round()` on each component - - Add `Vector3.WithX(float)` - - Returns a Vector3 with a new X component value - - Add `Vector3.WithY(float)` - - Returns a Vector3 with a new Y component value - - Add `Vector3.WithZ(float)` - - Returns a Vector3 with a new Z component value - - Add `Vector3.WithXY(float, float)` - - Returns a Vector3 with new X and Y component values - - Add `Vector3.WithXZ(float, float)` - - Returns a Vector3 with new X and Z component values - - Add `Vector3.WithYZ(float, float)` - - Returns a Vector3 with new Y and Z component values - - Add `BetterBehavior` (experimental wrapper over `MonoBehaviour`) + - Add `GameObject.LookAt(GameObject)` + - Rotates the Transform on the current GameObject so that it faces the Transform on another GameObject + - Add `GameObject.LookAt(Transform)` + - Rotates the Transform on the current GameObject so that it faces another transform + - Add `Transform.LookAt(GameObject)` + - Rotates the current Transform so that it faces the Transform on another GameObject + - Add `Vector3.Round([float])` + - Returns a rounded Vector3 by calling `float.Round()` on each component + - Add `Vector3.WithX(float)` + - Returns a Vector3 with a new X component value + - Add `Vector3.WithY(float)` + - Returns a Vector3 with a new Y component value + - Add `Vector3.WithZ(float)` + - Returns a Vector3 with a new Z component value + - Add `Vector3.WithXY(float, float)` + - Returns a Vector3 with new X and Y component values + - Add `Vector3.WithXZ(float, float)` + - Returns a Vector3 with new X and Z component values + - Add `Vector3.WithYZ(float, float)` + - Returns a Vector3 with new Y and Z component values + - Add `BetterBehavior` (experimental wrapper over `MonoBehaviour`) ### Changed + - n/a ### Removed + - n/a ## [2.2.0] - 2020-04-21 + ### Added + - Add `string.ChangeEncoding(Encoding, Encoding)` - Converts this string from one encoding to another - Add `string.IsLower()` @@ -62,19 +234,24 @@ - Various extension methods with regards to reflection: - `GetDefaultValue` and `GetDefaultValue` - gets the value stored in the member's `DefaultValue` attribute - `GetDescription`- gets the value stored in the member's `Description` attribute - - `SelectFromCustomAttribute` - Internally calls `GetCustomAttribute` and passes it to a `Func` so that specific members may be selected + - `SelectFromCustomAttribute` - Internally calls `GetCustomAttribute` and passes it to a `Func` so that + specific members may be selected ### Changed + - n/a ### Removed + - n/a ## [2.1.0] - 2020-04-18 + ### Added + - Add `bool bool.And(bool)` - Performs logical AND -- Add `bool bool.Or(bool)` +- Add `bool bool.Or(bool)` - Performs logical OR - Add `bool bool.Not(bool)` - Performs logical NOT @@ -94,29 +271,35 @@ - 1 if `true`, 0 otherwise ### Changed + - n/a ### Removed + - n/a ## [2.0.0] - 2020-04-18 ### Added + - Add `IEnumerable.Split(int)` - Performs the same operation as the previously defined `IEnumerable.Chunkify(int)`, except now accepts any type `T` ### Changed + - Fix `DateTime.Last(DayOfWeek)` implementation - Now returns the correct date/time for a given day of the week ### Removed + - Remove `IEnumerable.Chunkify(int)` - Replaced by a method of the same behaviour `IEnumerable.Split(int)` ## Earlier versions + ### ***Not documented*** -[Unreleased]: https://github.com/oliverbooth/X10D/tree/HEAD +[3.0.0]: https://github.com/oliverbooth/X10D/releases/tag/3.0.0 [2.6.0]: https://github.com/oliverbooth/X10D/releases/tag/2.6.0 [2.5.0]: https://github.com/oliverbooth/X10D/releases/tag/2.5.0 [2.2.0]: https://github.com/oliverbooth/X10D/releases/tag/2.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edf02e4..86d8255 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,25 +2,25 @@ Contributions to this project are always welcome. If you spot a bug, or want to request a new extension method, open a new issue or submit a pull request. ### Pull request guidelines -This project uses C# 8.0 language features, and adheres to the following analyzers: - -- Rozlynator -- FxCop -- StyleCop - -There is an `.editorconfig` and an analyzer `ruleset` file included in this repository. For quick and painless pull requests, ensure that these analyzers do not throw warnings. +This project uses C# 9.0 language features, and adheres to the FxCop analyzer. +There is an `.editorconfig` included in this repository. For quick and painless pull requests, ensure that the analyzer does not throw warnings. ### Code style Below are a few pointers to which you may refer, but keep in mind this is not an exhaustive list: -- Use C# 8.0 features where possible -- Try to ensure code is CLS-compliant +- Use C# 9.0 features where possible +- Try to ensure code is CLS-compliant. Where this is not possible, decorate methods with `CLSCompliantAttribute` and pass `false` - Follow all .NET guidelines for naming conventions -- Make full use of XMLDoc and be thorough - but concise - with all documentation. -- Ensure that no line exceeds 120 characters in length +- Make full use of XMLDoc and be thorough - but concise - with all documentation +- Ensure that no line exceeds 130 characters in length - Do NOT include file headers in any form -- Declare `using` directives within namespace scope -- Try to avoid using exceptions for flow control +- Declare `using` directives outside of namespace scope +- Avoid using exceptions for flow control where possible +- Use braces, even for single-statement bodies +- Use implicit type when the type is apparent or not important +- Use U.S. English throughout the codebase and documentation + +When in doubt, follow .NET guidelines for styling. ### Tests When introducing a new extension method, you must ensure that you have also defined a unit test that asserts its correct behavior. The code style guidelines and code-analysis rules apply to the `X10D.Tests` equally as much as `X10D`, although documentation may be briefer. Refer to existing tests as a guideline. diff --git a/LICENSE.md b/LICENSE.md index b6e3a9a..ed7c8c7 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Oliver Booth +Copyright (c) 2019-2022 Oliver Booth Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7df0391..be29bd1 100644 --- a/README.md +++ b/README.md @@ -1,259 +1,46 @@ -# X10D -## Extension methods on crack - -[](https://github.com/oliverbooth/X10D/actions?query=workflow%3A%22.NET+Core%22) -[](https://github.com/oliverbooth/X10D/issues) -[](https://www.nuget.org/packages/X10D/) -[](https://github.com/oliverbooth/X10D/blob/master/LICENSE.md) +

+

+GitHub Workflow Status +GitHub Issues +Coverage +NuGet Downloads +Stable Version +Nightly Version +MIT License +

### About -X10D (pronounced *extend*), is a class library that provides extension methods for numerous .NET types. The purpose of this library is to simplify a codebase by reducing the need for repeated code when performing common operations. Simplify your codebase. Take advantage of .NET. Use extension methods. +X10D (pronounced *extend*), is a .NET package that provides extension methods for numerous types. The purpose of this library is to simplify a codebase by reducing the need for repeated code when performing common operations. Simplify your codebase. Take advantage of .NET. Use extension methods. *(I'm also [dogfooding](https://www.pcmag.com/encyclopedia/term/dogfooding) this library, so there's that.)* ### Table of contents -- [Install](#install) +- [Installation](#installation) + - [Unity installation](#unity-installation) + - [NuGet installation](#nuget-installation) - [Features](#features) - - [Numeric](#numeric) - - [String](#string) - - [DateTime](#datetime) - - [Enumerable](#enumerable) - - [Enum](#enum) - - [Conversion](#conversion) - - [Random](#random) - [Contributing](#contributing) - [License](#license) You can find the list of classes that have extension methods by viewing the `README.md` file in any of the respective library folders. -## Install -Install X10D with NuGet via the following command: +## Installation +### NuGet installation ```ps -Install-Package X10D -Version 2.6.0 +Install-Package X10D -Version 3.0.0 ``` -or by downloading the [latest release](https://github.com/oliverbooth/X10D/releases/latest) from this repository. + +### Manual installation +Download the [latest release](https://github.com/oliverbooth/X10D/releases/latest) from this repository and adding a direct assembly reference for your chosen platform. + +### What happened to X10D.Unity? +I decided it was time for X10D to be migrated to .NET 6. Unity currently supports .NET Framework 4.x / .NET Standard 2.1, and as such is not compatible with X10D at this time. +Unity have announced official support for .NET 6 in the future (see [this forum post](https://forum.unity.com/threads/unity-future-net-development-status.1092205/) for more details), +but until such a time that Unity supports .NET 6, X10D.Unity will not be maintained or innovated upon. ## Features -### Numeric extensions -> 👍 ProTip: *Most* of the extensions available for `int` will also exist for `short`, `long`, and their unsigned counterparts! - -#### `bool` <-> `int` -Convert a `bool` to an `int` by using `ToInt32`. The value returned is 1 if the input is `true`, and 0 if it's `false`. -```cs -bool b = true; - -int i = b.ToInt32(); // 1 -``` -The same also works in reverse. Using `ToBoolean` on an `int` will return `false` if the input is 0, and `true`if the input is anything else. -```cs -int zero = 0; -long nonZero = 1; - -bool b1 = zero.ToBoolean(); // false -bool b2 = nonZero.ToBoolean(); // true -``` - -#### Between -Determine if a value is between other values using `Between` like so: -```cs -int i = 3; - -if (i.Between(2, 4)) -{ - // i is between 2 and 4! -} -``` -Since the signature of this method is defined with a generic constraint of `IComparable`, this will also work for any object that is `IComparable` - not just numeric types! -```cs -bool Between(this T actual, T lower, T upper) where T : IComparable -``` - -#### IsEven (*and IsOdd*) -As the names suggest, this method determines if the input value is evenly divisible by 2. -```cs -int i = 5; -bool b = i.IsEven(); // false -``` -There is also an `IsOdd` extension method, which will return the opposite of that returned by `IsEven`. - -#### IsPrime -Determine if an integral is a prime number by using `IsPrime`. -```cs -bool b = 43.IsPrime(); // true -``` - -#### Clamp -Clamp a value between an upper and lower bound -```cs -int i = 5.Clamp(0, 3); // 3 -``` - -#### Convert degrees <-> radians -Easily convert between radians and degrees -```cs -double rad = 2 * Math.PI; -double deg = rad.RadiansToDegrees(); // 360 - -rad = deg.DegreesToRadians(); // back to 2*pi -``` - -#### Round -Round a value to the nearest whole number: -```cs -var d = 2.75; -var rounded = d.Round(); // 3 -``` -Or specify a value to have it round to the nearest multiple of `x`: -```cs -double a = 8.0.Round(10); // 10 -double b = 2.0.Round(10); // 0 -``` - -### String -#### Repeat value -Repeat a string or a char a specific number of times using `Repeat` -```cs -var c = '-'; -var str = "foo"; - -string repeatedC = c.Repeat(10); // ---------- -string repeatedStr = str.Repeat(5); // foofoofoofoofoo -``` - -#### Base-64 encode/decode -```cs -var base64 = "Hello World".Base64Encode(); // SGVsbG8gV29ybGQ= -var str = "SGVsbG8gV29ybGQ=".Base64Decode(); // Hello World -``` - -### DateTime - -#### Age -Get a rounded integer representing the number of years since a given date. i.e. easily calculate someone's age: -```cs -var dateOfBirth = new DateTime(1960, 7, 16); -int age = dateOfBirth.Age(); // the age as of today -``` -You can also specify a date at which to stop counting the years, by passing an `asOf` date: -```cs -var dateOfBirth = new DateTime(1960, 7, 16); -int age = dateOfBirth.Age(new DateTime(1970, 7, 16)); // 10, the age as of 1970 -``` - -#### To/From Unix Timestamp -Convert to/from a Unix timestamp represented in seconds using `FromUnixTimestamp` on a numeric type, and `ToUnixTimestamp` on a `DateTime`. -```cs -long sec = 1587223415; -DateTime time = sec.FromUnixTimestamp(); // 2020-04-18 15:23:35 -long unix = time.ToUnixTimestamp(); -``` -or represent it with milliseconds by passing `true` for the `isMillis` argument: -```cs -long millis = 1587223415500; -DateTime time = millis.FromUnixTimestamp(true); // 2020-04-18 15:23:35.50 -long unix = time.ToUnixTimestamp(true); -``` - -#### Get first/last day of month -Get the first or last day of the month by using `FirstDayOfMonth` and `LastDayOfMonth` -```cs -var dt = new DateTime(2016, 2, 4); - -DateTime first = dt.FirstDayOfMonth(); // 2016-02-01 -DateTime last = dt.LastDayOfMonth(); // 2016-02-29 (2016 is a leap year) -``` -You can also use `First` or `Last` to get the first or final occurrence of a specific day of the week in a given month: -```cs -var dt = new DateTime(2019, 4, 14); - -DateTime theLastFriday = dt.Last(DayOfWeek.Friday); // 2019-04-24 -DateTime theLastThursday = dt.Last(DayOfWeek.Thursday); // 2019-04-40 -``` - -### Enumerable -#### Split into chunks -Split an `IEnumerable` into an `IEnumerable>`, essentially "chunking" the original IEnumerable into a specific size -```cs -var arr = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; -var chunks = arr.Split(2); // split into chunks of 2 - -foreach (var chunk in chunks) -{ - Console.WriteLine(string.Join(", ", chunk)); -} - -// Output: -// 1, 2 -// 3, 4 -// 5, 6 -// 7, 8 -``` -This also works for `string`: -```cs -var str = "Hello World"; -var chunks = str.Split(2); // split string into chunks of 2 - -foreach (var chunk in chunks) -{ - Console.WriteLine(string.Join(string.Empty, chunk)); -} - -// Output: -// He -// ll -// o <-- space is included -// Wo -// rl -// d <-- no space! end of string -``` - -### Enum -#### Parse string into enum -You can use the `EnumParse` method to convert a string into a value from an enum, while optionally ignoring case: -```cs -enum Number -{ - Zero, - One, - Two, - Three, -} - -Number num = "two".EnumParse(true); // num == Number.Two -``` - -#### `Next` / `Previous` enum cycling -Cycle through the values in an enum with `Next` and `Previous`: -```cs -Number two = Number.Two; - -Number one = two.Previous(); -Number three = two.Next(); -``` - -### Conversion -Easily convert between types using `To`, `ToOrNull`, `ToOrDefault`, or `ToOrOther`, thereby shortening the call to `Convert.ChangeType` or `Convert.ToX`: -```CS -int i = "43".To(); -int j = "a".ToOrDefault(); // 0 -int k = "a".ToOrOther(100); // 100 -``` - -### Random -Do more with Random including flip a coin, randomly select an element in an array, or shuffle the array entirely. -```cs -var random = new Random(); - -// flip a coin -bool heads = random.CoinToss(); - -// randomly choose an item -var arr = new int[] { 1, 2, 3, 4 }; -var item = random.OneOf(arr); - -// shuffle an array or list -var shuffled = arr.Shuffle(random); -``` +I'm planning on writing complete and extensive documentation in the near future. As of this time, feel free to browse the source or the API using your favourite IDE. +For those familiar with the 2.6.0 API, please read [CHANGELOG.md](CHANGELOG.md) for a complete list of changes. **3.0.0 is a major release and introduces many breaking changes.** ## Contributing Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/X10D.SourceValidator/Program.cs b/X10D.SourceValidator/Program.cs new file mode 100644 index 0000000..2f09427 --- /dev/null +++ b/X10D.SourceValidator/Program.cs @@ -0,0 +1,64 @@ +using System.Text; + +var directories = new Stack(Directory.GetDirectories(args[0])); +var problems = 0; +var files = 0; + +while (directories.Count > 0) +{ + string path = Path.GetFullPath(directories.Pop()); + + foreach (string directory in Directory.EnumerateDirectories(path)) + { + directories.Push(directory); + } + + foreach (string file in Directory.EnumerateFiles(path, "*.cs")) + { + files++; + await using var stream = File.OpenRead(file); + using var reader = new StreamReader(stream, Encoding.UTF8); + var blankLine = false; + + var lineNumber = 1; + while (await reader.ReadLineAsync() is { } line) + { + if (string.IsNullOrWhiteSpace(line)) + { + if (blankLine) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"{file}({lineNumber}): Double blank line"); + Console.ResetColor(); + problems++; + } + + blankLine = true; + } + else + { + blankLine = false; + } + + if (line.Length > 130) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"{file}({lineNumber}): Line is too long ({line.Length})"); + Console.ResetColor(); + problems++; + } + else if (line.Length > 0 && char.IsWhiteSpace(line[^1])) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"{file}({lineNumber}): Line contains trailing whitespace"); + Console.ResetColor(); + problems++; + } + + lineNumber++; + } + } +} + +Console.Out.WriteLine($"Finished scanning {files} files, {problems} problems encountered."); +return problems; diff --git a/X10D.SourceValidator/X10D.SourceValidator.csproj b/X10D.SourceValidator/X10D.SourceValidator.csproj new file mode 100644 index 0000000..8957a5d --- /dev/null +++ b/X10D.SourceValidator/X10D.SourceValidator.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/X10D.Tests/1000primes.txt b/X10D.Tests/1000primes.txt new file mode 100644 index 0000000..1d931b0 --- /dev/null +++ b/X10D.Tests/1000primes.txt @@ -0,0 +1,1000 @@ +2 +3 +5 +7 +11 +13 +17 +19 +23 +29 +31 +37 +41 +43 +47 +53 +59 +61 +67 +71 +73 +79 +83 +89 +97 +101 +103 +107 +109 +113 +127 +131 +137 +139 +149 +151 +157 +163 +167 +173 +179 +181 +191 +193 +197 +199 +211 +223 +227 +229 +233 +239 +241 +251 +257 +263 +269 +271 +277 +281 +283 +293 +307 +311 +313 +317 +331 +337 +347 +349 +353 +359 +367 +373 +379 +383 +389 +397 +401 +409 +419 +421 +431 +433 +439 +443 +449 +457 +461 +463 +467 +479 +487 +491 +499 +503 +509 +521 +523 +541 +547 +557 +563 +569 +571 +577 +587 +593 +599 +601 +607 +613 +617 +619 +631 +641 +643 +647 +653 +659 +661 +673 +677 +683 +691 +701 +709 +719 +727 +733 +739 +743 +751 +757 +761 +769 +773 +787 +797 +809 +811 +821 +823 +827 +829 +839 +853 +857 +859 +863 +877 +881 +883 +887 +907 +911 +919 +929 +937 +941 +947 +953 +967 +971 +977 +983 +991 +997 +1009 +1013 +1019 +1021 +1031 +1033 +1039 +1049 +1051 +1061 +1063 +1069 +1087 +1091 +1093 +1097 +1103 +1109 +1117 +1123 +1129 +1151 +1153 +1163 +1171 +1181 +1187 +1193 +1201 +1213 +1217 +1223 +1229 +1231 +1237 +1249 +1259 +1277 +1279 +1283 +1289 +1291 +1297 +1301 +1303 +1307 +1319 +1321 +1327 +1361 +1367 +1373 +1381 +1399 +1409 +1423 +1427 +1429 +1433 +1439 +1447 +1451 +1453 +1459 +1471 +1481 +1483 +1487 +1489 +1493 +1499 +1511 +1523 +1531 +1543 +1549 +1553 +1559 +1567 +1571 +1579 +1583 +1597 +1601 +1607 +1609 +1613 +1619 +1621 +1627 +1637 +1657 +1663 +1667 +1669 +1693 +1697 +1699 +1709 +1721 +1723 +1733 +1741 +1747 +1753 +1759 +1777 +1783 +1787 +1789 +1801 +1811 +1823 +1831 +1847 +1861 +1867 +1871 +1873 +1877 +1879 +1889 +1901 +1907 +1913 +1931 +1933 +1949 +1951 +1973 +1979 +1987 +1993 +1997 +1999 +2003 +2011 +2017 +2027 +2029 +2039 +2053 +2063 +2069 +2081 +2083 +2087 +2089 +2099 +2111 +2113 +2129 +2131 +2137 +2141 +2143 +2153 +2161 +2179 +2203 +2207 +2213 +2221 +2237 +2239 +2243 +2251 +2267 +2269 +2273 +2281 +2287 +2293 +2297 +2309 +2311 +2333 +2339 +2341 +2347 +2351 +2357 +2371 +2377 +2381 +2383 +2389 +2393 +2399 +2411 +2417 +2423 +2437 +2441 +2447 +2459 +2467 +2473 +2477 +2503 +2521 +2531 +2539 +2543 +2549 +2551 +2557 +2579 +2591 +2593 +2609 +2617 +2621 +2633 +2647 +2657 +2659 +2663 +2671 +2677 +2683 +2687 +2689 +2693 +2699 +2707 +2711 +2713 +2719 +2729 +2731 +2741 +2749 +2753 +2767 +2777 +2789 +2791 +2797 +2801 +2803 +2819 +2833 +2837 +2843 +2851 +2857 +2861 +2879 +2887 +2897 +2903 +2909 +2917 +2927 +2939 +2953 +2957 +2963 +2969 +2971 +2999 +3001 +3011 +3019 +3023 +3037 +3041 +3049 +3061 +3067 +3079 +3083 +3089 +3109 +3119 +3121 +3137 +3163 +3167 +3169 +3181 +3187 +3191 +3203 +3209 +3217 +3221 +3229 +3251 +3253 +3257 +3259 +3271 +3299 +3301 +3307 +3313 +3319 +3323 +3329 +3331 +3343 +3347 +3359 +3361 +3371 +3373 +3389 +3391 +3407 +3413 +3433 +3449 +3457 +3461 +3463 +3467 +3469 +3491 +3499 +3511 +3517 +3527 +3529 +3533 +3539 +3541 +3547 +3557 +3559 +3571 +3581 +3583 +3593 +3607 +3613 +3617 +3623 +3631 +3637 +3643 +3659 +3671 +3673 +3677 +3691 +3697 +3701 +3709 +3719 +3727 +3733 +3739 +3761 +3767 +3769 +3779 +3793 +3797 +3803 +3821 +3823 +3833 +3847 +3851 +3853 +3863 +3877 +3881 +3889 +3907 +3911 +3917 +3919 +3923 +3929 +3931 +3943 +3947 +3967 +3989 +4001 +4003 +4007 +4013 +4019 +4021 +4027 +4049 +4051 +4057 +4073 +4079 +4091 +4093 +4099 +4111 +4127 +4129 +4133 +4139 +4153 +4157 +4159 +4177 +4201 +4211 +4217 +4219 +4229 +4231 +4241 +4243 +4253 +4259 +4261 +4271 +4273 +4283 +4289 +4297 +4327 +4337 +4339 +4349 +4357 +4363 +4373 +4391 +4397 +4409 +4421 +4423 +4441 +4447 +4451 +4457 +4463 +4481 +4483 +4493 +4507 +4513 +4517 +4519 +4523 +4547 +4549 +4561 +4567 +4583 +4591 +4597 +4603 +4621 +4637 +4639 +4643 +4649 +4651 +4657 +4663 +4673 +4679 +4691 +4703 +4721 +4723 +4729 +4733 +4751 +4759 +4783 +4787 +4789 +4793 +4799 +4801 +4813 +4817 +4831 +4861 +4871 +4877 +4889 +4903 +4909 +4919 +4931 +4933 +4937 +4943 +4951 +4957 +4967 +4969 +4973 +4987 +4993 +4999 +5003 +5009 +5011 +5021 +5023 +5039 +5051 +5059 +5077 +5081 +5087 +5099 +5101 +5107 +5113 +5119 +5147 +5153 +5167 +5171 +5179 +5189 +5197 +5209 +5227 +5231 +5233 +5237 +5261 +5273 +5279 +5281 +5297 +5303 +5309 +5323 +5333 +5347 +5351 +5381 +5387 +5393 +5399 +5407 +5413 +5417 +5419 +5431 +5437 +5441 +5443 +5449 +5471 +5477 +5479 +5483 +5501 +5503 +5507 +5519 +5521 +5527 +5531 +5557 +5563 +5569 +5573 +5581 +5591 +5623 +5639 +5641 +5647 +5651 +5653 +5657 +5659 +5669 +5683 +5689 +5693 +5701 +5711 +5717 +5737 +5741 +5743 +5749 +5779 +5783 +5791 +5801 +5807 +5813 +5821 +5827 +5839 +5843 +5849 +5851 +5857 +5861 +5867 +5869 +5879 +5881 +5897 +5903 +5923 +5927 +5939 +5953 +5981 +5987 +6007 +6011 +6029 +6037 +6043 +6047 +6053 +6067 +6073 +6079 +6089 +6091 +6101 +6113 +6121 +6131 +6133 +6143 +6151 +6163 +6173 +6197 +6199 +6203 +6211 +6217 +6221 +6229 +6247 +6257 +6263 +6269 +6271 +6277 +6287 +6299 +6301 +6311 +6317 +6323 +6329 +6337 +6343 +6353 +6359 +6361 +6367 +6373 +6379 +6389 +6397 +6421 +6427 +6449 +6451 +6469 +6473 +6481 +6491 +6521 +6529 +6547 +6551 +6553 +6563 +6569 +6571 +6577 +6581 +6599 +6607 +6619 +6637 +6653 +6659 +6661 +6673 +6679 +6689 +6691 +6701 +6703 +6709 +6719 +6733 +6737 +6761 +6763 +6779 +6781 +6791 +6793 +6803 +6823 +6827 +6829 +6833 +6841 +6857 +6863 +6869 +6871 +6883 +6899 +6907 +6911 +6917 +6947 +6949 +6959 +6961 +6967 +6971 +6977 +6983 +6991 +6997 +7001 +7013 +7019 +7027 +7039 +7043 +7057 +7069 +7079 +7103 +7109 +7121 +7127 +7129 +7151 +7159 +7177 +7187 +7193 +7207 +7211 +7213 +7219 +7229 +7237 +7243 +7247 +7253 +7283 +7297 +7307 +7309 +7321 +7331 +7333 +7349 +7351 +7369 +7393 +7411 +7417 +7433 +7451 +7457 +7459 +7477 +7481 +7487 +7489 +7499 +7507 +7517 +7523 +7529 +7537 +7541 +7547 +7549 +7559 +7561 +7573 +7577 +7583 +7589 +7591 +7603 +7607 +7621 +7639 +7643 +7649 +7669 +7673 +7681 +7687 +7691 +7699 +7703 +7717 +7723 +7727 +7741 +7753 +7757 +7759 +7789 +7793 +7817 +7823 +7829 +7841 +7853 +7867 +7873 +7877 +7879 +7883 +7901 +7907 +7919 \ No newline at end of file diff --git a/X10D.Tests/X10D.Tests.csproj b/X10D.Tests/X10D.Tests.csproj index bdba516..f6f97d4 100644 --- a/X10D.Tests/X10D.Tests.csproj +++ b/X10D.Tests/X10D.Tests.csproj @@ -1,35 +1,29 @@ - + - netcoreapp3.1 + net6.0 false - ..\X10D.ruleset - true + enable + true - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + - \ No newline at end of file + + + + + + diff --git a/X10D.Tests/src/Assembly.cs b/X10D.Tests/src/Assembly.cs index 8c11453..f547610 100644 --- a/X10D.Tests/src/Assembly.cs +++ b/X10D.Tests/src/Assembly.cs @@ -1,3 +1 @@ -using System; - -[assembly: CLSCompliant(true)] +[assembly: CLSCompliant(true)] diff --git a/X10D.Tests/src/Collections/ArrayTests.cs b/X10D.Tests/src/Collections/ArrayTests.cs new file mode 100644 index 0000000..cc053ff --- /dev/null +++ b/X10D.Tests/src/Collections/ArrayTests.cs @@ -0,0 +1,48 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class ArrayTests +{ + [TestMethod] + public void AsReadOnlyShouldBeReadOnly() + { + var array = new object[] {1, "f", true}; + var readOnly = array.AsReadOnly(); + Assert.IsNotNull(readOnly); + Assert.IsTrue(readOnly.Count == 3); + + // ReSharper disable once ConvertTypeCheckToNullCheck + Assert.IsTrue(readOnly is IReadOnlyCollection); + } + + [TestMethod] + public void AsReadOnlyNullShouldThrow() + { + object[]? array = null; + Assert.ThrowsException(array!.AsReadOnly); + } + + [TestMethod] + [DataRow] + [DataRow(1)] + [DataRow(1, 2, 3)] + [DataRow(1, 2, 3, 4, 5)] + public void ClearShouldFillDefault(params int[] args) + { + args.Clear(); + + int[] clearedArray = Enumerable.Repeat(0, args.Length).ToArray(); + CollectionAssert.AreEqual(clearedArray, args); + } + + [TestMethod] + public void ClearNullShouldThrow() + { + int[]? array = null; + Assert.ThrowsException(array!.Clear); + Assert.ThrowsException(() => array!.Clear(0, 0)); + } +} diff --git a/X10D.Tests/src/Collections/BoolListTests.cs b/X10D.Tests/src/Collections/BoolListTests.cs new file mode 100644 index 0000000..e67aafc --- /dev/null +++ b/X10D.Tests/src/Collections/BoolListTests.cs @@ -0,0 +1,56 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class BoolListTests +{ + [TestMethod] + public void Pack8Bit_Should_Pack_Correctly() + { + var array = new[] {true, false, true, false, true, false, true, false}; + Assert.AreEqual(85, array.PackByte()); // 01010101 + } + + [TestMethod] + public void Pack16Bit_Should_Pack_Correctly() + { + var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true}; + Assert.AreEqual(2901, array.PackInt16()); // 101101010101 + } + + [TestMethod] + public void Pack32Bit_Should_Pack_Correctly() + { + var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true}; + Assert.AreEqual(2901, array.PackInt32()); // 101101010101 + } + + [TestMethod] + public void Pack64Bit_Should_Pack_Correctly() + { + var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true}; + Assert.AreEqual(2901, array.PackInt64()); // 101101010101 + } + + [TestMethod] + public void Pack_ShouldThrow_GivenLargeArray() + { + bool[] array = Enumerable.Repeat(false, 65).ToArray(); + Assert.ThrowsException(() => array.PackByte()); + Assert.ThrowsException(() => array.PackInt16()); + Assert.ThrowsException(() => array.PackInt32()); + Assert.ThrowsException(() => array.PackInt64()); + } + + [TestMethod] + public void Pack_ShouldThrow_GivenNull() + { + bool[]? array = null; + Assert.ThrowsException(() => array!.PackByte()); + Assert.ThrowsException(() => array!.PackInt16()); + Assert.ThrowsException(() => array!.PackInt32()); + Assert.ThrowsException(() => array!.PackInt64()); + } +} diff --git a/X10D.Tests/src/Collections/ByteTests.cs b/X10D.Tests/src/Collections/ByteTests.cs new file mode 100644 index 0000000..15c28a8 --- /dev/null +++ b/X10D.Tests/src/Collections/ByteTests.cs @@ -0,0 +1,57 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void UnpackBits_ShouldUnpackToArrayCorrectly() + { + bool[] bits = ((byte)0b11010100).Unpack(); + + Assert.AreEqual(8, bits.Length); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + } + + [TestMethod] + public void UnpackBits_ShouldUnpackToSpanCorrectly() + { + Span bits = stackalloc bool[8]; + ((byte)0b11010100).Unpack(bits); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + } + + [TestMethod] + public void UnpackBits_ShouldRepackEqually() + { + Assert.AreEqual(0b11010100, ((byte)0b11010100).Unpack().PackByte()); + } + + [TestMethod] + public void UnpackBits_ShouldThrow_GivenTooSmallSpan() + { + Assert.ThrowsException(() => + { + Span bits = stackalloc bool[0]; + ((byte)0b11010100).Unpack(bits); + }); + } +} diff --git a/X10D.Tests/src/Collections/DictionaryTests.cs b/X10D.Tests/src/Collections/DictionaryTests.cs new file mode 100644 index 0000000..9fb8be2 --- /dev/null +++ b/X10D.Tests/src/Collections/DictionaryTests.cs @@ -0,0 +1,189 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class DictionaryTests +{ + [TestMethod] + public void AddOrUpdate_ShouldAddNewKey_IfNotExists() + { + var dictionary = new Dictionary(); + Assert.IsFalse(dictionary.ContainsKey(1)); + + dictionary.AddOrUpdate(1, "one", (_, _) => string.Empty); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("one", dictionary[1]); + + dictionary.Clear(); + Assert.IsFalse(dictionary.ContainsKey(1)); + + dictionary.AddOrUpdate(1, _ => "one", (_, _) => string.Empty); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("one", dictionary[1]); + + dictionary.Clear(); + Assert.IsFalse(dictionary.ContainsKey(1)); + + dictionary.AddOrUpdate(1, (_, _) => "one", (_, _, _) => string.Empty, 0); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("one", dictionary[1]); + } + + [TestMethod] + public void AddOrUpdate_ShouldUpdateKey_IfExists() + { + var dictionary = new Dictionary {[1] = "one"}; + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("one", dictionary[1]); + + dictionary.AddOrUpdate(1, string.Empty, (_, _) => "two"); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("two", dictionary[1]); + + dictionary.Clear(); + Assert.IsFalse(dictionary.ContainsKey(1)); + dictionary[1] = "one"; + + dictionary.AddOrUpdate(1, _ => string.Empty, (_, _) => "two"); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("two", dictionary[1]); + + dictionary.Clear(); + Assert.IsFalse(dictionary.ContainsKey(1)); + dictionary[1] = "one"; + + dictionary.AddOrUpdate(1, (_, _) => string.Empty, (_, _, _) => "two", 0); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("two", dictionary[1]); + } + + [TestMethod] + public void AddOrUpdate_ShouldThrow_GivenNullDictionary() + { + Dictionary? dictionary = null; + Assert.ThrowsException(() => dictionary!.AddOrUpdate(1, string.Empty, (_, _) => string.Empty)); + Assert.ThrowsException(() => + dictionary!.AddOrUpdate(1, _ => string.Empty, (_, _) => string.Empty)); + Assert.ThrowsException(() => + dictionary!.AddOrUpdate(1, (_, _) => string.Empty, (_, _, _) => string.Empty, 0)); + } + + [TestMethod] + public void AddOrUpdate_ShouldThrow_GivenNullUpdateValueFactory() + { + var dictionary = new Dictionary(); + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, string.Empty, null!)); + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, _ => string.Empty, null!)); + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, (_, _) => string.Empty, null!, 0)); + } + + [TestMethod] + public void AddOrUpdate_ShouldThrow_GivenNullAddValueFactory() + { + var dictionary = new Dictionary(); + Func? addValueFactory = null; + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, addValueFactory!, (_, _) => "one")); + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, null!, (_, _, _) => "one", 0)); + } + + [TestMethod] + public void ToConnectionString_ShouldReturnConnectionString() + { + var dictionary = new Dictionary + { + ["Data Source"] = "localhost", ["Initial Catalog"] = "test", ["Integrated Security"] = "True", ["Foobar"] = null + }; + + string connectionString = dictionary.ToConnectionString(); + Assert.AreEqual("Data Source=localhost;Initial Catalog=test;Integrated Security=True;Foobar=", connectionString); + } + + [TestMethod] + public void ToConnectionString_ShouldReturnTransformedValueConnectionString() + { + var dictionary = new Dictionary + { + ["Data Source"] = "localhost", ["Initial Catalog"] = "test", ["Integrated Security"] = "True", ["Foobar"] = null + }; + + string connectionString = dictionary.ToConnectionString(v => v?.ToUpperInvariant()); + Assert.AreEqual("Data Source=LOCALHOST;Initial Catalog=TEST;Integrated Security=TRUE;Foobar=", connectionString); + } + + [TestMethod] + public void ToConnectionString_ShouldReturnTransformedKeyValueConnectionString() + { + var dictionary = new Dictionary + { + ["Data Source"] = "localhost", ["Initial Catalog"] = "test", ["Integrated Security"] = "True", ["Foobar"] = null + }; + + string connectionString = dictionary.ToConnectionString(k => k.ToUpper(), v => v?.ToUpperInvariant()); + Assert.AreEqual("DATA SOURCE=LOCALHOST;INITIAL CATALOG=TEST;INTEGRATED SECURITY=TRUE;FOOBAR=", connectionString); + } + + [TestMethod] + public void ToConnectionString_ShouldThrow_GivenNullSource() + { + Dictionary? dictionary = null; + Assert.ThrowsException(() => dictionary!.ToConnectionString()); + Assert.ThrowsException(() => dictionary!.ToConnectionString(null!)); + Assert.ThrowsException(() => dictionary!.ToConnectionString(null!, null!)); + } + + [TestMethod] + public void ToConnectionString_ShouldThrow_GivenNullSelector() + { + var dictionary = new Dictionary(); + Assert.ThrowsException(() => dictionary.ToConnectionString(null!)); + Assert.ThrowsException(() => dictionary.ToConnectionString(null!, _ => _)); + Assert.ThrowsException(() => dictionary.ToConnectionString(_ => _, null!)); + } + + [TestMethod] + public void ToGetParameters_ShouldReturnParameters() + { + var dictionary = new Dictionary {["id"] = "1", ["user"] = "hello world", ["foo"] = "bar"}; + + string queryString = dictionary.ToGetParameters(); + Assert.AreEqual("id=1&user=hello+world&foo=bar", queryString); + } + + [TestMethod] + public void ToGetParameters_ShouldReturnTransformedValueParameters() + { + var dictionary = new Dictionary {["id"] = "1", ["user"] = "hello world", ["foo"] = null}; + + string queryString = dictionary.ToGetParameters(v => v?.ToUpper()); + Assert.AreEqual("id=1&user=HELLO+WORLD&foo=", queryString); + } + + [TestMethod] + public void ToGetParameters_ShouldReturnTransformedKeyValueParameters() + { + var dictionary = new Dictionary {["id"] = "1", ["user"] = "hello world", ["foo"] = null}; + + string queryString = dictionary.ToGetParameters(k => k.ToUpper(), v => v?.ToUpper()); + Assert.AreEqual("ID=1&USER=HELLO+WORLD&FOO=", queryString); + } + + [TestMethod] + public void ToGetParameters_ShouldThrow_GivenNullSource() + { + Dictionary? dictionary = null; + Assert.ThrowsException(() => dictionary!.ToGetParameters()); + Assert.ThrowsException(() => dictionary!.ToGetParameters(null!)); + Assert.ThrowsException(() => dictionary!.ToGetParameters(null!, null!)); + } + + [TestMethod] + public void ToGetParameters_ShouldThrow_GivenNullSelector() + { + var dictionary = new Dictionary(); + Assert.ThrowsException(() => dictionary.ToGetParameters(null!)); + Assert.ThrowsException(() => dictionary.ToGetParameters(null!, _ => _)); + Assert.ThrowsException(() => dictionary.ToGetParameters(_ => _, null!)); + } +} diff --git a/X10D.Tests/src/Collections/EnumerableTests.cs b/X10D.Tests/src/Collections/EnumerableTests.cs new file mode 100644 index 0000000..6d990ee --- /dev/null +++ b/X10D.Tests/src/Collections/EnumerableTests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class EnumerableTests +{ + [TestMethod] + public void Shuffled_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((List?)null)!.Shuffled()); + } + + [TestMethod] + public void Shuffled_ShouldReorder_GivenNotNull() + { + int[] array = Enumerable.Range(1, 52).ToArray(); // 52! chance of being shuffled to the same order + int[] shuffled = array[..]; + + CollectionAssert.AreEqual(array, shuffled); + + shuffled = array.Shuffled().ToArray(); + CollectionAssert.AreNotEqual(array, shuffled); + } +} diff --git a/X10D.Tests/src/Collections/Int16Tests.cs b/X10D.Tests/src/Collections/Int16Tests.cs new file mode 100644 index 0000000..d077744 --- /dev/null +++ b/X10D.Tests/src/Collections/Int16Tests.cs @@ -0,0 +1,67 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void UnpackBits_ShouldUnpackToArrayCorrectly() + { + bool[] bits = ((short)0b11010100).Unpack(); + + Assert.AreEqual(16, bits.Length); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 16; index++) + { + Assert.IsFalse(bits[index]); + } + } + + [TestMethod] + public void UnpackBits_ShouldUnpackToSpanCorrectly() + { + Span bits = stackalloc bool[16]; + ((short)0b11010100).Unpack(bits); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 16; index++) + { + Assert.IsFalse(bits[index]); + } + } + + [TestMethod] + public void UnpackBits_ShouldRepackEqually() + { + Assert.AreEqual(0b11010100, ((short)0b11010100).Unpack().PackInt16()); + } + + [TestMethod] + public void UnpackBits_ShouldThrow_GivenTooSmallSpan() + { + Assert.ThrowsException(() => + { + Span bits = stackalloc bool[0]; + ((short)0b11010100).Unpack(bits); + }); + } +} diff --git a/X10D.Tests/src/Collections/Int32Tests.cs b/X10D.Tests/src/Collections/Int32Tests.cs new file mode 100644 index 0000000..cd86c75 --- /dev/null +++ b/X10D.Tests/src/Collections/Int32Tests.cs @@ -0,0 +1,67 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void UnpackBits_ShouldUnpackToArrayCorrectly() + { + bool[] bits = 0b11010100.Unpack(); + + Assert.AreEqual(32, bits.Length); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 32; index++) + { + Assert.IsFalse(bits[index]); + } + } + + [TestMethod] + public void UnpackBits_ShouldUnpackToSpanCorrectly() + { + Span bits = stackalloc bool[32]; + 0b11010100.Unpack(bits); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 32; index++) + { + Assert.IsFalse(bits[index]); + } + } + + [TestMethod] + public void UnpackBits_ShouldRepackEqually() + { + Assert.AreEqual(0b11010100, 0b11010100.Unpack().PackInt32()); + } + + [TestMethod] + public void UnpackBits_ShouldThrow_GivenTooSmallSpan() + { + Assert.ThrowsException(() => + { + Span bits = stackalloc bool[0]; + 0b11010100.Unpack(bits); + }); + } +} diff --git a/X10D.Tests/src/Collections/Int64Tests.cs b/X10D.Tests/src/Collections/Int64Tests.cs new file mode 100644 index 0000000..2622863 --- /dev/null +++ b/X10D.Tests/src/Collections/Int64Tests.cs @@ -0,0 +1,71 @@ +using System.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void UnpackBits_ShouldUnpackToArrayCorrectly() + { + bool[] bits = 0b11010100L.Unpack(); + + Assert.AreEqual(64, bits.Length); + + Trace.WriteLine(Convert.ToString(0b11010100L, 2)); + Trace.WriteLine(string.Join("", bits.Select(b => b ? "1" : "0"))); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 64; index++) + { + Assert.IsFalse(bits[index], index.ToString()); + } + } + + [TestMethod] + public void UnpackBits_ShouldUnpackToSpanCorrectly() + { + Span bits = stackalloc bool[64]; + 0b11010100L.Unpack(bits); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 64; index++) + { + Assert.IsFalse(bits[index], index.ToString()); + } + } + + [TestMethod] + public void UnpackBits_ShouldRepackEqually() + { + Assert.AreEqual(0b11010100L, 0b11010100L.Unpack().PackInt64()); + } + + [TestMethod] + public void UnpackBits_ShouldThrow_GivenTooSmallSpan() + { + Assert.ThrowsException(() => + { + Span bits = stackalloc bool[0]; + 0b11010100L.Unpack(bits); + }); + } +} diff --git a/X10D.Tests/src/Collections/ListTests.cs b/X10D.Tests/src/Collections/ListTests.cs new file mode 100644 index 0000000..3be91f8 --- /dev/null +++ b/X10D.Tests/src/Collections/ListTests.cs @@ -0,0 +1,113 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class ListTests +{ + [TestMethod] + [DataRow(1)] + [DataRow(1, 2, 3)] + [DataRow(1, 2, 3, 4, 5)] + public void Fill_ShouldGiveHomogenousList_GivenValue(params int[] args) + { + int[] all42 = Enumerable.Repeat(42, args.Length).ToArray(); + var list = new List(args); + + CollectionAssert.AreEqual(args, list); + + args.Fill(42); + list.Fill(42); + + CollectionAssert.AreEqual(args, list); + CollectionAssert.AreEqual(all42, args); + CollectionAssert.AreEqual(all42, list); + } + + [TestMethod] + [DataRow(1)] + [DataRow(1, 2, 3)] + [DataRow(1, 2, 3, 4, 5)] + public void SlicedFill_ShouldLeaveFirstElement_GivenStartIndex1(params int[] args) + { + int first = args[0]; + args.Fill(1, 1, args.Length - 1); + + int[] comparison = Enumerable.Repeat(1, args.Length - 1).ToArray(); + Assert.AreEqual(first, args[0]); + CollectionAssert.AreEqual(comparison, args[1..]); + } + + [TestMethod] + public void Fill_ShouldThrow_GivenExceededCount() + { + int[] array = Array.Empty(); + var list = new List(); + Assert.ThrowsException(() => array.Fill(0, 0, 1)); + Assert.ThrowsException(() => list.Fill(0, 0, 1)); + } + + [TestMethod] + public void Fill_ShouldThrow_GivenNegativeCount() + { + int[] array = Array.Empty(); + var list = new List(); + Assert.ThrowsException(() => array.Fill(0, 0, -1)); + Assert.ThrowsException(() => list.Fill(0, 0, -1)); + } + + [TestMethod] + public void Fill_ShouldThrow_GivenNegativeStartIndex() + { + int[] array = Array.Empty(); + var list = new List(); + Assert.ThrowsException(() => array.Fill(0, -1, 0)); + Assert.ThrowsException(() => list.Fill(0, -1, 0)); + } + + [TestMethod] + public void Fill_ShouldThrow_GivenNull() + { + int[]? array = null; + List? list = null; + Assert.ThrowsException(() => array!.Fill(0)); + Assert.ThrowsException(() => list!.Fill(0)); + Assert.ThrowsException(() => array!.Fill(0, 0, 0)); + Assert.ThrowsException(() => list!.Fill(0, 0, 0)); + } + + [TestMethod] + public void Shuffle_ShouldReorder_GivenNotNull() + { + var list = new List(Enumerable.Range(1, 52)); // 52! chance of being shuffled to the same order + var shuffled = new List(list); + + CollectionAssert.AreEqual(list, shuffled); + + shuffled.Shuffle(); + + CollectionAssert.AreNotEqual(list, shuffled); + } + + [TestMethod] + public void Shuffle_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((List?)null)!.Shuffle()); + } + + [TestMethod] + public void Random_ShouldReturnContainedObject_GivenNotNull() + { + var list = new List(Enumerable.Range(1, 52)); // 52! chance of being shuffled to the same order + int random = list.Random(); + + Assert.IsTrue(list.Contains(random)); + } + + [TestMethod] + public void Random_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((List?)null)!.Random()); + } +} diff --git a/X10D.Tests/src/Core/BooleanTests.cs b/X10D.Tests/src/Core/BooleanTests.cs deleted file mode 100644 index 15bad0f..0000000 --- a/X10D.Tests/src/Core/BooleanTests.cs +++ /dev/null @@ -1,214 +0,0 @@ -namespace X10D.Tests.Core -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class BooleanTests - { - /// - /// Tests for . - /// - [TestMethod] - public void And() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsTrue(a.And(b)); - Assert.IsFalse(b.And(c)); - Assert.IsFalse(c.And(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void NAnd() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsFalse(a.NAnd(b)); - Assert.IsTrue(b.NAnd(c)); - Assert.IsTrue(c.NAnd(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void NOr() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsFalse(a.NOr(b)); - Assert.IsFalse(b.NOr(c)); - Assert.IsTrue(c.NOr(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Not() - { - const bool a = true; - const bool b = false; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.IsFalse(a.Not()); - Assert.IsTrue(b.Not()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Or() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsTrue(a.Or(b)); - Assert.IsTrue(b.Or(c)); - Assert.IsFalse(c.Or(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToByte() - { - const bool a = true; - const bool b = false; - const byte c = 1; - const byte d = 0; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.AreEqual(c, a.ToByte()); - Assert.AreEqual(d, b.ToByte()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToInt16() - { - const bool a = true; - const bool b = false; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.AreEqual(1, a.ToInt16()); - Assert.AreEqual(0, b.ToInt16()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToInt32() - { - const bool a = true; - const bool b = false; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.AreEqual(1, a.ToInt32()); - Assert.AreEqual(0, b.ToInt32()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToInt64() - { - const bool a = true; - const bool b = false; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.AreEqual(1L, a.ToInt64()); - Assert.AreEqual(0L, b.ToInt64()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void XNOr() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsTrue(a.XNOr(b)); - Assert.IsFalse(b.XNOr(c)); - Assert.IsTrue(c.XNOr(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void XOr() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsFalse(a.XOr(b)); - Assert.IsTrue(b.XOr(c)); - Assert.IsFalse(c.XOr(d)); - } - } -} diff --git a/X10D.Tests/src/Core/ByteTests.cs b/X10D.Tests/src/Core/ByteTests.cs deleted file mode 100644 index e97061a..0000000 --- a/X10D.Tests/src/Core/ByteTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace X10D.Tests.Core -{ - using System.Collections.Generic; - using System.Text; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class ByteTests - { - /// - /// Tests for . - /// - [TestMethod] - public void AsString() - { - byte[] a = { 0x00, 0x73, 0xc6, 0xff }; - Assert.AreEqual("00-73-C6-FF", a.AsString()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetInt16() - { - byte[] a = { 0xF3, 0x3F }; - Assert.AreEqual(16371, a.GetInt16()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetInt32() - { - byte[] a = { 0xB0, 0x0B, 0x13, 0x5F }; - Assert.AreEqual(1595083696, a.GetInt32()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetInt64() - { - byte[] a = { 0xB0, 0x0B, 0x13, 0x50, 0x05, 0x31, 0xB0, 0x0B }; - Assert.AreEqual(842227029206305712L, a.GetInt64()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetString() - { - byte[] a = { 0x48, 0xc3, 0xa9, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64 }; - Assert.AreEqual("H\u00e9llo World", a.GetString()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetStringAscii() - { - byte[] a = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64 }; - Assert.AreEqual("Hello World", a.GetString(Encoding.ASCII)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetUInt16() - { - byte[] a = { 0xF3, 0x3F }; - Assert.AreEqual(16371, a.GetUInt16()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetUInt32() - { - byte[] a = { 0xB0, 0x0B, 0x13, 0x5F }; - Assert.AreEqual(1595083696U, a.GetUInt32()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetUInt64() - { - byte[] a = { 0xB0, 0x0B, 0x13, 0x50, 0x05, 0x31, 0xB0, 0x0B }; - Assert.AreEqual(842227029206305712UL, a.GetUInt64()); - } - } -} diff --git a/X10D.Tests/src/Core/CharTests.cs b/X10D.Tests/src/Core/CharTests.cs deleted file mode 100644 index d462d59..0000000 --- a/X10D.Tests/src/Core/CharTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using System.Linq; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class CharTests - { - /// - /// Tests for . - /// - [TestMethod] - public void Random() - { - var set = "abcdefghijklmnopqrstuvwxyz".ToCharArray(); - var random = set.Random(20); - - Assert.IsTrue(random.All(c => Array.IndexOf(set, c) >= 0)); - Assert.IsFalse(random.Any(c => Array.IndexOf(set, c) < -1)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Repeat() - { - Assert.AreEqual("aaaaaaaaaa", 'a'.Repeat(10)); - } - } -} diff --git a/X10D.Tests/src/Core/ComparableTests.cs b/X10D.Tests/src/Core/ComparableTests.cs deleted file mode 100644 index c477f9d..0000000 --- a/X10D.Tests/src/Core/ComparableTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace X10D.Tests.Core -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class ComparableTests - { - /// - /// Tests for . - /// - [TestMethod] - public void Between() - { - Assert.IsTrue(5.Between(2, 7)); - Assert.IsTrue(10.Between(9, 11)); - Assert.IsFalse(100.Between(80, 99)); - } - } -} diff --git a/X10D.Tests/src/Core/ConvertibleTests.cs b/X10D.Tests/src/Core/ConvertibleTests.cs deleted file mode 100644 index cc11fc2..0000000 --- a/X10D.Tests/src/Core/ConvertibleTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class ConvertibleTests - { - /// - /// Tests for . - /// - [TestMethod] - public void To() - { - Assert.AreEqual(2, "2".To()); - Assert.AreEqual("12.5", 12.50.To()); - Assert.IsTrue("True".To()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToOrDefault() - { - Assert.AreEqual(2, "2".ToOrDefault()); - Assert.AreEqual("12.5", 12.50.ToOrDefault()); - Assert.IsTrue("True".ToOrDefault()); - Assert.ThrowsException(() => "Foo".ToOrDefault()); - Assert.IsTrue("1.5".ToOrDefault(out float f)); - Assert.AreEqual(1.5f, f); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToOrNull() - { - Assert.IsFalse("foo".ToOrNull(out ConvertibleTests t)); - Assert.IsNull(t); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToOrOther() - { - Assert.AreEqual(2.0, "Foo".ToOrOther(2.0)); - Assert.IsFalse("Foo".ToOrOther(out var d, 2.0)); - Assert.AreEqual(2.0, d); - } - } -} diff --git a/X10D.Tests/src/Core/CoreTests.cs b/X10D.Tests/src/Core/CoreTests.cs new file mode 100644 index 0000000..57bf03d --- /dev/null +++ b/X10D.Tests/src/Core/CoreTests.cs @@ -0,0 +1,76 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class CoreTests +{ + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsArrayValue_ShouldBeLength1_GivenValue(object o) + { + object[] array = o.AsArrayValue()!; + Assert.IsNotNull(array); + Assert.IsTrue(array.Length == 1); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsArrayValue_ShouldContainValue_Given_Value(object o) + { + object[] array = o.AsArrayValue()!; + Assert.IsNotNull(array); + Assert.AreEqual(o, array[0]); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsEnumerableValue_ShouldBeLength1_GivenValue(object o) + { + IEnumerable enumerable = o.AsEnumerableValue()!; + Assert.IsNotNull(enumerable); + Assert.IsTrue(enumerable.Count() == 1); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsEnumerableValue_ShouldContainValue_Given_Value(object o) + { + IEnumerable enumerable = o.AsEnumerableValue()!; + Assert.IsNotNull(enumerable); + Assert.AreEqual(o, enumerable.ElementAt(0)); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void RepeatValue_ShouldContainRepeatedValue_GivenValue(object o) + { + IEnumerable enumerable = o.RepeatValue(10); + Assert.IsNotNull(enumerable); + + object[] array = enumerable.ToArray(); + Assert.AreEqual(10, array.Length); + CollectionAssert.AreEqual(new[] {o, o, o, o, o, o, o, o, o, o}, array); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void RepeatValue_ShouldThrow_GivenNegativeCount(object o) + { + // we must force enumeration via ToArray() to ensure the exception is thrown + Assert.ThrowsException(() => o.RepeatValue(-1).ToArray()); + } +} diff --git a/X10D.Tests/src/Core/DateTimeTests.cs b/X10D.Tests/src/Core/DateTimeTests.cs deleted file mode 100644 index a670af9..0000000 --- a/X10D.Tests/src/Core/DateTimeTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class DateTimeTests - { - /// - /// Tests for . - /// - [TestMethod] - public void Age() - { - // no choice but to create dynamic based on today's date. - // age varies with time - var now = DateTime.Now; - var dt = new DateTime(now.Year - 18, 1, 1); - - Assert.AreEqual(18, dt.Age()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void First() - { - var dt = new DateTime(2018, 6, 20); - - Assert.AreEqual(4, dt.First(DayOfWeek.Monday).Day); - } - - /// - /// Tests for . - /// - [TestMethod] - public void FirstDayOfMonth() - { - var dt = new DateTime(2018, 6, 20); - var first = dt.FirstDayOfMonth(); - - Assert.AreEqual(dt.Year, first.Year); - Assert.AreEqual(dt.Month, first.Month); - Assert.AreEqual(1, first.Day); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Last() - { - { - var dt = new DateTime(2019, 12, 1); - var last = dt.Last(DayOfWeek.Wednesday); - - Assert.AreEqual(dt.Year, last.Year); - Assert.AreEqual(dt.Month, last.Month); - Assert.AreEqual(25, last.Day); - } - - { - var dt = new DateTime(2020, 4, 14); - var last = dt.Last(DayOfWeek.Friday); - - Assert.AreEqual(dt.Year, last.Year); - Assert.AreEqual(dt.Month, last.Month); - Assert.AreEqual(24, last.Day); - - last = dt.Last(DayOfWeek.Thursday); - Assert.AreEqual(dt.Year, last.Year); - Assert.AreEqual(dt.Month, last.Month); - Assert.AreEqual(30, last.Day); - } - } - - /// - /// Tests for . - /// - [TestMethod] - public void LastDayOfMonth() - { - var dt = new DateTime(2016, 2, 4); - var last = dt.LastDayOfMonth(); - - Assert.AreEqual(dt.Year, last.Year); - Assert.AreEqual(dt.Month, last.Month); - Assert.AreEqual(29, last.Day); // 2016 is a leap year - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToUnixTimestamp() - { - var dt = new DateTime(2015, 10, 21, 1, 0, 0, DateTimeKind.Utc); - var unix = dt.ToUnixTimeStamp(); - - Assert.AreEqual(1445389200L, unix); - } - } -} diff --git a/X10D.Tests/src/Core/DictionaryTests.cs b/X10D.Tests/src/Core/DictionaryTests.cs deleted file mode 100644 index 983fc9b..0000000 --- a/X10D.Tests/src/Core/DictionaryTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace X10D.Tests.Core -{ - using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class DictionaryTests - { - /// - /// Tests for . - /// - [TestMethod] - public void ToConnectionString() - { - var dictionary = new Dictionary - { - { "username", "Foo" }, { "password", "Foo Bar" }, { "port", 3306 }, - }; - - var connectionString = dictionary.ToConnectionString(); - Assert.AreEqual("username=Foo;password=\"Foo Bar\";port=3306", connectionString); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToGetParameters() - { - var dictionary = new Dictionary - { - { "username", "Foo" }, { "password", "Foo Bar" }, { "port", 3306 }, - }; - - var getParameterString = dictionary.ToGetParameters(); - Assert.AreEqual("username=Foo&password=Foo+Bar&port=3306", getParameterString); - } - } -} diff --git a/X10D.Tests/src/Core/DoubleTests.cs b/X10D.Tests/src/Core/DoubleTests.cs deleted file mode 100644 index 9990370..0000000 --- a/X10D.Tests/src/Core/DoubleTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class DoubleTests - { - /// - /// Tests for . - /// - [TestMethod] - public void Clamp() - { - Assert.AreEqual(2.0, 3.0.Clamp(1.0, 2.0)); - Assert.AreEqual(1.0, (-3.0).Clamp(1.0, 2.0)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void DegreesToRadians() - { - Assert.AreEqual(Math.PI, 180.0.DegreesToRadians()); - Assert.AreEqual(Math.PI * 1.5, 270.0.DegreesToRadians()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetBytes() - { - CollectionAssert.AreEqual( - new byte[] { 0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 0x09, 0x40 }, - Math.PI.GetBytes()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void IsEven() - { - Assert.IsTrue(2.0.IsEven()); - Assert.IsFalse(1.0.IsEven()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void IsOdd() - { - Assert.IsFalse(2.0.IsOdd()); - Assert.IsTrue(1.0.IsOdd()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void RadiansToDegrees() - { - Assert.AreEqual(180.0, Math.PI.RadiansToDegrees()); - Assert.AreEqual(360.0, (2.0 * Math.PI).RadiansToDegrees()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Round() - { - Assert.AreEqual(5.0, 3.5.Round(5)); - Assert.AreEqual(5.0, 7.0.Round(5)); - Assert.AreEqual(10.0, 7.5.Round(5)); - } - } -} diff --git a/X10D.Tests/src/Core/EnumTests.cs b/X10D.Tests/src/Core/EnumTests.cs new file mode 100644 index 0000000..8e7889d --- /dev/null +++ b/X10D.Tests/src/Core/EnumTests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class EnumTests +{ + // Microsoft wrongfully decided to have Sunday be 0, Monday be 1, etc. + // I personally hate this, Sunday is not the first day of the week. + // it's clearly Monday as defined by ISO 8601. + // but Microsoft can't fix this without breaking compatibility. + // I have feelings... + + [TestMethod] + public void Next() + { + Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Sunday.Next()); + Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Monday.Next()); + Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Tuesday.Next()); + Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Wednesday.Next()); + Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Thursday.Next()); + Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Friday.Next()); + Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Saturday.Next()); // Saturday is the "last" day. wrap to "first" + } + + [TestMethod] + public void NextUnchecked() + { + Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Sunday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Monday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Tuesday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Wednesday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Thursday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Friday.NextUnchecked()); + Assert.ThrowsException(() => DayOfWeek.Saturday.NextUnchecked()); + } + + [TestMethod] + public void Previous() + { + Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Sunday.Previous()); // Sunday is the "first" day. wrap to "last" + Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Monday.Previous()); + Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Tuesday.Previous()); + Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Wednesday.Previous()); + Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Thursday.Previous()); + Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Friday.Previous()); + Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Saturday.Previous()); + } + + [TestMethod] + public void PreviousUnchecked() + { + Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Monday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Tuesday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Wednesday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Thursday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Friday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Saturday.PreviousUnchecked()); + Assert.ThrowsException(() => DayOfWeek.Sunday.PreviousUnchecked()); + } +} diff --git a/X10D.Tests/src/Core/EnumerableTests.cs b/X10D.Tests/src/Core/EnumerableTests.cs index 6da9ac3..5b6deba 100644 --- a/X10D.Tests/src/Core/EnumerableTests.cs +++ b/X10D.Tests/src/Core/EnumerableTests.cs @@ -1,55 +1,20 @@ -namespace X10D.Tests.Core +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class EnumerableTests { - using System.Collections.Generic; - using System.Linq; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class EnumerableTests + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsEnumerable_ShouldWrapElement_GivenValue(object o) { - /// - /// Tests for using an array of . - /// - [TestMethod] - public void SplitByte() - { - byte[] foo = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; - IEnumerable> chunks = foo.Split(2).ToArray(); - - Assert.AreEqual(4, chunks.Count()); - CollectionAssert.AreEqual(new byte[] { 0x01, 0x02 }, chunks.ElementAt(0).ToList()); - CollectionAssert.AreEqual(new byte[] { 0x03, 0x04 }, chunks.ElementAt(1).ToList()); - CollectionAssert.AreEqual(new byte[] { 0x05, 0x06 }, chunks.ElementAt(2).ToList()); - CollectionAssert.AreEqual(new byte[] { 0x07, 0x08 }, chunks.ElementAt(3).ToList()); - - // test exceeding chunk size - chunks = foo.Split(foo.Length + 10).ToArray(); - Assert.AreEqual(1, chunks.Count()); - CollectionAssert.AreEqual(foo, chunks.SelectMany(c => c).ToList()); - } - - /// - /// Tests for using an array of . - /// - [TestMethod] - public void SplitInt32() - { - int[] foo = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; - IEnumerable> chunks = foo.Split(2).ToArray(); - - Assert.AreEqual(4, chunks.Count()); - CollectionAssert.AreEqual(new[] { 0x01, 0x02 }, chunks.ElementAt(0).ToList()); - CollectionAssert.AreEqual(new[] { 0x03, 0x04 }, chunks.ElementAt(1).ToList()); - CollectionAssert.AreEqual(new[] { 0x05, 0x06 }, chunks.ElementAt(2).ToList()); - CollectionAssert.AreEqual(new[] { 0x07, 0x08 }, chunks.ElementAt(3).ToList()); - - // test exceeding chunk size - chunks = foo.Split(foo.Length + 10).ToArray(); - Assert.AreEqual(1, chunks.Count()); - CollectionAssert.AreEqual(foo, chunks.SelectMany(c => c).ToList()); - } + IEnumerable array = o.AsEnumerableValue().ToArray(); // prevent multiple enumeration of IEnumerable + Assert.IsNotNull(array); + Assert.IsTrue(array.Count() == 1); + Assert.AreEqual(o, array.ElementAt(0)); } } diff --git a/X10D.Tests/src/Core/RandomTests.cs b/X10D.Tests/src/Core/RandomTests.cs new file mode 100644 index 0000000..41d182b --- /dev/null +++ b/X10D.Tests/src/Core/RandomTests.cs @@ -0,0 +1,245 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class RandomTests +{ + [TestMethod] + public void NextBoolean_ShouldBeFalse_GivenSeed1234() + { + var random = new Random(1234); + Assert.IsFalse(random.NextBoolean()); + } + + [TestMethod] + public void NextBoolean_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextBoolean()); + } + + [TestMethod] + public void NextByte_ShouldBe101_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(101, random.NextByte()); + } + + [TestMethod] + public void NextByte_WithMax10_ShouldBe3_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3, random.NextByte(10)); + } + + [TestMethod] + public void NextByte_WithMin0Max10_ShouldBe3_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3, random.NextByte(0, 10)); + } + + [TestMethod] + public void NextByte_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextByte()); + Assert.ThrowsException(() => random!.NextByte(10)); + Assert.ThrowsException(() => random!.NextByte(0, 10)); + } + + [TestMethod] + public void NextDouble_WithMax10_ShouldBe3point9908097935797695_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3.9908097935797695, random.NextDouble(10)); + } + + [TestMethod] + public void NextDouble_WithMin0Max10_ShouldBe3point9908097935797695_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3.9908097935797695, random.NextDouble(0, 10)); + } + + [TestMethod] + public void NextDouble_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextDouble(10)); + Assert.ThrowsException(() => random!.NextDouble(0, 10)); + } + + [TestMethod] + public void NextDouble_ShouldThrow_GivenMaxLessThan0() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextDouble(-1)); + } + + [TestMethod] + public void NextDouble_ShouldThrow_GivenMaxLessThanMin() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextDouble(0, -1)); + } + + [TestMethod] + public void NextEnum_ShouldBeTuesday_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(DayOfWeek.Tuesday, random.Next()); + } + + [TestMethod] + public void NextEnum_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.Next()); + } + + [TestMethod] + public void NextFrom_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextFrom("")); + } + + [TestMethod] + public void NextFrom_ShouldThrow_GivenNullSource() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextFrom((string?)null!)); + } + + [TestMethod] + public void NextFrom_ShouldEnumerate_GivenNonList() + { + IEnumerable Source() + { + yield return 0; + } + + var random = new Random(1234); + Assert.AreEqual(0, random.NextFrom(Source())); + } + + [TestMethod] + public void NextInt16_ShouldBe13076_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(13076, random.NextInt16()); + } + + [TestMethod] + public void NextInt16_WithMax10_ShouldBe3_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3, random.NextInt16(10)); + } + + [TestMethod] + public void NextInt16_WithMin0Max10_ShouldBe3_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3, random.NextInt16(0, 10)); + } + + [TestMethod] + public void NextInt16_ShouldThrow_GivenMaxLessThan0() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextInt16(-1)); + } + + [TestMethod] + public void NextInt16_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextInt16()); + Assert.ThrowsException(() => random!.NextInt16(10)); + Assert.ThrowsException(() => random!.NextInt16(0, 10)); + } + + [TestMethod] + public void NextSingle_WithMax10_ShouldBe3point99081_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3.99081f, random.NextSingle(10)); + } + + [TestMethod] + public void NextSingle_WithMin0Max10_ShouldBe3point99081_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3.99081f, random.NextSingle(0, 10)); + } + + [TestMethod] + public void NextSingle_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextSingle(10)); + Assert.ThrowsException(() => random!.NextSingle(0, 10)); + } + + [TestMethod] + public void NextSingle_ShouldThrow_GivenMaxLessThan0() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextSingle(-1)); + } + + [TestMethod] + public void NextSingle_ShouldThrow_GivenMaxLessThanMin() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextSingle(0, -1)); + } + + [TestMethod] + public void NextString_ShouldBe_kxiyiyvnqi_GivenSeed1234() + { + const string alphabet = "abcdefghijklmnopqrstuvwxyz"; + + var random = new Random(1234); + Assert.AreEqual("kxiyiyvnqi", random.NextString(alphabet.ToCharArray(), 10)); + } + + [TestMethod] + public void NextString_ShouldBeEmpty_GivenLength0() + { + var random = new Random(1234); + Assert.AreEqual(string.Empty, random.NextString(ArraySegment.Empty, 0)); + } + + [TestMethod] + public void NextString_ShouldBeLength1_GivenLength1() + { + var random = new Random(1234); + Assert.AreEqual(1, random.NextString("hello world".ToCharArray(), 1).Length); + } + + [TestMethod] + public void NextString_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextString(ArraySegment.Empty, 0)); + } + + [TestMethod] + public void NextString_ShouldThrow_GivenNullSource() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextString(null!, 0)); + } + + [TestMethod] + public void NextString_ShouldThrow_GivenNegativeLength() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextString(ArraySegment.Empty, -1)); + } +} diff --git a/X10D.Tests/src/Core/ReflectionTests.cs b/X10D.Tests/src/Core/ReflectionTests.cs deleted file mode 100644 index b182a4b..0000000 --- a/X10D.Tests/src/Core/ReflectionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using System.ComponentModel; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class ReflectionTests - { - /// - /// Test for . - /// - [TestMethod] - public void GetDefaultValue() - { - var klass = new TestClass(); - - foreach (var property in klass.GetType().GetProperties()) - { - Assert.AreEqual("Foo", property.GetDefaultValue()); - } - } - - /// - /// Test for . - /// - [TestMethod] - public void GetDescription() - { - var klass = new TestClass(); - - foreach (var property in klass.GetType().GetProperties()) - { - Assert.AreEqual("Test description", property.GetDescription()); - } - } - - /// - /// Test for . - /// - [TestMethod] - public void SelectFromCustomAttribute() - { - var klass = new TestClass(); - - foreach (var property in klass.GetType().GetProperties()) - { - var value = property.SelectFromCustomAttribute(a => a.TestValue); - Assert.AreEqual("Bar", value); - } - } - - [AttributeUsage(AttributeTargets.Property)] - private sealed class TestAttribute : Attribute - { - public string TestValue { get; set; } - } - - private sealed class TestClass - { - [System.ComponentModel.Description("Test description")] - [DefaultValue("Foo")] - [Test(TestValue = "Bar")] - public string TestProperty { get; set; } - } - } -} diff --git a/X10D.Tests/src/Core/StringTests.cs b/X10D.Tests/src/Core/StringTests.cs deleted file mode 100644 index 6d58576..0000000 --- a/X10D.Tests/src/Core/StringTests.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace X10D.Tests.Core -{ - using System.Linq; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class StringTests - { - /// - /// Tests for . - /// - [TestMethod] - public void AsNullIfEmpty() - { - Assert.AreEqual(null, string.Empty.AsNullIfEmpty()); - Assert.AreEqual(null, ((string)null).AsNullIfEmpty()); - Assert.AreEqual(" ", " ".AsNullIfEmpty()); - Assert.AreEqual("foo", "foo".AsNullIfEmpty()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void AsNullIfWhiteSpace() - { - Assert.AreEqual(null, string.Empty.AsNullIfWhiteSpace()); - Assert.AreEqual(null, ((string)null).AsNullIfWhiteSpace()); - Assert.AreEqual(null, " ".AsNullIfWhiteSpace()); - Assert.AreEqual("foo", "foo".AsNullIfWhiteSpace()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Repeat() - { - Assert.AreEqual("foofoofoofoofoo", "foo".Repeat(5)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Reverse() - { - Assert.AreEqual("dlroW olleH", StringExtensions.Reverse("Hello World")); - Assert.AreEqual("Foobar", StringExtensions.Reverse("rabooF")); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Split() - { - const string str = "Hello World"; - - // ReSharper disable once SuggestVarOrType_Elsewhere - var arr = str.Split(2).ToArray(); - CollectionAssert.AreEqual(new[] { "He", "ll", "o ", "Wo", "rl", "d" }, arr); - } - - /// - /// Tests for . - /// - [TestMethod] - public void WithAlternative() - { - Assert.AreEqual("Hello", "Hello".WithAlternative("Discarded")); - Assert.AreEqual("Alternative", string.Empty.WithAlternative("Alternative")); - Assert.AreEqual(" ", " ".WithAlternative("Discarded")); - Assert.AreEqual("Alternative", " ".WithAlternative("Alternative", true)); - Assert.AreEqual("Alternative", ((string)null).WithAlternative("Alternative")); - } - } -} diff --git a/X10D.Tests/src/Core/TimeSpanParserTests.cs b/X10D.Tests/src/Core/TimeSpanParserTests.cs deleted file mode 100644 index bbbb5e7..0000000 --- a/X10D.Tests/src/Core/TimeSpanParserTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class TimeSpanParserTests - { - /// - /// Tests for . - /// - [TestMethod] - public void TestParser() - { - Assert.AreEqual(TimeSpan.FromHours(3), "3h".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromMinutes(2.5), "2.5m".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromHours(1), "60m".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromDays(1), "1d".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromDays(8), "1w 1d".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromDays(8), "1w1d".ToTimeSpan()); - } - } -} diff --git a/X10D.Tests/src/Drawing/RandomTests.cs b/X10D.Tests/src/Drawing/RandomTests.cs new file mode 100644 index 0000000..07d3360 --- /dev/null +++ b/X10D.Tests/src/Drawing/RandomTests.cs @@ -0,0 +1,37 @@ +using System.Drawing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class RandomTests +{ + [TestMethod] + public void NextColorArgb_ShouldReturn331515e5_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(Color.FromArgb(51, 21, 21, 229), random.NextColorArgb()); + } + + [TestMethod] + public void NextColorArgb_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextColorArgb()); + } + + [TestMethod] + public void NextColorRgb_ShouldReturn1515e5_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(Color.FromArgb(255, 21, 21, 229), random.NextColorRgb()); + } + + [TestMethod] + public void NextColorRgb_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextColorRgb()); + } +} diff --git a/X10D.Tests/src/IO/BooleanTests.cs b/X10D.Tests/src/IO/BooleanTests.cs new file mode 100644 index 0000000..8b4701d --- /dev/null +++ b/X10D.Tests/src/IO/BooleanTests.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class BooleanTests +{ + [TestMethod] + public void GetBytes_ReturnsArrayContaining1() + { + const bool value = true; + CollectionAssert.AreEqual(new byte[] {1}, value.GetBytes()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanContaining1_GivenLargeEnoughSpan() + { + const bool value = true; + Span buffer = stackalloc byte[1]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(new byte[] {1}, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const bool value = true; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/ByteTests.cs b/X10D.Tests/src/IO/ByteTests.cs new file mode 100644 index 0000000..47c14cb --- /dev/null +++ b/X10D.Tests/src/IO/ByteTests.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void GetBytes_ReturnsArrayContainingItself() + { + const byte value = 0xFF; + CollectionAssert.AreEqual(new[] {value}, value.GetBytes()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanContainingItself_GivenLargeEnoughSpan() + { + const byte value = 0xFF; + Span buffer = stackalloc byte[1]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(new[] {value}, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const byte value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/DoubleTests.cs b/X10D.Tests/src/IO/DoubleTests.cs new file mode 100644 index 0000000..616fd79 --- /dev/null +++ b/X10D.Tests/src/IO/DoubleTests.cs @@ -0,0 +1,66 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class DoubleTests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const double value = 42.5; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0, 0, 0, 0, 0, 0x40, 0x45, 0x40} + : new byte[] {0x40, 0x45, 0x40, 0, 0, 0, 0, 0}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const double value = 42.5; + byte[] littleEndian = {0, 0, 0, 0, 0, 0x40, 0x45, 0x40}; + byte[] bigEndian = {0x40, 0x45, 0x40, 0, 0, 0, 0, 0}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const double value = 42.5; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0, 0, 0, 0, 0, 0x40, 0x45, 0x40} + : new byte[] {0x40, 0x45, 0x40, 0, 0, 0, 0, 0}; + + Span buffer = stackalloc byte[8]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const double value = 42.5; + byte[] littleEndian = {0, 0, 0, 0, 0, 0x40, 0x45, 0x40}; + byte[] bigEndian = {0x40, 0x45, 0x40, 0, 0, 0, 0, 0}; + + Span buffer = stackalloc byte[8]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const double value = 42.5; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/FileInfoTests.cs b/X10D.Tests/src/IO/FileInfoTests.cs new file mode 100644 index 0000000..a1047ba --- /dev/null +++ b/X10D.Tests/src/IO/FileInfoTests.cs @@ -0,0 +1,93 @@ +using System.Security.Cryptography; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class FileInfoTests +{ + [TestMethod] + public void GetHashSha1ShouldBeCorrect() + { + string fileName = $"temp.{DateTimeOffset.Now.ToUnixTimeSeconds()}.bin"; + if (File.Exists(fileName)) + { + Assert.Fail("Temporary file already exists"); + } + + File.WriteAllText(fileName, "Hello World"); + Assert.IsTrue(File.Exists(fileName)); + + // SHA-1 + byte[] expectedHash = + { + 0x0A, 0x4D, 0x55, 0xA8, 0xD7, 0x78, 0xE5, 0x02, 0x2F, 0xAB, 0x70, 0x19, 0x77, 0xC5, 0xD8, 0x40, 0xBB, 0xC4, 0x86, + 0xD0 + }; + + try + { + byte[] hash = new FileInfo(fileName).GetHash(); + CollectionAssert.AreEqual(expectedHash, hash); + } + finally + { + File.Delete(fileName); // cleanup is important + } + } + + [TestMethod] + public void TryWriteHashSha1ShouldBeCorrect() + { + string fileName = $"temp.{DateTimeOffset.Now.ToUnixTimeSeconds()}.bin"; + if (File.Exists(fileName)) + { + Assert.Fail("Temporary file already exists"); + } + + File.WriteAllText(fileName, "Hello World"); + Assert.IsTrue(File.Exists(fileName)); + + // SHA-1 + byte[] expectedHash = + { + 0x0A, 0x4D, 0x55, 0xA8, 0xD7, 0x78, 0xE5, 0x02, 0x2F, 0xAB, 0x70, 0x19, 0x77, 0xC5, 0xD8, 0x40, 0xBB, 0xC4, 0x86, + 0xD0 + }; + + try + { + Span hash = stackalloc byte[20]; + new FileInfo(fileName).TryWriteHash(hash, out int bytesWritten); + Assert.AreEqual(expectedHash.Length, bytesWritten); + CollectionAssert.AreEqual(expectedHash, hash.ToArray()); + } + finally + { + File.Delete(fileName); // cleanup is important + } + } + + [TestMethod] + public void GetHashNullShouldThrow() + { + // any HashAlgorithm will do, but SHA1 is used above. so to remain consistent, we use it here + Assert.ThrowsException(() => ((FileInfo?)null)!.GetHash()); + Assert.ThrowsException(() => ((FileInfo?)null)!.TryWriteHash(Span.Empty, out _)); + } + + [TestMethod] + public void GetHashInvalidFileShouldThrow() + { + string fileName = $"temp.{DateTimeOffset.Now.ToUnixTimeSeconds()}.bin"; + if (File.Exists(fileName)) + { + Assert.Fail("Temporary file already exists"); + } + + // any HashAlgorithm will do, but SHA1 is used above. so to remain consistent, we use it here + Assert.ThrowsException(() => new FileInfo(fileName).GetHash()); + Assert.ThrowsException(() => new FileInfo(fileName).TryWriteHash(Span.Empty, out _)); + } +} diff --git a/X10D.Tests/src/IO/Int16Tests.cs b/X10D.Tests/src/IO/Int16Tests.cs new file mode 100644 index 0000000..346e95a --- /dev/null +++ b/X10D.Tests/src/IO/Int16Tests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const short value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0} : new byte[] {0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const short value = 0x0F; + byte[] littleEndian = {0x0F, 0}; + byte[] bigEndian = {0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const short value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0} : new byte[] {0, 0x0F}; + + Span buffer = stackalloc byte[2]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const short value = 0x0F; + byte[] littleEndian = {0x0F, 0}; + byte[] bigEndian = {0, 0x0F}; + + Span buffer = stackalloc byte[2]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const short value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/Int32Tests.cs b/X10D.Tests/src/IO/Int32Tests.cs new file mode 100644 index 0000000..7135b7c --- /dev/null +++ b/X10D.Tests/src/IO/Int32Tests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const int value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0, 0, 0} : new byte[] {0, 0, 0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const int value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const int value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0, 0, 0} : new byte[] {0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[4]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const int value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[4]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const int value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/Int64Tests.cs b/X10D.Tests/src/IO/Int64Tests.cs new file mode 100644 index 0000000..1f7623b --- /dev/null +++ b/X10D.Tests/src/IO/Int64Tests.cs @@ -0,0 +1,66 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const long value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0x0F, 0, 0, 0, 0, 0, 0, 0} + : new byte[] {0, 0, 0, 0, 0, 0, 0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const long value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0, 0, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const long value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0x0F, 0, 0, 0, 0, 0, 0, 0} + : new byte[] {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[8]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const long value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0, 0, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[8]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const long value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/ListOfByteTests.cs b/X10D.Tests/src/IO/ListOfByteTests.cs new file mode 100644 index 0000000..9fe2056 --- /dev/null +++ b/X10D.Tests/src/IO/ListOfByteTests.cs @@ -0,0 +1,168 @@ +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class ListOfByteTests +{ + [TestMethod] + public void AsString_ShouldReturnBytes_GivenBytes() + { + var bytes = new byte[] {0x01, 0x02, 0x03, 0x04, 0x05}; + Assert.AreEqual("01-02-03-04-05", bytes.AsString()); + } + + [TestMethod] + public void AsString_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.AsString()); + } + + [TestMethod] + public void ToDouble_ShouldReturnDouble_GivenBytes() + { + var bytes = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x7A, 0x40}; + + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + + Assert.AreEqual(420.0, bytes.ToDouble(), 1e-6); + } + + [TestMethod] + public void ToDouble_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToDouble()); + } + + [TestMethod] + public void ToInt16_ShouldReturnInt16_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01}; + Assert.AreEqual(420, bytes.ToInt16()); + } + + [TestMethod] + public void ToInt16_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToInt16()); + } + + [TestMethod] + public void ToInt32_ShouldReturnInt32_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01, 0x00, 0x00}; + Assert.AreEqual(420, bytes.ToInt32()); + } + + [TestMethod] + public void ToInt32_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToInt32()); + } + + [TestMethod] + public void ToInt64_ShouldReturnInt32_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + Assert.AreEqual(420L, bytes.ToInt64()); + } + + [TestMethod] + public void ToInt64_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToInt64()); + } + + [TestMethod] + public void ToSingle_ShouldReturnDouble_GivenBytes() + { + var bytes = new byte[] {0x00, 0x00, 0xD2, 0x43}; + + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + + Assert.AreEqual(420.0, bytes.ToSingle(), 1e-6); + } + + [TestMethod] + public void ToSingle_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToSingle()); + } + + [TestMethod] + public void ToString_ShouldReturnHelloWorld_GivenUTF8() + { + var bytes = new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64}; + Assert.AreEqual("Hello World", bytes.ToString(Encoding.UTF8)); + } + + [TestMethod] + public void ToString_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToString(Encoding.UTF8)); + } + + [TestMethod] + public void ToString_ShouldThrow_GivenNullEncoding() + { + var bytes = new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64}; + Assert.ThrowsException(() => bytes.ToString(null!)); + } + + [TestMethod] + public void ToUInt16_ShouldReturnInt16_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01}; + Assert.AreEqual((ushort)420, bytes.ToUInt16()); + } + + [TestMethod] + public void ToUInt16_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToUInt16()); + } + + [TestMethod] + public void ToUInt32_ShouldReturnInt32_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01, 0x00, 0x00}; + Assert.AreEqual(420U, bytes.ToUInt32()); + } + + [TestMethod] + public void ToUInt32_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToUInt32()); + } + + [TestMethod] + public void ToUInt64_ShouldReturnInt32_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + Assert.AreEqual(420UL, bytes.ToUInt64()); + } + + [TestMethod] + public void ToUInt64_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToUInt64()); + } +} diff --git a/X10D.Tests/src/IO/SByteTests.cs b/X10D.Tests/src/IO/SByteTests.cs new file mode 100644 index 0000000..8f28976 --- /dev/null +++ b/X10D.Tests/src/IO/SByteTests.cs @@ -0,0 +1,33 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void GetBytes_ReturnsArrayContainingItself() + { + const sbyte value = 0x0F; + CollectionAssert.AreEqual(new[] {(byte)value}, value.GetBytes()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanContainingItself_GivenLargeEnoughSpan() + { + const sbyte value = 0x0F; + Span buffer = stackalloc byte[1]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(new[] {(byte)value}, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const sbyte value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/SingleTests.cs b/X10D.Tests/src/IO/SingleTests.cs new file mode 100644 index 0000000..0bca2ce --- /dev/null +++ b/X10D.Tests/src/IO/SingleTests.cs @@ -0,0 +1,66 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class SingleTests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const float value = 42.5f; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0, 0, 0x2A, 0x42} + : new byte[] {0x42, 0x2A, 0, 0}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const float value = 42.5f; + byte[] littleEndian = {0, 0, 0x2A, 0x42}; + byte[] bigEndian = {0x42, 0x2A, 0, 0}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const float value = 42.5f; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0, 0, 0x2A, 0x42} + : new byte[] {0x42, 0x2A, 0, 0}; + + Span buffer = stackalloc byte[4]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const float value = 42.5f; + byte[] littleEndian = {0, 0, 0x2A, 0x42}; + byte[] bigEndian = {0x42, 0x2A, 0, 0}; + + Span buffer = stackalloc byte[4]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const float value = 42.5f; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/StreamTests.cs b/X10D.Tests/src/IO/StreamTests.cs new file mode 100644 index 0000000..77809fd --- /dev/null +++ b/X10D.Tests/src/IO/StreamTests.cs @@ -0,0 +1,526 @@ +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class StreamTests +{ + [TestMethod] + public void GetHashSha1ShouldBeCorrect() + { + // SHA-1 + byte[] expectedHash = + { + 0x0A, 0x4D, 0x55, 0xA8, 0xD7, 0x78, 0xE5, 0x02, 0x2F, 0xAB, 0x70, 0x19, 0x77, 0xC5, 0xD8, 0x40, 0xBB, 0xC4, 0x86, + 0xD0 + }; + + using var stream = new MemoryStream(); + stream.Write(Encoding.UTF8.GetBytes("Hello World")); + stream.Position = 0; + + byte[] hash = stream.GetHash(); + Trace.WriteLine($"Hash: {BitConverter.ToString(hash)}"); + Trace.WriteLine($"Expected: {BitConverter.ToString(expectedHash)}"); + CollectionAssert.AreEqual(expectedHash, hash); + } + + [TestMethod] + public void GetHashNullShouldThrow() + { + // any HashAlgorithm will do, but SHA1 is used above. so to remain consistent, we use it here + Assert.ThrowsException(() => ((Stream?)null)!.GetHash()); + Assert.ThrowsException(() => ((Stream?)null)!.TryWriteHash(Span.Empty, out _)); + } + + [TestMethod] + public void TryWriteHashSha1_ShouldBeCorrect() + { + // SHA-1 + byte[] expectedHash = + { + 0x0A, 0x4D, 0x55, 0xA8, 0xD7, 0x78, 0xE5, 0x02, 0x2F, 0xAB, 0x70, 0x19, 0x77, 0xC5, 0xD8, 0x40, 0xBB, 0xC4, 0x86, + 0xD0 + }; + + using var stream = new MemoryStream(); + stream.Write(Encoding.UTF8.GetBytes("Hello World")); + stream.Position = 0; + + Span hash = stackalloc byte[20]; + stream.TryWriteHash(hash, out int bytesWritten); + Assert.AreEqual(expectedHash.Length, bytesWritten); + CollectionAssert.AreEqual(expectedHash, hash.ToArray()); + } + + [TestMethod] + public void GetHash_TryWriteHash_ShouldThrow_GivenNonReadableStream() + { + Assert.ThrowsException(() => + { + using var stream = new DummyStream(); + stream.GetHash(); + }); + + Assert.ThrowsException(() => + { + using var stream = new DummyStream(); + stream.TryWriteHash(Span.Empty, out _); + }); + } + + [TestMethod] + public void LargeStreamShouldThrow() + { + Assert.ThrowsException(() => + { + using var stream = new DummyStream(true); + stream.TryWriteHash(Span.Empty, out _); + }); + } + + [TestMethod] + public void NullCreateMethodShouldThrow() + { + Assert.ThrowsException(() => Stream.Null.GetHash()); + Assert.ThrowsException(() => + Stream.Null.TryWriteHash(Span.Empty, out _)); + } + + [TestMethod] + public void NoCreateMethodShouldThrow() + { + Assert.ThrowsException(() => Stream.Null.GetHash()); + Assert.ThrowsException(() => + Stream.Null.TryWriteHash(Span.Empty, out _)); + } + + [TestMethod] + public void Write_ShouldThrow_GivenUndefinedEndianness() + { + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0.0f, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0.0, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0.0m, (Endianness)(-1)); + }); + + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write((short)0, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0L, (Endianness)(-1)); + }); + + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write((ushort)0, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0U, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0UL, (Endianness)(-1)); + }); + } + + [TestMethod] + public void Read_ShouldThrow_GivenNullStream() + { + Stream? stream = null; + Assert.ThrowsException(() => stream!.ReadSingle()); + Assert.ThrowsException(() => stream!.ReadDouble()); + Assert.ThrowsException(() => stream!.ReadDecimal()); + Assert.ThrowsException(() => stream!.ReadInt16()); + Assert.ThrowsException(() => stream!.ReadInt32()); + Assert.ThrowsException(() => stream!.ReadInt64()); + Assert.ThrowsException(() => stream!.ReadUInt16()); + Assert.ThrowsException(() => stream!.ReadUInt32()); + Assert.ThrowsException(() => stream!.ReadUInt64()); + } + + [TestMethod] + public void Write_ShouldThrow_GivenNullStream() + { + Stream? stream = null; + Assert.ThrowsException(() => stream!.Write(0.0f, Endianness.LittleEndian)); + Assert.ThrowsException(() => stream!.Write(0.0, Endianness.LittleEndian)); + Assert.ThrowsException(() => stream!.Write(0.0m, Endianness.LittleEndian)); + Assert.ThrowsException(() => stream!.Write((short)0)); + Assert.ThrowsException(() => stream!.Write(0)); + Assert.ThrowsException(() => stream!.Write(0L)); + Assert.ThrowsException(() => stream!.Write((ushort)0)); + Assert.ThrowsException(() => stream!.Write(0U)); + Assert.ThrowsException(() => stream!.Write(0UL)); + } + + [TestMethod] + public void Read_ShouldThrow_GivenUndefinedEndianness() + { + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadSingle((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadDouble((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadDecimal((Endianness)(-1)); + }); + + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadInt16((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadInt32((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadInt64((Endianness)(-1)); + }); + + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadUInt16((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadUInt32((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadUInt64((Endianness)(-1)); + }); + } + + [TestMethod] + public void ReadDouble_WriteDouble_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420.0, BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0, stream.ReadDouble(), 1e-6); + + stream.Position = 0; + stream.Write(420.0, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420.0, stream.ReadDouble(Endianness.LittleEndian), 1e-6); + + stream.Position = 0; + stream.Write(420.0, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0, stream.ReadDouble(Endianness.BigEndian), 1e-6); + } + + [TestMethod] + public void ReadDecimal_WriteSingle_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420.0m, BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0m, stream.ReadDecimal()); + + stream.Position = 0; + stream.Write(420.0m, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420.0m, stream.ReadDecimal(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420.0m, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0m, stream.ReadDecimal(Endianness.BigEndian)); + } + + [TestMethod] + public void ReadSingle_WriteSingle_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420.0f, BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0f, stream.ReadSingle(), 1e-6f); + + stream.Position = 0; + stream.Write(420.0f, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420.0f, stream.ReadSingle(Endianness.LittleEndian), 1e-6f); + + stream.Position = 0; + stream.Write(420.0f, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0f, stream.ReadSingle(Endianness.BigEndian), 1e-6f); + } + + [TestMethod] + public void ReadInt16_WriteInt16_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write((short)420); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt16()); + + stream.Position = 0; + stream.Write((short)420, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt16(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write((short)420, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt16(Endianness.BigEndian)); + } + + [TestMethod] + public void ReadInt32_WriteInt32_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt32()); + + stream.Position = 0; + stream.Write(420, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt32(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt32(Endianness.BigEndian)); + } + + [TestMethod] + public void ReadInt64_WriteInt64_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420L); + + stream.Position = 0; + Assert.AreEqual(420L, stream.ReadInt64()); + + stream.Position = 0; + stream.Write(420L, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420L, stream.ReadInt64(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420L, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420L, stream.ReadInt64(Endianness.BigEndian)); + } + + [TestMethod] + [CLSCompliant(false)] + public void ReadUInt16_WriteUInt16_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write((ushort)420); + + stream.Position = 0; + Assert.AreEqual((ushort)420, stream.ReadUInt16()); + + stream.Position = 0; + stream.Write((ushort)420, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual((ushort)420, stream.ReadUInt16(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write((ushort)420, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual((ushort)420, stream.ReadUInt16(Endianness.BigEndian)); + } + + [TestMethod] + [CLSCompliant(false)] + public void ReadUInt32_WriteUInt32_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420U); + + stream.Position = 0; + Assert.AreEqual(420U, stream.ReadUInt32()); + + stream.Position = 0; + stream.Write(420U, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420U, stream.ReadUInt32(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420U, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420U, stream.ReadUInt32(Endianness.BigEndian)); + } + + [TestMethod] + [CLSCompliant(false)] + public void ReadUInt64_WriteUInt64_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420UL); + + stream.Position = 0; + Assert.AreEqual(420UL, stream.ReadUInt64()); + + stream.Position = 0; + stream.Write(420UL, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420UL, stream.ReadUInt64(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420UL, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420UL, stream.ReadUInt64(Endianness.BigEndian)); + } + + private class DummyStream : Stream + { + public DummyStream(bool readable = false) + { + CanRead = readable; + CanSeek = readable; + CanWrite = readable; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + + public override void SetLength(long value) + { + } + + public override void Write(byte[] buffer, int offset, int count) + { + } + + public override bool CanRead { get; } + + public override bool CanSeek { get; } + + public override bool CanWrite { get; } + + public override long Length + { + get => long.MaxValue; + } + + public override long Position + { + get => 0; + set { } + } + } + + private class HashAlgorithmTestClass : HashAlgorithm + { + public static new HashAlgorithmTestClass? Create() + { + return null; + } + + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + throw new NotImplementedException(); + } + + protected override byte[] HashFinal() + { + throw new NotImplementedException(); + } + + public override void Initialize() + { + throw new NotImplementedException(); + } + } + + private class HashAlgorithmTestClassNoCreateMethod : HashAlgorithm + { + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + throw new NotImplementedException(); + } + + protected override byte[] HashFinal() + { + throw new NotImplementedException(); + } + + public override void Initialize() + { + throw new NotImplementedException(); + } + } +} diff --git a/X10D.Tests/src/IO/UInt16Tests.cs b/X10D.Tests/src/IO/UInt16Tests.cs new file mode 100644 index 0000000..08c0e61 --- /dev/null +++ b/X10D.Tests/src/IO/UInt16Tests.cs @@ -0,0 +1,63 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const ushort value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0} : new byte[] {0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const ushort value = 0x0F; + byte[] littleEndian = {0x0F, 0}; + byte[] bigEndian = {0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const ushort value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0} : new byte[] {0, 0x0F}; + + Span buffer = stackalloc byte[2]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const ushort value = 0x0F; + byte[] littleEndian = {0x0F, 0}; + byte[] bigEndian = {0, 0x0F}; + + Span buffer = stackalloc byte[2]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const ushort value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/UInt32Tests.cs b/X10D.Tests/src/IO/UInt32Tests.cs new file mode 100644 index 0000000..a9b3cc9 --- /dev/null +++ b/X10D.Tests/src/IO/UInt32Tests.cs @@ -0,0 +1,63 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const uint value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0, 0, 0} : new byte[] {0, 0, 0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const uint value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const uint value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0, 0, 0} : new byte[] {0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[4]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const uint value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[4]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const uint value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/UInt64Tests.cs b/X10D.Tests/src/IO/UInt64Tests.cs new file mode 100644 index 0000000..d31a209 --- /dev/null +++ b/X10D.Tests/src/IO/UInt64Tests.cs @@ -0,0 +1,67 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const ulong value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0x0F, 0, 0, 0, 0, 0, 0, 0} + : new byte[] {0, 0, 0, 0, 0, 0, 0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const ulong value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0, 0, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const ulong value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0x0F, 0, 0, 0, 0, 0, 0, 0} + : new byte[] {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[8]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const ulong value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0, 0, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[8]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const ulong value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/Linq/ByteTests.cs b/X10D.Tests/src/Linq/ByteTests.cs new file mode 100644 index 0000000..1c269b4 --- /dev/null +++ b/X10D.Tests/src/Linq/ByteTests.cs @@ -0,0 +1,96 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + byte Cast(int i) => (byte)i; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Select(Cast).Product()); + + // 6! will overflow for byte + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + byte Double(int i) => (byte)(i * 2); + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + + // Π_(i=1)^n (2i) will overflow at i=4 for byte + } + + [TestMethod] + public void RangeTo_Byte_ShouldYieldCorrectValues() + { + const byte start = 1; + const byte end = 10; + + byte current = 1; + foreach (byte value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int16_ShouldYieldCorrectValues() + { + const byte start = 1; + const short end = 10; + + short current = 1; + foreach (short value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int32_ShouldYieldCorrectValues() + { + const byte start = 1; + const int end = 10; + + int current = 1; + foreach (int value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int64_ShouldYieldCorrectValues() + { + const byte start = 1; + const long end = 10; + + long current = 1; + foreach (long value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } +} diff --git a/X10D.Tests/src/Linq/DecimalTests.cs b/X10D.Tests/src/Linq/DecimalTests.cs new file mode 100644 index 0000000..abb3f7b --- /dev/null +++ b/X10D.Tests/src/Linq/DecimalTests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class DecimalTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + decimal Cast(int i) => i; + + Assert.AreEqual(0m, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1m, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2m, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6m, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24m, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120m, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720m, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040m, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320m, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880m, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800m, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + decimal Double(int i) => i * 2m; + + Assert.AreEqual(0m, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2m, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8m, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48m, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384m, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840m, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080m, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120m, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920m, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560m, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200m, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Linq/DoubleTests.cs b/X10D.Tests/src/Linq/DoubleTests.cs new file mode 100644 index 0000000..8e33bdd --- /dev/null +++ b/X10D.Tests/src/Linq/DoubleTests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class DoubleTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + double Cast(int i) => i; + + Assert.AreEqual(0.0, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1.0, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2.0, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6.0, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24.0, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120.0, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720.0, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040.0, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320.0, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880.0, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800.0, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + double Double(int i) => i * 2.0; + + Assert.AreEqual(0.0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2.0, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8.0, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48.0, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384.0, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840.0, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080.0, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120.0, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920.0, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560.0, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200.0, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Linq/Int16Tests.cs b/X10D.Tests/src/Linq/Int16Tests.cs new file mode 100644 index 0000000..c0f0636 --- /dev/null +++ b/X10D.Tests/src/Linq/Int16Tests.cs @@ -0,0 +1,85 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + short Cast(int i) => (short)i; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040, Enumerable.Range(1, 7).Select(Cast).Product()); + + // 8! will overflow for short + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + short Double(int i) => (short)(i * 2); + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840, Enumerable.Range(1, 5).Product(Double)); + + // Π_(i=1)^n (2i) will overflow at i=6 for short + } + + [TestMethod] + public void RangeTo_Int16_ShouldYieldCorrectValues() + { + const short start = 1; + const short end = 10; + + short current = 1; + foreach (short value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int32_ShouldYieldCorrectValues() + { + const short start = 1; + const int end = 10; + + int current = 1; + foreach (int value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int64_ShouldYieldCorrectValues() + { + const short start = 1; + const long end = 10; + + long current = 1; + foreach (long value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } +} diff --git a/X10D.Tests/src/Linq/Int32Tests.cs b/X10D.Tests/src/Linq/Int32Tests.cs new file mode 100644 index 0000000..efc0870 --- /dev/null +++ b/X10D.Tests/src/Linq/Int32Tests.cs @@ -0,0 +1,73 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + Assert.AreEqual(0, Enumerable.Range(0, 10).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Product()); + Assert.AreEqual(720, Enumerable.Range(1, 6).Product()); + Assert.AreEqual(5040, Enumerable.Range(1, 7).Product()); + Assert.AreEqual(40320, Enumerable.Range(1, 8).Product()); + Assert.AreEqual(362880, Enumerable.Range(1, 9).Product()); + Assert.AreEqual(3628800, Enumerable.Range(1, 10).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + int Double(int i) => i * 2; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560, Enumerable.Range(1, 9).Product(Double)); + + // Π_(i=1)^n (2i) will overflow at i=10 for int + } + + [TestMethod] + public void RangeTo_Int32_ShouldYieldCorrectValues() + { + const int start = 1; + const int end = 10; + + int current = 1; + foreach (int value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int64_ShouldYieldCorrectValues() + { + const int start = 1; + const long end = 10; + + long current = 1; + foreach (long value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } +} diff --git a/X10D.Tests/src/Linq/Int64Tests.cs b/X10D.Tests/src/Linq/Int64Tests.cs new file mode 100644 index 0000000..5535eeb --- /dev/null +++ b/X10D.Tests/src/Linq/Int64Tests.cs @@ -0,0 +1,59 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + long Cast(int i) => i; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + long Double(int i) => i * 2; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200, Enumerable.Range(1, 10).Product(Double)); + } + + [TestMethod] + public void RangeTo_Int64_ShouldYieldCorrectValues() + { + const long start = 1; + const long end = 10; + + long current = 1; + foreach (long value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } +} diff --git a/X10D.Tests/src/Linq/ReadOnlySpanTests.cs b/X10D.Tests/src/Linq/ReadOnlySpanTests.cs new file mode 100644 index 0000000..e33f5cd --- /dev/null +++ b/X10D.Tests/src/Linq/ReadOnlySpanTests.cs @@ -0,0 +1,58 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class ReadOnlySpanTests +{ + [TestMethod] + public void AllShouldReturnTrueForEmptySpan() + { + var span = new ReadOnlySpan(); + Assert.IsTrue(span.All(x => x > 0)); + } + + [TestMethod] + public void AllShouldBeCorrect() + { + var span = new ReadOnlySpan(new[] {2, 4, 6, 8, 10}); + Assert.IsTrue(span.All(x => x % 2 == 0)); + Assert.IsFalse(span.All(x => x % 2 == 1)); + } + + [TestMethod] + public void AnyShouldReturnFalseForEmptySpan() + { + var span = new ReadOnlySpan(); + Assert.IsFalse(span.Any(x => x > 0)); + } + + [TestMethod] + public void AnyShouldBeCorrect() + { + var span = new ReadOnlySpan(new[] {2, 4, 6, 8, 10}); + Assert.IsTrue(span.Any(x => x % 2 == 0)); + Assert.IsFalse(span.Any(x => x % 2 == 1)); + } + + [TestMethod] + public void AllNullPredicateShouldThrow() + { + Assert.ThrowsException(() => + { + var span = new ReadOnlySpan(); + return span.All(null!); + }); + } + + [TestMethod] + public void AnyNullPredicateShouldThrow() + { + Assert.ThrowsException(() => + { + var span = new ReadOnlySpan(); + return span.Any(null!); + }); + } +} diff --git a/X10D.Tests/src/Linq/SByteExtensions.cs b/X10D.Tests/src/Linq/SByteExtensions.cs new file mode 100644 index 0000000..59d01a7 --- /dev/null +++ b/X10D.Tests/src/Linq/SByteExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + sbyte Cast(int i) => (sbyte)i; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Product()); + + // 6! will overflow for sbyte + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + sbyte Double(int i) => (sbyte)(i * 2); + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + + // Π_(i=1)^(n(i*2)) will overflow at i=4 for sbyte + } +} diff --git a/X10D.Tests/src/Linq/SingleTests.cs b/X10D.Tests/src/Linq/SingleTests.cs new file mode 100644 index 0000000..b504972 --- /dev/null +++ b/X10D.Tests/src/Linq/SingleTests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class SingleTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + float Cast(int i) => i; + + Assert.AreEqual(0f, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1f, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2f, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6f, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24f, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120f, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720f, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040f, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320f, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880f, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800f, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + float Double(int i) => i * 2f; + + Assert.AreEqual(0f, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2f, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8f, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48f, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384f, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840f, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080f, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120f, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920f, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560f, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200f, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Linq/SpanTests.cs b/X10D.Tests/src/Linq/SpanTests.cs new file mode 100644 index 0000000..98c5fe3 --- /dev/null +++ b/X10D.Tests/src/Linq/SpanTests.cs @@ -0,0 +1,58 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class SpanTests +{ + [TestMethod] + public void AllShouldReturnTrueForEmptySpan() + { + var span = new Span(); + Assert.IsTrue(span.All(x => x > 0)); + } + + [TestMethod] + public void AllShouldBeCorrect() + { + var span = new Span(new[] {2, 4, 6, 8, 10}); + Assert.IsTrue(span.All(x => x % 2 == 0)); + Assert.IsFalse(span.All(x => x % 2 == 1)); + } + + [TestMethod] + public void AnyShouldReturnFalseForEmptySpan() + { + var span = new Span(); + Assert.IsFalse(span.Any(x => x > 0)); + } + + [TestMethod] + public void AnyShouldBeCorrect() + { + var span = new Span(new[] {2, 4, 6, 8, 10}); + Assert.IsTrue(span.Any(x => x % 2 == 0)); + Assert.IsFalse(span.Any(x => x % 2 == 1)); + } + + [TestMethod] + public void AllNullPredicateShouldThrow() + { + Assert.ThrowsException(() => + { + var span = new Span(); + return span.All(null!); + }); + } + + [TestMethod] + public void AnyNullPredicateShouldThrow() + { + Assert.ThrowsException(() => + { + var span = new Span(); + return span.Any(null!); + }); + } +} diff --git a/X10D.Tests/src/Linq/UInt16Tests.cs b/X10D.Tests/src/Linq/UInt16Tests.cs new file mode 100644 index 0000000..bf496e8 --- /dev/null +++ b/X10D.Tests/src/Linq/UInt16Tests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + ushort Cast(int i) => (ushort)i; + + Assert.AreEqual(0U, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1U, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2U, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6U, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24U, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120U, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720U, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040U, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320U, Enumerable.Range(1, 8).Select(Cast).Product()); + + // 9! will overflow for ushort + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + ushort Double(int i) => (ushort)(i * 2); + + Assert.AreEqual(0U, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2U, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8U, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48U, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384U, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840U, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080U, Enumerable.Range(1, 6).Product(Double)); + + // Π_(i=1)^n (2i) will overflow at i=7 for ushort + } +} diff --git a/X10D.Tests/src/Linq/UInt32Tests.cs b/X10D.Tests/src/Linq/UInt32Tests.cs new file mode 100644 index 0000000..060592e --- /dev/null +++ b/X10D.Tests/src/Linq/UInt32Tests.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + ulong Cast(int i) => (ulong)i; + + Assert.AreEqual(0U, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1U, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2U, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6U, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24U, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120U, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720U, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040U, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320U, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880U, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800U, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + uint Double(int i) => (uint)i * 2; + + Assert.AreEqual(0U, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2U, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8U, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48U, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384U, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840U, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080U, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120U, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920U, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560U, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200U, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Linq/UInt64Tests.cs b/X10D.Tests/src/Linq/UInt64Tests.cs new file mode 100644 index 0000000..c9c2409 --- /dev/null +++ b/X10D.Tests/src/Linq/UInt64Tests.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + ulong Cast(int i) => (ulong)i; + + Assert.AreEqual(0UL, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1UL, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2UL, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6UL, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24UL, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120UL, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720UL, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040UL, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320UL, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880UL, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800UL, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + ulong Double(int i) => (ulong)i * 2; + + Assert.AreEqual(0UL, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2UL, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8UL, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48UL, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384UL, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840UL, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080UL, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120UL, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920UL, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560UL, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200UL, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Math/ByteTests.cs b/X10D.Tests/src/Math/ByteTests.cs new file mode 100644 index 0000000..cfc4386 --- /dev/null +++ b/X10D.Tests/src/Math/ByteTests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const byte value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, ((byte)0).Factorial()); + Assert.AreEqual(1L, ((byte)1).Factorial()); + Assert.AreEqual(2L, ((byte)2).Factorial()); + Assert.AreEqual(6L, ((byte)3).Factorial()); + Assert.AreEqual(24L, ((byte)4).Factorial()); + Assert.AreEqual(120L, ((byte)5).Factorial()); + Assert.AreEqual(720L, ((byte)6).Factorial()); + Assert.AreEqual(5040L, ((byte)7).Factorial()); + Assert.AreEqual(40320L, ((byte)8).Factorial()); + Assert.AreEqual(362880L, ((byte)9).Factorial()); + Assert.AreEqual(3628800L, ((byte)10).Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const byte one = 1; + const byte two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const byte one = 1; + const byte two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, ((byte)0).MultiplicativePersistence()); + Assert.AreEqual(1, ((byte)10).MultiplicativePersistence()); + Assert.AreEqual(2, ((byte)25).MultiplicativePersistence()); + Assert.AreEqual(3, ((byte)39).MultiplicativePersistence()); + Assert.AreEqual(4, ((byte)77).MultiplicativePersistence()); + } +} diff --git a/X10D.Tests/src/Math/ComparableTests.cs b/X10D.Tests/src/Math/ComparableTests.cs new file mode 100644 index 0000000..dd71c7d --- /dev/null +++ b/X10D.Tests/src/Math/ComparableTests.cs @@ -0,0 +1,201 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class ComparableTests +{ + private class ComparableTestClass : IComparable + { + public int CompareTo(ComparableTestClass? other) + { + return 0; + } + } + + private readonly int _lower = 1; + private readonly int _upper = 10; + private readonly int _value = 5; + + [TestMethod] + public void Between_5_1_10_ShouldBeTrue() + { + Assert.IsTrue(_value.Between(_lower, _upper)); + } + + [TestMethod] + public void Between_1_1_10_ShouldBeFalse() + { + // default option is exclusive + Assert.IsFalse(_lower.Between(_lower, _upper)); + } + + [TestMethod] + public void Between_1_1_10_Inclusive_ShouldBeTrue() + { + Assert.IsTrue(_lower.Between(_lower, _upper, InclusiveOptions.Inclusive)); + Assert.IsTrue(_lower.Between(_lower, _upper, InclusiveOptions.LowerInclusive)); + Assert.IsFalse(_lower.Between(_lower, _upper, InclusiveOptions.UpperInclusive)); + } + + [TestMethod] + public void Between_10_1_10_ShouldBeFalse() + { + // default option is exclusive + Assert.IsFalse(_upper.Between(_lower, _upper)); + } + + [TestMethod] + public void Between_10_1_10_Inclusive_ShouldBeTrue() + { + Assert.IsTrue(_upper.Between(_lower, _upper, InclusiveOptions.Inclusive)); + Assert.IsTrue(_upper.Between(_lower, _upper, InclusiveOptions.UpperInclusive)); + Assert.IsFalse(_upper.Between(_lower, _upper, InclusiveOptions.LowerInclusive)); + } + + [TestMethod] + public void Between_1_10_5_ShouldThrow() + { + Assert.ThrowsException(() => _lower.Between(_upper, _value)); + } + + [TestMethod] + public void Between_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => ((ComparableTestClass?)null)!.Between(nullPointer!, nullPointer!)); + } + + [TestMethod] + public void Clamp_3_1_5_ShouldBe3() + { + Assert.AreEqual(3, 3.Clamp(1, 5)); + } + + [TestMethod] + public void Clamp_10_1_5_ShouldBe5() + { + Assert.AreEqual(5, 10.Clamp(1, 5)); + } + + [TestMethod] + public void Clamp_n_6_5_ShouldThrow() + { + Assert.ThrowsException(() => 0.Clamp(6, 5)); + } + + [TestMethod] + public void GreaterThan_5_6_ShouldBeFalse() + { + Assert.IsFalse(5.GreaterThan(6)); + } + + [TestMethod] + public void GreaterThan_6_5_ShouldBeTrue() + { + Assert.IsTrue(6.GreaterThan(5)); + } + + [TestMethod] + public void GreaterThan_5_5_ShouldBeFalse() + { + Assert.IsFalse(5.LessThan(5)); + } + + [TestMethod] + public void GreaterThan_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.GreaterThan(nullPointer!)); + } + + [TestMethod] + public void GreaterThanOrEqualTo_5_5_ShouldBeTrue() + { + Assert.IsTrue(5.GreaterThanOrEqualTo(5)); + } + + [TestMethod] + public void GreaterThanOrEqualTo_6_5_ShouldBeTrue() + { + Assert.IsTrue(6.GreaterThanOrEqualTo(5)); + } + + [TestMethod] + public void GreaterThanOrEqualTo_5_6_ShouldBeFalse() + { + Assert.IsFalse(5.GreaterThanOrEqualTo(6)); + } + + [TestMethod] + public void GreaterThanOrEqualTo_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.GreaterThanOrEqualTo(nullPointer!)); + } + + [TestMethod] + public void LessThan_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.LessThan(nullPointer!)); + } + + [TestMethod] + public void LessThan_6_5_ShouldBeFalse() + { + Assert.IsFalse(6.LessThan(5)); + } + + [TestMethod] + public void LessThan_5_6_ShouldBeTrue() + { + Assert.IsTrue(5.LessThan(6)); + } + + [TestMethod] + public void LessThan_5_5_ShouldBeFalse() + { + Assert.IsFalse(5.LessThan(5)); + } + + [TestMethod] + public void LessThanOrEqualTo_5_5_ShouldBeTrue() + { + Assert.IsTrue(5.LessThanOrEqualTo(5)); + } + + [TestMethod] + public void LessThanOrEqualTo_5_6_ShouldBeTrue() + { + Assert.IsTrue(5.LessThanOrEqualTo(6)); + } + + [TestMethod] + public void LessThanOrEqualTo_6_5_ShouldBeFalse() + { + Assert.IsFalse(6.LessThanOrEqualTo(5)); + } + + [TestMethod] + public void LessThanOrEqualTo_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.LessThanOrEqualTo(nullPointer!)); + } + + [TestMethod] + public void Max_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.Max(nullPointer!)); + } + + [TestMethod] + public void Min_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.Min(nullPointer!)); + } +} diff --git a/X10D.Tests/src/Math/DecimalTests.cs b/X10D.Tests/src/Math/DecimalTests.cs new file mode 100644 index 0000000..a8ce150 --- /dev/null +++ b/X10D.Tests/src/Math/DecimalTests.cs @@ -0,0 +1,122 @@ +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class DecimalTests +{ + [TestMethod] + public void ComplexSqrt_ShouldBeCorrect_GivenReal() + { + Assert.AreEqual(0.0, 0.0m.ComplexSqrt()); + Assert.AreEqual(1.4142135623730951, 2.0m.ComplexSqrt()); + Assert.AreEqual(3.0, 9.0m.ComplexSqrt()); + Assert.AreEqual(4.0, 16.0m.ComplexSqrt()); + Assert.AreEqual(100.0, 10000.0m.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeImaginary_GivenNegativeValue() + { + Assert.AreEqual(new Complex(0, 1), (-1.0m).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 1.4142135623730951), (-2.0m).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 3.0), (-9.0m).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 4.0), (-16.0m).ComplexSqrt()); + } + + [TestMethod] + public void IsEven_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse((-3.0m).IsEven()); + Assert.IsFalse((-1.0m).IsEven()); + Assert.IsFalse(1.0m.IsEven()); + Assert.IsFalse(3.0m.IsEven()); + } + + [TestMethod] + public void IsEven_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-4.0m).IsEven()); + Assert.IsTrue((-2.0m).IsEven()); + Assert.IsTrue(0.0m.IsEven()); + Assert.IsTrue(2.0m.IsEven()); + Assert.IsTrue(4.0m.IsEven()); + } + + [TestMethod] + public void IsOdd_ShouldBeFalse_GivenEvenNumber() + { + Assert.IsFalse((-4.0m).IsOdd()); + Assert.IsFalse((-2.0m).IsOdd()); + Assert.IsFalse(0.0m.IsOdd()); + Assert.IsFalse(2.0m.IsOdd()); + Assert.IsFalse(4.0m.IsOdd()); + } + + [TestMethod] + public void IsOdd_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-3.0m).IsOdd()); + Assert.IsTrue((-1.0m).IsOdd()); + Assert.IsTrue(1.0m.IsOdd()); + Assert.IsTrue(3.0m.IsOdd()); + } + + [TestMethod] + public void Round_ShouldRoundToNearestInteger() + { + Assert.AreEqual(4.0m, 3.5m.Round()); + Assert.AreEqual(7.0m, 6.8m.Round()); + Assert.AreEqual(7.0m, 7.2m.Round()); + } + + [TestMethod] + public void Round_ShouldRoundToNearestMultiple() + { + Assert.AreEqual(5.0m, 3.5m.Round(5)); + Assert.AreEqual(5.0m, 7.0m.Round(5)); + Assert.AreEqual(10.0m, 7.5m.Round(5)); + } + + [TestMethod] + public void Sign_ShouldBeMinus1_GivenNegative() + { + Assert.AreEqual(-1, -1.0m.Sign()); + Assert.AreEqual(-1, -2.0m.Sign()); + Assert.AreEqual(-1, -3.0m.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe0_Given0() + { + Assert.AreEqual(0, 0.0m.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe1_GivenPositive() + { + Assert.AreEqual(1, 1.0m.Sign()); + Assert.AreEqual(1, 2.0m.Sign()); + Assert.AreEqual(1, 3.0m.Sign()); + } + + [TestMethod] + public void Sqrt_ShouldBeCorrect_GivenValue() + { + Assert.AreEqual(0.0m, 0.0m.Sqrt()); + Assert.AreEqual(1.4142135623730950488016887242m, 2.0m.Sqrt()); + Assert.AreEqual(3.0m, 9.0m.Sqrt()); + Assert.AreEqual(4.0m, 16.0m.Sqrt()); + Assert.AreEqual(100.0m, 10000.0m.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldThrow_GivenNegativeValue() + { + Assert.ThrowsException(() => (-1.0m).Sqrt()); + Assert.ThrowsException(() => (-2.0m).Sqrt()); + Assert.ThrowsException(() => (-3.0m).Sqrt()); + } +} diff --git a/X10D.Tests/src/Math/DoubleTests.cs b/X10D.Tests/src/Math/DoubleTests.cs new file mode 100644 index 0000000..2496348 --- /dev/null +++ b/X10D.Tests/src/Math/DoubleTests.cs @@ -0,0 +1,242 @@ +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class DoubleTests +{ + [TestMethod] + public void DegreesToRadians_ShouldBeCorrect() + { + Assert.AreEqual(System.Math.PI, 180.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(System.Math.PI * 1.5, 270.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(0.0, 0.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(0.017453292519943295, 1.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(0.10471975511965978, 6.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(0.20943951023931956, 12.0.DegreesToRadians(), 1e-6); + } + + [TestMethod] + public void RadiansToDegrees_ShouldBeCorrect() + { + Assert.AreEqual(180.0, System.Math.PI.RadiansToDegrees(), 1e-6); + Assert.AreEqual(360.0, (2.0 * System.Math.PI).RadiansToDegrees(), 1e-6); + Assert.AreEqual(0.0, 0.0.RadiansToDegrees(), 1e-6); + Assert.AreEqual(1.0, 0.017453292519943295.RadiansToDegrees(), 1e-6); + Assert.AreEqual(6.000000000000001, 0.10471975511965978.RadiansToDegrees(), 1e-6); // rounding errors are fun + Assert.AreEqual(12.0, 0.20943951023931953.RadiansToDegrees(), 1e-6); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeCorrect_GivenReal() + { + Assert.AreEqual(0.0, 0.0.ComplexSqrt()); + Assert.AreEqual(1.4142135623730951, 2.0.ComplexSqrt()); + Assert.AreEqual(3.0, 9.0.ComplexSqrt()); + Assert.AreEqual(4.0, 16.0.ComplexSqrt()); + Assert.AreEqual(100.0, 10000.0.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeImaginary_GivenNegativeValue() + { + Assert.AreEqual(new Complex(0, 1), (-1.0).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 1.4142135623730951), (-2.0).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 3.0), (-9.0).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 4.0), (-16.0).ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeComplexInfinity_GivenInfinity() + { + Assert.AreEqual(Complex.Infinity, double.NegativeInfinity.ComplexSqrt()); + Assert.AreEqual(Complex.Infinity, double.PositiveInfinity.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeNaN_GivenNaN() + { + Assert.AreEqual(Complex.NaN, double.NaN.ComplexSqrt()); + } + + [TestMethod] + public void IsEven_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse((-3.0).IsEven()); + Assert.IsFalse((-1.0).IsEven()); + Assert.IsFalse(1.0.IsEven()); + Assert.IsFalse(3.0.IsEven()); + } + + [TestMethod] + public void IsEven_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-4.0).IsEven()); + Assert.IsTrue((-2.0).IsEven()); + Assert.IsTrue(0.0.IsEven()); + Assert.IsTrue(2.0.IsEven()); + Assert.IsTrue(4.0.IsEven()); + } + + [TestMethod] + public void IsOdd_ShouldBeFalse_GivenEvenNumber() + { + Assert.IsFalse((-4.0).IsOdd()); + Assert.IsFalse((-2.0).IsOdd()); + Assert.IsFalse(0.0.IsOdd()); + Assert.IsFalse(2.0.IsOdd()); + Assert.IsFalse(4.0.IsOdd()); + } + + [TestMethod] + public void IsOdd_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-3.0).IsOdd()); + Assert.IsTrue((-1.0).IsOdd()); + Assert.IsTrue(1.0.IsOdd()); + Assert.IsTrue(3.0.IsOdd()); + } + + [TestMethod] + public void Round_ShouldRoundToNearestInteger() + { + Assert.AreEqual(4.0, 3.5.Round(), 1e-6); + Assert.AreEqual(7.0, 6.8.Round(), 1e-6); + Assert.AreEqual(7.0, 7.2.Round(), 1e-6); + } + + [TestMethod] + public void Round_ShouldRoundToNearestMultiple() + { + Assert.AreEqual(5.0, 3.5.Round(5), 1e-6); + Assert.AreEqual(5.0, 7.0.Round(5), 1e-6); + Assert.AreEqual(10.0, 7.5.Round(5), 1e-6); + } + + [TestMethod] + public void Sign_ShouldBeMinus1_GivenNegative() + { + Assert.AreEqual(-1, -1.0.Sign()); + Assert.AreEqual(-1, -2.0.Sign()); + Assert.AreEqual(-1, -3.0.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe0_Given0() + { + Assert.AreEqual(0, 0.0.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe1_GivenPositive() + { + Assert.AreEqual(1, 1.0.Sign()); + Assert.AreEqual(1, 2.0.Sign()); + Assert.AreEqual(1, 3.0.Sign()); + } + + [TestMethod] + public void Sqrt_ShouldBeCorrect_GivenValue() + { + Assert.AreEqual(0.0, 0.0.Sqrt(), 1e-6); + Assert.AreEqual(1.414213562373095, 2.0.Sqrt(), 1e-6); + Assert.AreEqual(3.0, 9.0.Sqrt(), 1e-6); + Assert.AreEqual(4.0, 16.0.Sqrt(), 1e-6); + Assert.AreEqual(100.0, 10000.0.Sqrt(), 1e-6); + } + + [TestMethod] + public void Sqrt_ShouldBeNaN_GivenNaN() + { + Assert.AreEqual(double.NaN, double.NaN.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldBeNaN_GivenNegativeValue() + { + Assert.AreEqual(double.NaN, (-1.0).Sqrt()); + Assert.AreEqual(double.NaN, (-2.0).Sqrt()); + Assert.AreEqual(double.NaN, (-3.0).Sqrt()); + Assert.AreEqual(double.NaN, double.NegativeInfinity.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldBePositiveInfinity_GivenPositiveInfinity() + { + Assert.AreEqual(double.PositiveInfinity, double.PositiveInfinity.Sqrt()); + } + + [TestMethod] + public void Acos_ShouldBeCorrect() + { + Assert.AreEqual(1.0471975511965979, 0.5.Acos(), 1e-6); + } + + [TestMethod] + public void Acosh_ShouldBeCorrect() + { + Assert.AreEqual(0.9624236501192069, 1.5.Acosh(), 1e-6); + } + + [TestMethod] + public void Asin_ShouldBeCorrect() + { + Assert.AreEqual(0.5235987755982989, 0.5.Asin(), 1e-6); + } + + [TestMethod] + public void Asinh_ShouldBeCorrect() + { + Assert.AreEqual(1.1947632172871094, 1.5.Asinh(), 1e-6); + } + + [TestMethod] + public void Atan_ShouldBeCorrect() + { + Assert.AreEqual(0.4636476090008061, 0.5.Atan(), 1e-6); + } + + [TestMethod] + public void Atanh_ShouldBeCorrect() + { + Assert.AreEqual(0.5493061443340549, 0.5.Atanh(), 1e-6); + } + + [TestMethod] + public void Cos_ShouldBeCorrect() + { + Assert.AreEqual(0.8775825618903728, 0.5.Cos(), 1e-6); + } + + [TestMethod] + public void Cosh_ShouldBeCorrect() + { + Assert.AreEqual(2.352409615243247, 1.5.Cosh(), 1e-6); + } + + [TestMethod] + public void Sin_ShouldBeCorrect() + { + Assert.AreEqual(0.479425538604203, 0.5.Sin(), 1e-6); + } + + [TestMethod] + public void Sinh_ShouldBeCorrect() + { + Assert.AreEqual(2.1292794550948173, 1.5.Sinh(), 1e-6); + } + + [TestMethod] + public void Tan_ShouldBeCorrect() + { + Assert.AreEqual(0.5463024898437905, 0.5.Tan(), 1e-6); + } + + [TestMethod] + public void Tanh_ShouldBeCorrect() + { + Assert.AreEqual(0.46211715726000974, 0.5.Tanh(), 1e-6); + } +} diff --git a/X10D.Tests/src/Math/Int16Tests.cs b/X10D.Tests/src/Math/Int16Tests.cs new file mode 100644 index 0000000..15edb9a --- /dev/null +++ b/X10D.Tests/src/Math/Int16Tests.cs @@ -0,0 +1,80 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const short value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, ((short)0).Factorial()); + Assert.AreEqual(1L, ((short)1).Factorial()); + Assert.AreEqual(2L, ((short)2).Factorial()); + Assert.AreEqual(6L, ((short)3).Factorial()); + Assert.AreEqual(24L, ((short)4).Factorial()); + Assert.AreEqual(120L, ((short)5).Factorial()); + Assert.AreEqual(720L, ((short)6).Factorial()); + Assert.AreEqual(5040L, ((short)7).Factorial()); + Assert.AreEqual(40320L, ((short)8).Factorial()); + Assert.AreEqual(362880L, ((short)9).Factorial()); + Assert.AreEqual(3628800L, ((short)10).Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const short one = 1; + const short two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const short one = 1; + const short two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, ((short)0).MultiplicativePersistence()); + Assert.AreEqual(1, ((short)10).MultiplicativePersistence()); + Assert.AreEqual(2, ((short)25).MultiplicativePersistence()); + Assert.AreEqual(3, ((short)39).MultiplicativePersistence()); + Assert.AreEqual(4, ((short)77).MultiplicativePersistence()); + Assert.AreEqual(5, ((short)679).MultiplicativePersistence()); + Assert.AreEqual(6, ((short)6788).MultiplicativePersistence()); + } + + [TestMethod] + public void NegativeFactorialShouldThrow() + { + Assert.ThrowsException(() => ((short)-1).Factorial()); + } + + [TestMethod] + public void SignShouldBeCorrect() + { + const short one = 1; + const short zero = 0; + Assert.AreEqual(one, one.Sign()); + Assert.AreEqual(zero, zero.Sign()); + Assert.AreEqual(-one, (-one).Sign()); + } +} diff --git a/X10D.Tests/src/Math/Int32Tests.cs b/X10D.Tests/src/Math/Int32Tests.cs new file mode 100644 index 0000000..db0d5fb --- /dev/null +++ b/X10D.Tests/src/Math/Int32Tests.cs @@ -0,0 +1,83 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const int value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, 0.Factorial()); + Assert.AreEqual(1L, 1.Factorial()); + Assert.AreEqual(2L, 2.Factorial()); + Assert.AreEqual(6L, 3.Factorial()); + Assert.AreEqual(24L, 4.Factorial()); + Assert.AreEqual(120L, 5.Factorial()); + Assert.AreEqual(720L, 6.Factorial()); + Assert.AreEqual(5040L, 7.Factorial()); + Assert.AreEqual(40320L, 8.Factorial()); + Assert.AreEqual(362880L, 9.Factorial()); + Assert.AreEqual(3628800L, 10.Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const int one = 1; + const int two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const int one = 1; + const int two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, 0.MultiplicativePersistence()); + Assert.AreEqual(1, 10.MultiplicativePersistence()); + Assert.AreEqual(2, 25.MultiplicativePersistence()); + Assert.AreEqual(3, 39.MultiplicativePersistence()); + Assert.AreEqual(4, 77.MultiplicativePersistence()); + Assert.AreEqual(5, 679.MultiplicativePersistence()); + Assert.AreEqual(6, 6788.MultiplicativePersistence()); + Assert.AreEqual(7, 68889.MultiplicativePersistence()); + Assert.AreEqual(8, 2677889.MultiplicativePersistence()); + Assert.AreEqual(9, 26888999.MultiplicativePersistence()); + } + + [TestMethod] + public void NegativeFactorialShouldThrow() + { + Assert.ThrowsException(() => (-1).Factorial()); + } + + [TestMethod] + public void SignShouldBeCorrect() + { + const int one = 1; + const int zero = 0; + Assert.AreEqual(one, one.Sign()); + Assert.AreEqual(zero, zero.Sign()); + Assert.AreEqual(-one, (-one).Sign()); + } +} diff --git a/X10D.Tests/src/Math/Int64Tests.cs b/X10D.Tests/src/Math/Int64Tests.cs new file mode 100644 index 0000000..44acb55 --- /dev/null +++ b/X10D.Tests/src/Math/Int64Tests.cs @@ -0,0 +1,85 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const long value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, 0L.Factorial()); + Assert.AreEqual(1L, 1L.Factorial()); + Assert.AreEqual(2L, 2L.Factorial()); + Assert.AreEqual(6L, 3L.Factorial()); + Assert.AreEqual(24L, 4L.Factorial()); + Assert.AreEqual(120L, 5L.Factorial()); + Assert.AreEqual(720L, 6L.Factorial()); + Assert.AreEqual(5040L, 7L.Factorial()); + Assert.AreEqual(40320L, 8L.Factorial()); + Assert.AreEqual(362880L, 9L.Factorial()); + Assert.AreEqual(3628800L, 10L.Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const long one = 1; + const long two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const long one = 1; + const long two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, 0L.MultiplicativePersistence()); + Assert.AreEqual(1, 10L.MultiplicativePersistence()); + Assert.AreEqual(2, 25L.MultiplicativePersistence()); + Assert.AreEqual(3, 39L.MultiplicativePersistence()); + Assert.AreEqual(4, 77L.MultiplicativePersistence()); + Assert.AreEqual(5, 679L.MultiplicativePersistence()); + Assert.AreEqual(6, 6788L.MultiplicativePersistence()); + Assert.AreEqual(7, 68889L.MultiplicativePersistence()); + Assert.AreEqual(8, 2677889L.MultiplicativePersistence()); + Assert.AreEqual(9, 26888999L.MultiplicativePersistence()); + Assert.AreEqual(10, 3778888999L.MultiplicativePersistence()); + Assert.AreEqual(11, 277777788888899L.MultiplicativePersistence()); + } + + [TestMethod] + public void NegativeFactorialShouldThrow() + { + Assert.ThrowsException(() => (-1L).Factorial()); + } + + [TestMethod] + public void SignShouldBeCorrect() + { + const long one = 1; + const long zero = 0; + Assert.AreEqual(one, one.Sign()); + Assert.AreEqual(zero, zero.Sign()); + Assert.AreEqual(-one, (-one).Sign()); + } +} diff --git a/X10D.Tests/src/Math/IsPrimeTests.cs b/X10D.Tests/src/Math/IsPrimeTests.cs new file mode 100644 index 0000000..acc577b --- /dev/null +++ b/X10D.Tests/src/Math/IsPrimeTests.cs @@ -0,0 +1,154 @@ +using System.Reflection; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class IsPrimeTests +{ + private IReadOnlyList _primeNumbers = ArraySegment.Empty; + + [TestInitialize] + public void Initialize() + { + _primeNumbers = LoadPrimes(); + Assert.AreEqual(1000, _primeNumbers.Count); + } + + [TestMethod] + public void First1000Primes() + { + for (var index = 0; index < _primeNumbers.Count; index++) + { + int value = _primeNumbers[index]; + + Assert.IsTrue(value.IsPrime()); + Assert.IsTrue(((long)value).IsPrime()); + + if (value is >= byte.MinValue and <= byte.MaxValue) + { + Assert.IsTrue(((byte)value).IsPrime()); + } + + if (value is >= short.MinValue and <= short.MaxValue) + { + Assert.IsTrue(((short)value).IsPrime()); + } + + if (value is >= byte.MinValue and <= byte.MaxValue) + { + Assert.IsTrue(((byte)value).IsPrime()); + } + + if (value is >= ushort.MinValue and <= ushort.MaxValue) + { + Assert.IsTrue(((ushort)value).IsPrime()); + } + + if (value is >= sbyte.MinValue and <= sbyte.MaxValue) + { + Assert.IsTrue(((sbyte)value).IsPrime()); + } + + if (value >= 0) + { + Assert.IsTrue(((uint)value).IsPrime()); + Assert.IsTrue(((ulong)value).IsPrime()); + } + } + } + + [TestMethod] + public void Negatives() + { + for (var value = short.MinValue; value < 0; value++) + { + Assert.IsFalse(value.IsPrime()); + Assert.IsFalse(((int)value).IsPrime()); + Assert.IsFalse(((long)value).IsPrime()); + + if (value is >= sbyte.MinValue and <= sbyte.MaxValue) + { + Assert.IsFalse(((sbyte)value).IsPrime()); + } + } + } + + [TestMethod] + public void LessThan2() + { + for (var value = 0; value < 2; value++) + { + Assert.IsFalse(value.IsPrime()); + Assert.IsFalse(((byte)value).IsPrime()); + Assert.IsFalse(((short)value).IsPrime()); + Assert.IsFalse(((long)value).IsPrime()); + + Assert.IsFalse(((sbyte)value).IsPrime()); + Assert.IsFalse(((ushort)value).IsPrime()); + Assert.IsFalse(((uint)value).IsPrime()); + Assert.IsFalse(((ulong)value).IsPrime()); + } + } + + [TestMethod] + public void ZeroTo7919() + { + for (var value = 0; value < 7920; value++) + { + bool expected = _primeNumbers.Contains(value); + + Assert.AreEqual(expected, ((short)value).IsPrime()); + Assert.AreEqual(expected, value.IsPrime()); + Assert.AreEqual(expected, ((long)value).IsPrime()); + + Assert.AreEqual(expected, ((ushort)value).IsPrime()); + Assert.AreEqual(expected, ((uint)value).IsPrime()); + Assert.AreEqual(expected, ((ulong)value).IsPrime()); + } + } + + [TestMethod] + public void ZeroToByteMaxValue() + { + for (byte value = 0; value < byte.MaxValue; value++) + { + bool expected = _primeNumbers.Contains(value); + + Assert.AreEqual(expected, value.IsPrime()); + Assert.AreEqual(expected, ((short)value).IsPrime()); + Assert.AreEqual(expected, ((int)value).IsPrime()); + Assert.AreEqual(expected, ((long)value).IsPrime()); + + Assert.AreEqual(expected, ((ushort)value).IsPrime()); + Assert.AreEqual(expected, ((uint)value).IsPrime()); + Assert.AreEqual(expected, ((ulong)value).IsPrime()); + + if (value < sbyte.MaxValue) + { + Assert.AreEqual(expected, ((sbyte)value).IsPrime()); + } + } + } + + private static IReadOnlyList LoadPrimes() + { + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("X10D.Tests.1000primes.txt"); + Assert.IsNotNull(stream); + + using var reader = new StreamReader(stream, Encoding.UTF8); + var primes = new List(); + + while (reader.ReadLine() is { } line) + { + if (int.TryParse(line, out int prime)) + { + primes.Add(prime); + } + } + + return primes.AsReadOnly(); + } +} diff --git a/X10D.Tests/src/Math/SByteTests.cs b/X10D.Tests/src/Math/SByteTests.cs new file mode 100644 index 0000000..73b97a2 --- /dev/null +++ b/X10D.Tests/src/Math/SByteTests.cs @@ -0,0 +1,79 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const sbyte value = 127; // sbyte.MaxValue. can't use 238 like the other tests + Assert.AreEqual(1, value.DigitalRoot()); + Assert.AreEqual(1, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, ((sbyte)0).Factorial()); + Assert.AreEqual(1L, ((sbyte)1).Factorial()); + Assert.AreEqual(2L, ((sbyte)2).Factorial()); + Assert.AreEqual(6L, ((sbyte)3).Factorial()); + Assert.AreEqual(24L, ((sbyte)4).Factorial()); + Assert.AreEqual(120L, ((sbyte)5).Factorial()); + Assert.AreEqual(720L, ((sbyte)6).Factorial()); + Assert.AreEqual(5040L, ((sbyte)7).Factorial()); + Assert.AreEqual(40320L, ((sbyte)8).Factorial()); + Assert.AreEqual(362880L, ((sbyte)9).Factorial()); + Assert.AreEqual(3628800L, ((sbyte)10).Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const sbyte one = 1; + const sbyte two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const sbyte one = 1; + const sbyte two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, ((sbyte)0).MultiplicativePersistence()); + Assert.AreEqual(1, ((sbyte)10).MultiplicativePersistence()); + Assert.AreEqual(2, ((sbyte)25).MultiplicativePersistence()); + Assert.AreEqual(3, ((sbyte)39).MultiplicativePersistence()); + Assert.AreEqual(4, ((sbyte)77).MultiplicativePersistence()); + } + + [TestMethod] + public void NegativeFactorialShouldThrow() + { + Assert.ThrowsException(() => ((sbyte)-1).Factorial()); + } + + [TestMethod] + public void SignShouldBeCorrect() + { + const sbyte one = 1; + const sbyte zero = 0; + Assert.AreEqual(one, one.Sign()); + Assert.AreEqual(zero, zero.Sign()); + Assert.AreEqual(-one, (-one).Sign()); + } +} diff --git a/X10D.Tests/src/Math/SingleTests.cs b/X10D.Tests/src/Math/SingleTests.cs new file mode 100644 index 0000000..12b1ae2 --- /dev/null +++ b/X10D.Tests/src/Math/SingleTests.cs @@ -0,0 +1,242 @@ +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class SingleTests +{ + [TestMethod] + public void DegreesToRadians_ShouldBeCorrect() + { + Assert.AreEqual(MathF.PI, 180.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(MathF.PI * 1.5f, 270.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(0.0f, 0.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(0.017453292f, 1.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(0.10471976f, 6.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(0.20943952f, 12.0f.DegreesToRadians(), 1e-6f); + } + + [TestMethod] + public void RadiansToDegrees_ShouldBeCorrect() + { + Assert.AreEqual(180.0f, MathF.PI.RadiansToDegrees(), 1e-6f); + Assert.AreEqual(270.0f, (MathF.PI * 1.5f).RadiansToDegrees(), 1e-6f); + Assert.AreEqual(0.0, 0.0f.RadiansToDegrees(), 1e-6f); + Assert.AreEqual(0.99999994f, 0.017453292f.RadiansToDegrees(), 1e-6f); // rounding errors are fun + Assert.AreEqual(6.0f, 0.10471976f.RadiansToDegrees(), 1e-6f); + Assert.AreEqual(12.0f, 0.20943952f.RadiansToDegrees(), 1e-6f); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeCorrect_GivenReal() + { + Assert.AreEqual(0.0f, 0.0f.ComplexSqrt()); + Assert.AreEqual(1.4142135f, 2.0f.ComplexSqrt()); + Assert.AreEqual(3.0f, 9.0f.ComplexSqrt()); + Assert.AreEqual(4.0f, 16.0f.ComplexSqrt()); + Assert.AreEqual(100.0f, 10000.0f.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeImaginary_GivenNegativeValue() + { + Assert.AreEqual(new Complex(0, 1), (-1.0f).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 1.4142135f), (-2.0f).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 3.0f), (-9.0f).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 4.0f), (-16.0f).ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeComplexInfinity_GivenInfinity() + { + Assert.AreEqual(Complex.Infinity, float.NegativeInfinity.ComplexSqrt()); + Assert.AreEqual(Complex.Infinity, float.PositiveInfinity.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeNaN_GivenNaN() + { + Assert.AreEqual(Complex.NaN, float.NaN.ComplexSqrt()); + } + + [TestMethod] + public void IsEven_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse((-3.0f).IsEven()); + Assert.IsFalse((-1.0f).IsEven()); + Assert.IsFalse(1.0f.IsEven()); + Assert.IsFalse(3.0f.IsEven()); + } + + [TestMethod] + public void IsEven_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-4.0f).IsEven()); + Assert.IsTrue((-2.0f).IsEven()); + Assert.IsTrue(0.0f.IsEven()); + Assert.IsTrue(2.0f.IsEven()); + Assert.IsTrue(4.0f.IsEven()); + } + + [TestMethod] + public void IsOdd_ShouldBeFalse_GivenEvenNumber() + { + Assert.IsFalse((-4.0f).IsOdd()); + Assert.IsFalse((-2.0f).IsOdd()); + Assert.IsFalse(0.0f.IsOdd()); + Assert.IsFalse(2.0f.IsOdd()); + Assert.IsFalse(4.0f.IsOdd()); + } + + [TestMethod] + public void IsOdd_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-3.0f).IsOdd()); + Assert.IsTrue((-1.0f).IsOdd()); + Assert.IsTrue(1.0f.IsOdd()); + Assert.IsTrue(3.0f.IsOdd()); + } + + [TestMethod] + public void Round_ShouldRoundToNearestInteger() + { + Assert.AreEqual(4.0f, 3.5f.Round(), 1e-6f); + Assert.AreEqual(7.0f, 6.8f.Round(), 1e-6f); + Assert.AreEqual(7.0f, 7.2f.Round(), 1e-6f); + } + + [TestMethod] + public void Round_ShouldRoundToNearestMultiple() + { + Assert.AreEqual(5.0f, 3.5f.Round(5), 1e-6f); + Assert.AreEqual(5.0f, 7.0f.Round(5), 1e-6f); + Assert.AreEqual(10.0f, 7.5f.Round(5), 1e-6f); + } + + [TestMethod] + public void Sign_ShouldBeMinus1_GivenNegative() + { + Assert.AreEqual(-1, -1.0f.Sign()); + Assert.AreEqual(-1, -2.0f.Sign()); + Assert.AreEqual(-1, -3.0f.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe0_Given0() + { + Assert.AreEqual(0, 0.0f.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe1_GivenPositive() + { + Assert.AreEqual(1, 1.0f.Sign()); + Assert.AreEqual(1, 2.0f.Sign()); + Assert.AreEqual(1, 3.0f.Sign()); + } + + [TestMethod] + public void Sqrt_ShouldBeCorrect_GivenValue() + { + Assert.AreEqual(0.0f, 0.0f.Sqrt(), 1e-6f); + Assert.AreEqual(1.4142135f, 2.0f.Sqrt(), 1e-6f); + Assert.AreEqual(3.0f, 9.0f.Sqrt(), 1e-6f); + Assert.AreEqual(4.0f, 16.0f.Sqrt(), 1e-6f); + Assert.AreEqual(100.0f, 10000.0f.Sqrt(), 1e-6f); + } + + [TestMethod] + public void Sqrt_ShouldBeNaN_GivenNaN() + { + Assert.AreEqual(float.NaN, float.NaN.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldBeNaN_GivenNegativeValue() + { + Assert.AreEqual(float.NaN, (-1.0f).Sqrt()); + Assert.AreEqual(float.NaN, (-2.0f).Sqrt()); + Assert.AreEqual(float.NaN, (-3.0f).Sqrt()); + Assert.AreEqual(float.NaN, float.NegativeInfinity.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldBePositiveInfinity_GivenPositiveInfinity() + { + Assert.AreEqual(float.PositiveInfinity, float.PositiveInfinity.Sqrt()); + } + + [TestMethod] + public void Acos_ShouldBeCorrect() + { + Assert.AreEqual(1.0471975803375244f, 0.5f.Acos(), 1e-6f); + } + + [TestMethod] + public void Acosh_ShouldBeCorrect() + { + Assert.AreEqual(0.9624236822128296f, 1.5f.Acosh(), 1e-6f); + } + + [TestMethod] + public void Asin_ShouldBeCorrect() + { + Assert.AreEqual(0.5235987901687622f, 0.5f.Asin(), 1e-6f); + } + + [TestMethod] + public void Asinh_ShouldBeCorrect() + { + Assert.AreEqual(1.19476318359375f, 1.5f.Asinh(), 1e-6f); + } + + [TestMethod] + public void Atan_ShouldBeCorrect() + { + Assert.AreEqual(0.46364760398864746, 0.5f.Atan(), 1e-6f); + } + + [TestMethod] + public void Atanh_ShouldBeCorrect() + { + Assert.AreEqual(0.5493061542510986f, 0.5f.Atanh(), 1e-6f); + } + + [TestMethod] + public void Cos_ShouldBeCorrect() + { + Assert.AreEqual(0.8775825500488281f, 0.5f.Cos(), 1e-6f); + } + + [TestMethod] + public void Cosh_ShouldBeCorrect() + { + Assert.AreEqual(2.352409601211548f, 1.5f.Cosh(), 1e-6f); + } + + [TestMethod] + public void Sin_ShouldBeCorrect() + { + Assert.AreEqual(0.4794255495071411, 0.5f.Sin(), 1e-6f); + } + + [TestMethod] + public void Sinh_ShouldBeCorrect() + { + Assert.AreEqual(2.129279375076294f, 1.5f.Sinh(), 1e-6f); + } + + [TestMethod] + public void Tan_ShouldBeCorrect() + { + Assert.AreEqual(0.4794255495071411f, 0.5f.Tan(), 1e-6f); + } + + [TestMethod] + public void Tanh_ShouldBeCorrect() + { + Assert.AreEqual(0.46211716532707214f, 0.5f.Tanh(), 1e-6f); + } +} diff --git a/X10D.Tests/src/Math/UInt16Tests.cs b/X10D.Tests/src/Math/UInt16Tests.cs new file mode 100644 index 0000000..ac8575a --- /dev/null +++ b/X10D.Tests/src/Math/UInt16Tests.cs @@ -0,0 +1,65 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const ushort value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1UL, ((ushort)0).Factorial()); + Assert.AreEqual(1UL, ((ushort)1).Factorial()); + Assert.AreEqual(2UL, ((ushort)2).Factorial()); + Assert.AreEqual(6UL, ((ushort)3).Factorial()); + Assert.AreEqual(24UL, ((ushort)4).Factorial()); + Assert.AreEqual(120UL, ((ushort)5).Factorial()); + Assert.AreEqual(720UL, ((ushort)6).Factorial()); + Assert.AreEqual(5040UL, ((ushort)7).Factorial()); + Assert.AreEqual(40320UL, ((ushort)8).Factorial()); + Assert.AreEqual(362880UL, ((ushort)9).Factorial()); + Assert.AreEqual(3628800UL, ((ushort)10).Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const ushort one = 1; + const ushort two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const ushort one = 1; + const ushort two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, ((ushort)0).MultiplicativePersistence()); + Assert.AreEqual(1, ((ushort)10).MultiplicativePersistence()); + Assert.AreEqual(2, ((ushort)25).MultiplicativePersistence()); + Assert.AreEqual(3, ((ushort)39).MultiplicativePersistence()); + Assert.AreEqual(4, ((ushort)77).MultiplicativePersistence()); + Assert.AreEqual(5, ((ushort)679).MultiplicativePersistence()); + Assert.AreEqual(6, ((ushort)6788).MultiplicativePersistence()); + } +} diff --git a/X10D.Tests/src/Math/UInt32Tests.cs b/X10D.Tests/src/Math/UInt32Tests.cs new file mode 100644 index 0000000..6c06407 --- /dev/null +++ b/X10D.Tests/src/Math/UInt32Tests.cs @@ -0,0 +1,69 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const uint value = 238; + Assert.AreEqual(4U, value.DigitalRoot()); + Assert.AreEqual(4U, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1UL, 0U.Factorial()); + Assert.AreEqual(1UL, 1U.Factorial()); + Assert.AreEqual(2UL, 2U.Factorial()); + Assert.AreEqual(6UL, 3U.Factorial()); + Assert.AreEqual(24UL, 4U.Factorial()); + Assert.AreEqual(120UL, 5U.Factorial()); + Assert.AreEqual(720UL, 6U.Factorial()); + Assert.AreEqual(5040UL, 7U.Factorial()); + Assert.AreEqual(40320UL, 8U.Factorial()); + Assert.AreEqual(362880UL, 9U.Factorial()); + Assert.AreEqual(3628800UL, 10U.Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const uint one = 1; + const uint two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const uint one = 1; + const uint two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, 0U.MultiplicativePersistence()); + Assert.AreEqual(1, 10U.MultiplicativePersistence()); + Assert.AreEqual(2, 25U.MultiplicativePersistence()); + Assert.AreEqual(3, 39U.MultiplicativePersistence()); + Assert.AreEqual(4, 77U.MultiplicativePersistence()); + Assert.AreEqual(5, 679U.MultiplicativePersistence()); + Assert.AreEqual(6, 6788U.MultiplicativePersistence()); + Assert.AreEqual(7, 68889U.MultiplicativePersistence()); + Assert.AreEqual(8, 2677889U.MultiplicativePersistence()); + Assert.AreEqual(9, 26888999U.MultiplicativePersistence()); + Assert.AreEqual(10, 3778888999U.MultiplicativePersistence()); + } +} diff --git a/X10D.Tests/src/Math/UInt64Tests.cs b/X10D.Tests/src/Math/UInt64Tests.cs new file mode 100644 index 0000000..d18a7d6 --- /dev/null +++ b/X10D.Tests/src/Math/UInt64Tests.cs @@ -0,0 +1,74 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const ulong value = 238; + Assert.AreEqual(4U, value.DigitalRoot()); + + // -ulong operator not defined because it might exceed long.MinValue, + // so instead, cast to long and then negate. + // HAX. + Assert.AreEqual(4U, (-(long)value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1UL, 0UL.Factorial()); + Assert.AreEqual(1UL, 1UL.Factorial()); + Assert.AreEqual(2UL, 2UL.Factorial()); + Assert.AreEqual(6UL, 3UL.Factorial()); + Assert.AreEqual(24UL, 4UL.Factorial()); + Assert.AreEqual(120UL, 5UL.Factorial()); + Assert.AreEqual(720UL, 6UL.Factorial()); + Assert.AreEqual(5040UL, 7UL.Factorial()); + Assert.AreEqual(40320UL, 8UL.Factorial()); + Assert.AreEqual(362880UL, 9UL.Factorial()); + Assert.AreEqual(3628800UL, 10UL.Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const ulong one = 1; + const ulong two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const ulong one = 1; + const ulong two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, 0UL.MultiplicativePersistence()); + Assert.AreEqual(1, 10UL.MultiplicativePersistence()); + Assert.AreEqual(2, 25UL.MultiplicativePersistence()); + Assert.AreEqual(3, 39UL.MultiplicativePersistence()); + Assert.AreEqual(4, 77UL.MultiplicativePersistence()); + Assert.AreEqual(5, 679UL.MultiplicativePersistence()); + Assert.AreEqual(6, 6788UL.MultiplicativePersistence()); + Assert.AreEqual(7, 68889UL.MultiplicativePersistence()); + Assert.AreEqual(8, 2677889UL.MultiplicativePersistence()); + Assert.AreEqual(9, 26888999UL.MultiplicativePersistence()); + Assert.AreEqual(10, 3778888999UL.MultiplicativePersistence()); + Assert.AreEqual(11, 277777788888899UL.MultiplicativePersistence()); + } +} diff --git a/X10D.Tests/src/Net/EndPointTests.cs b/X10D.Tests/src/Net/EndPointTests.cs new file mode 100644 index 0000000..8b508d9 --- /dev/null +++ b/X10D.Tests/src/Net/EndPointTests.cs @@ -0,0 +1,76 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class EndPointTests +{ + [TestMethod] + public void GetHost_ShouldBeLocalhost_GivenLocalhostDnsEndPoint() + { + var endPoint = new DnsEndPoint("localhost", 1234); + Assert.AreEqual("localhost", endPoint.GetHost()); + } + + [TestMethod] + public void GetHost_ShouldBe127001_GivenLoopbackIPEndPoint() + { + var endPoint = new IPEndPoint(IPAddress.Loopback, 1234); + Assert.AreEqual("127.0.0.1", endPoint.GetHost()); + } + + [TestMethod] + public void GetHost_ShouldBeColonColon1_GivenIPv6LoopBackIPEndPoint() + { + var endPoint = new IPEndPoint(IPAddress.IPv6Loopback, 1234); + Assert.AreEqual("::1", endPoint.GetHost()); + } + + [TestMethod] + public void GetHost_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((IPEndPoint?)null)!.GetHost()); + Assert.ThrowsException(() => ((DnsEndPoint?)null)!.GetHost()); + } + + [TestMethod] + public void GetPort_ShouldBe1234_Given1234IPEndPoint() + { + var endPoint = new IPEndPoint(IPAddress.Loopback, 1234); + Assert.AreEqual(1234, endPoint.GetPort()); + } + + [TestMethod] + public void GetPort_ShouldBe1234_Given1234DnsEndPoint() + { + var endPoint = new DnsEndPoint("localhost", 1234); + Assert.AreEqual(1234, endPoint.GetPort()); + } + + [TestMethod] + public void GetPort_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((IPEndPoint?)null)!.GetPort()); + Assert.ThrowsException(() => ((DnsEndPoint?)null)!.GetPort()); + } + + [TestMethod] + public void GetHost_ShouldBeEmpty_GivenInvalidEndPoint() + { + var endPoint = new DummyEndPoint(); + Assert.AreEqual(string.Empty, endPoint.GetHost()); + } + + [TestMethod] + public void GetPort_ShouldBe0_GivenInvalidEndPoint() + { + var endPoint = new DummyEndPoint(); + Assert.AreEqual(0, endPoint.GetPort()); + } + + private class DummyEndPoint : EndPoint + { + } +} diff --git a/X10D.Tests/src/Net/IPAddressTests.cs b/X10D.Tests/src/Net/IPAddressTests.cs new file mode 100644 index 0000000..ac86bf6 --- /dev/null +++ b/X10D.Tests/src/Net/IPAddressTests.cs @@ -0,0 +1,43 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class IPAddressTests +{ + private IPAddress _ipv4Address = null!; + private IPAddress _ipv6Address = null!; + + [TestInitialize] + public void Initialize() + { + _ipv4Address = IPAddress.Parse("127.0.0.1"); + _ipv6Address = IPAddress.Parse("::1"); + } + + [TestMethod] + public void IsIPv4_ShouldBeTrue_GivenIPv4() + { + Assert.IsTrue(_ipv4Address.IsIPv4()); + } + + [TestMethod] + public void IsIPv4_ShouldBeFalse_GivenIPv6() + { + Assert.IsFalse(_ipv6Address.IsIPv4()); + } + + [TestMethod] + public void IsIPv6_ShouldBeFalse_GivenIPv4() + { + Assert.IsFalse(_ipv4Address.IsIPv6()); + } + + [TestMethod] + public void IsIPv6_ShouldBeTrue_GivenIPv6() + { + Assert.IsTrue(_ipv6Address.IsIPv6()); + } +} diff --git a/X10D.Tests/src/Net/Int16Tests.cs b/X10D.Tests/src/Net/Int16Tests.cs new file mode 100644 index 0000000..9dea812 --- /dev/null +++ b/X10D.Tests/src/Net/Int16Tests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void HostToNetworkOrder_ReturnsCorrectValue() + { + const short hostOrder = 0x0102; + const short networkOrder = 0x0201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? networkOrder : hostOrder, hostOrder.HostToNetworkOrder()); + } + + [TestMethod] + public void NetworkToHostOrder_ReturnsCorrectValue() + { + const short hostOrder = 0x0102; + const short networkOrder = 0x0201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? hostOrder : networkOrder, networkOrder.NetworkToHostOrder()); + } +} diff --git a/X10D.Tests/src/Net/Int32Tests.cs b/X10D.Tests/src/Net/Int32Tests.cs new file mode 100644 index 0000000..7157eef --- /dev/null +++ b/X10D.Tests/src/Net/Int32Tests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void HostToNetworkOrder_ReturnsCorrectValue() + { + const int hostOrder = 0x01020304; + const int networkOrder = 0x04030201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? networkOrder : hostOrder, hostOrder.HostToNetworkOrder()); + } + + [TestMethod] + public void NetworkToHostOrder_ReturnsCorrectValue() + { + const int hostOrder = 0x01020304; + const int networkOrder = 0x04030201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? hostOrder : networkOrder, networkOrder.NetworkToHostOrder()); + } +} diff --git a/X10D.Tests/src/Net/Int64Tests.cs b/X10D.Tests/src/Net/Int64Tests.cs new file mode 100644 index 0000000..c207125 --- /dev/null +++ b/X10D.Tests/src/Net/Int64Tests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void HostToNetworkOrder_ReturnsCorrectValue() + { + const long hostOrder = 0x0102030405060708; + const long networkOrder = 0x0807060504030201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? networkOrder : hostOrder, hostOrder.HostToNetworkOrder()); + } + + [TestMethod] + public void NetworkToHostOrder_ReturnsCorrectValue() + { + const long hostOrder = 0x0102030405060708; + const long networkOrder = 0x0807060504030201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? hostOrder : networkOrder, networkOrder.NetworkToHostOrder()); + } +} diff --git a/X10D.Tests/src/Numerics/ByteTests.cs b/X10D.Tests/src/Numerics/ByteTests.cs new file mode 100644 index 0000000..d4890b2 --- /dev/null +++ b/X10D.Tests/src/Numerics/ByteTests.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const byte value = 181; // 10110101 + const byte expected = 91; // 01011011 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(4)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const byte value = 181; // 10110101 + Assert.AreEqual(value, value.RotateLeft(8)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const byte value = 181; // 10110101 + const byte expected = 91; // 01011011 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(4)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const byte value = 181; // 10110101 + Assert.AreEqual(value, value.RotateRight(8)); + } +} diff --git a/X10D.Tests/src/Numerics/Int16Tests.cs b/X10D.Tests/src/Numerics/Int16Tests.cs new file mode 100644 index 0000000..11a4a3d --- /dev/null +++ b/X10D.Tests/src/Numerics/Int16Tests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const short value = 2896; // 00001011 01010000 + const short expected = 27137; // 01101010 00000001 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(5)); + Assert.AreEqual(value, value.RotateLeft(16)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const short value = 2896; // 00001011 01010000 + Assert.AreEqual(value, value.RotateLeft(16)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const short value = 2896; // 00001011 01010000 + const short expected = -32678; // 01111111 10100110 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(5)); + Assert.AreEqual(value, value.RotateRight(16)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const short value = 2896; // 00001011 01010000 + Assert.AreEqual(value, value.RotateRight(16)); + } +} diff --git a/X10D.Tests/src/Numerics/Int32Tests.cs b/X10D.Tests/src/Numerics/Int32Tests.cs new file mode 100644 index 0000000..874b5af --- /dev/null +++ b/X10D.Tests/src/Numerics/Int32Tests.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const int value = 284719; // 00000000 00000100 01011000 00101111 + const int expected = -1336016888; // 10110000 01011110 00000000 00001000 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(17)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const int value = 284719; // 00000000 00000100 01011000 00101111 + Assert.AreEqual(value, value.RotateLeft(32)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const int value = 284719; // 00000000 00000100 01011000 00101111 + const int expected = 739737602; // 00101100 00010111 10000000 00000010 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(17)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const int value = 284719; // 00000000 00000100 01011000 00101111 + Assert.AreEqual(value, value.RotateRight(32)); + } +} diff --git a/X10D.Tests/src/Numerics/Int64Tests.cs b/X10D.Tests/src/Numerics/Int64Tests.cs new file mode 100644 index 0000000..2dff9cf --- /dev/null +++ b/X10D.Tests/src/Numerics/Int64Tests.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const long value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + const long expected = -1588168355691398970; // 11101001 11110101 10110001 01001011 10000011 01111111 01111000 11000110 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(42)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const long value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + Assert.AreEqual(value, value.RotateLeft(64)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const long value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + const long expected = -608990218894919625; // 11110111 10001100 01101110 10011111 01011011 00010100 10111000 00110111 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(42)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const long value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + Assert.AreEqual(value, value.RotateRight(64)); + } +} diff --git a/X10D.Tests/src/Numerics/RandomTests.cs b/X10D.Tests/src/Numerics/RandomTests.cs new file mode 100644 index 0000000..3864574 --- /dev/null +++ b/X10D.Tests/src/Numerics/RandomTests.cs @@ -0,0 +1,68 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class RandomTests +{ + [TestMethod] + public void NextUnitVector2_ShouldReturnVector_WithMagnitude1() + { + var random = new Random(); + var vector = random.NextUnitVector2(); + Assert.AreEqual(1, vector.Length(), 1e-6); + } + + [TestMethod] + public void NextUnitVector2_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextUnitVector2()); + } + + [TestMethod] + public void NextUnitVector3_ShouldReturnVector_WithMagnitude1() + { + var random = new Random(); + var vector = random.NextUnitVector3(); + Assert.AreEqual(1, vector.Length(), 1e-6); + } + + [TestMethod] + public void NextUnitVector3_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextUnitVector3()); + } + + [TestMethod] + public void NextRotation_ShouldReturnQuaternion_WithMagnitude1() + { + var random = new Random(); + var rotation = random.NextRotation(); + Assert.AreEqual(1, rotation.Length(), 1e-6); + } + + [TestMethod] + public void NextRotation_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextRotation()); + } + + [TestMethod] + public void NextRotationUniform_ShouldReturnQuaternion_WithMagnitude1() + { + var random = new Random(); + var rotation = random.NextRotationUniform(); + Assert.AreEqual(1, rotation.Length(), 1e-6); + } + + [TestMethod] + public void NextRotationUniform_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextRotationUniform()); + } +} diff --git a/X10D.Tests/src/Numerics/SByteTests.cs b/X10D.Tests/src/Numerics/SByteTests.cs new file mode 100644 index 0000000..c24ebcf --- /dev/null +++ b/X10D.Tests/src/Numerics/SByteTests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const sbyte value = 117; // 01110101 + const sbyte expected = 87; // 01010111 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(4)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const sbyte value = 117; // 01110101 + Assert.AreEqual(value, value.RotateLeft(8)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const sbyte value = 117; // 01110101 + const sbyte expected = 87; // 01010111 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(4)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const sbyte value = 117; // 01110101 + Assert.AreEqual(value, value.RotateRight(8)); + } +} diff --git a/X10D.Tests/src/Numerics/UInt16Tests.cs b/X10D.Tests/src/Numerics/UInt16Tests.cs new file mode 100644 index 0000000..23a5d39 --- /dev/null +++ b/X10D.Tests/src/Numerics/UInt16Tests.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const ushort value = 2896; // 00001011 01010000 + const ushort expected = 27137; // 01101010 00000001 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(5)); + Assert.AreEqual(value, value.RotateLeft(16)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const ushort value = 2896; // 00001011 01010000 + Assert.AreEqual(value, value.RotateLeft(16)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const ushort value = 2896; // 00001011 01010000 + const ushort expected = 32858; // 10000000 01011010 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(5)); + Assert.AreEqual(value, value.RotateRight(16)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const ushort value = 2896; // 00001011 01010000 + Assert.AreEqual(value, value.RotateRight(16)); + } +} diff --git a/X10D.Tests/src/Numerics/UInt32Tests.cs b/X10D.Tests/src/Numerics/UInt32Tests.cs new file mode 100644 index 0000000..5686953 --- /dev/null +++ b/X10D.Tests/src/Numerics/UInt32Tests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const uint value = 284719; // 00000000 00000100 01011000 00101111 + const uint expected = 2958950408; // 10110000 01011110 00000000 00001000 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(17)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const uint value = 284719; // 00000000 00000100 01011000 00101111 + Assert.AreEqual(value, value.RotateLeft(32)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const uint value = 284719; // 00000000 00000100 01011000 00101111 + const uint expected = 739737602; // 00101100 00010111 10000000 00000010 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(17)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const uint value = 284719; // 00000000 00000100 01011000 00101111 + Assert.AreEqual(value, value.RotateRight(32)); + } +} diff --git a/X10D.Tests/src/Numerics/UInt64Tests.cs b/X10D.Tests/src/Numerics/UInt64Tests.cs new file mode 100644 index 0000000..7d2f026 --- /dev/null +++ b/X10D.Tests/src/Numerics/UInt64Tests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const ulong value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + const ulong expected = 16858575718018152646; // 11101001 11110101 10110001 01001011 10000011 01111111 01111000 11000110 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(42)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const ulong value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + Assert.AreEqual(value, value.RotateLeft(64)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const ulong value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + const ulong expected = 17837753854814631991; // 11110111 10001100 01101110 10011111 01011011 00010100 10111000 00110111 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(42)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const ulong value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + Assert.AreEqual(value, value.RotateRight(64)); + } +} diff --git a/X10D.Tests/src/Reflection/MemberInfoTests.cs b/X10D.Tests/src/Reflection/MemberInfoTests.cs new file mode 100644 index 0000000..24d2675 --- /dev/null +++ b/X10D.Tests/src/Reflection/MemberInfoTests.cs @@ -0,0 +1,78 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Reflection; + +namespace X10D.Tests.Reflection; + +[TestClass] +public class MemberInfoTests +{ + [TestMethod] + public void HasCustomAttribute_ShouldBeTrue_GivenCLSCompliantAttributeOnUnsignedTypes() + { + Assert.IsTrue(typeof(sbyte).HasCustomAttribute(typeof(CLSCompliantAttribute))); // okay, sbyte is signed. I know. + Assert.IsTrue(typeof(ushort).HasCustomAttribute(typeof(CLSCompliantAttribute))); + Assert.IsTrue(typeof(uint).HasCustomAttribute(typeof(CLSCompliantAttribute))); + Assert.IsTrue(typeof(ulong).HasCustomAttribute(typeof(CLSCompliantAttribute))); + } + + [TestMethod] + public void HasCustomAttribute_ShouldBeTrue_GivenCLSCompliantAttributeOnUnsignedTypes_Generic() + { + Assert.IsTrue(typeof(sbyte).HasCustomAttribute()); // okay, sbyte is signed. I know. + Assert.IsTrue(typeof(ushort).HasCustomAttribute()); + Assert.IsTrue(typeof(uint).HasCustomAttribute()); + Assert.IsTrue(typeof(ulong).HasCustomAttribute()); + } + + [TestMethod] + public void HasCustomAttribute_ShouldThrow_GivenNull() + { + Type? type = null; + Assert.ThrowsException(() => type!.HasCustomAttribute()); + Assert.ThrowsException(() => type!.HasCustomAttribute(typeof(CLSCompliantAttribute))); + + Assert.ThrowsException(() => typeof(object).HasCustomAttribute(null!)); + } + + [TestMethod] + public void HasCustomAttribute_ShouldThrow_GivenNonAttribute() + { + Assert.ThrowsException(() => typeof(object).HasCustomAttribute(typeof(object))); + } + + [TestMethod] + public void SelectFromCustomAttribute_ShouldBeFalse_GivenCLSCompliantAttributeOnUnsignedTypes() + { + Assert.IsFalse(typeof(sbyte).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant)); + Assert.IsFalse(typeof(ushort).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant)); + Assert.IsFalse(typeof(uint).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant)); + Assert.IsFalse(typeof(ulong).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant)); + } + + [TestMethod] + public void SelectFromCustomAttribute_ShouldBeTrue_GivenCLSCompliantAttributeOnSignedTypes() + { + Assert.IsTrue(typeof(byte).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true)); + Assert.IsTrue(typeof(short).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true)); + Assert.IsTrue(typeof(int).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true)); + Assert.IsTrue(typeof(long).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true)); + } + + [TestMethod] + public void SelectFromCustomAttribute_ShouldThrow_GivenNull() + { + Type? type = null; + + Assert.ThrowsException(() => + (type!.SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant))); + + Assert.ThrowsException(() => + (type!.SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true))); + + Assert.ThrowsException(() => + { + Func? selector = null; + typeof(int).SelectFromCustomAttribute(selector!); + }); + } +} diff --git a/X10D.Tests/src/Reflection/TypeTests.cs b/X10D.Tests/src/Reflection/TypeTests.cs new file mode 100644 index 0000000..4324afb --- /dev/null +++ b/X10D.Tests/src/Reflection/TypeTests.cs @@ -0,0 +1,59 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Reflection; + +namespace X10D.Tests.Reflection; + +[TestClass] +public class TypeTests +{ + [TestMethod] + public void Inherits_ShouldBeTrue_GivenStringInheritsObject() + { + Assert.IsTrue(typeof(string).Inherits(typeof(object))); + Assert.IsTrue(typeof(string).Inherits()); + } + + [TestMethod] + public void Inherits_ShouldBeFalse_GivenObjectInheritsString() + { + Assert.IsFalse(typeof(object).Inherits(typeof(string))); + Assert.IsFalse(typeof(object).Inherits()); + } + + [TestMethod] + public void Inherits_ShouldThrow_GivenValueType() + { + Assert.ThrowsException(() => typeof(int).Inherits(typeof(object))); + Assert.ThrowsException(() => typeof(object).Inherits(typeof(int))); + } + + [TestMethod] + public void Inherits_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => typeof(object).Inherits(null!)); + Assert.ThrowsException(() => ((Type?)null)!.Inherits(typeof(object))); + } + + [TestMethod] + public void Implements_ShouldBeTrue_GivenInt32ImplementsIComparable() + { + Assert.IsTrue(typeof(int).Implements()); + Assert.IsTrue(typeof(int).Implements>()); + Assert.IsTrue(typeof(int).Implements(typeof(IComparable))); + Assert.IsTrue(typeof(int).Implements(typeof(IComparable))); + } + + [TestMethod] + public void Implements_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => typeof(object).Implements(null!)); + Assert.ThrowsException(() => ((Type?)null)!.Implements(typeof(object))); + } + + [TestMethod] + public void Implements_ShouldThrow_GivenNonInterface() + { + Assert.ThrowsException(() => typeof(string).Implements()); + Assert.ThrowsException(() => typeof(string).Implements(typeof(object))); + } +} diff --git a/X10D.Tests/src/Text/CharTests.cs b/X10D.Tests/src/Text/CharTests.cs new file mode 100644 index 0000000..cea0606 --- /dev/null +++ b/X10D.Tests/src/Text/CharTests.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class CharTests +{ + [TestMethod] + public void RepeatShouldBeCorrect() + { + const string expected = "aaaaaaaaaa"; + string actual = 'a'.Repeat(10); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void RepeatOneCountShouldBeLength1String() + { + string repeated = 'a'.Repeat(1); + Assert.AreEqual(1, repeated.Length); + Assert.AreEqual("a", repeated); + } + + [TestMethod] + public void RepeatZeroCountShouldBeEmpty() + { + Assert.AreEqual(string.Empty, 'a'.Repeat(0)); + } + + [TestMethod] + public void RepeatNegativeCountShouldThrow() + { + Assert.ThrowsException(() => 'a'.Repeat(-1)); + } +} diff --git a/X10D.Tests/src/Text/CoreTests.cs b/X10D.Tests/src/Text/CoreTests.cs new file mode 100644 index 0000000..f6d9aea --- /dev/null +++ b/X10D.Tests/src/Text/CoreTests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class CoreTests +{ + [TestMethod] + public void ToJsonShouldNotBeEmpty() + { + object? obj = null; + string json = obj.ToJson(); + Assert.IsFalse(string.IsNullOrEmpty(json)); + } + + [TestMethod] + public void ToJsonShouldDeserializeEquivalent() + { + int[] source = Enumerable.Range(1, 100).ToArray(); + string json = source.ToJson(); + int[]? target = json.FromJson(); + CollectionAssert.AreEqual(source, target); + CollectionAssert.AreEquivalent(source, target); + } +} diff --git a/X10D.Tests/src/Text/RuneTests.cs b/X10D.Tests/src/Text/RuneTests.cs new file mode 100644 index 0000000..7d3315d --- /dev/null +++ b/X10D.Tests/src/Text/RuneTests.cs @@ -0,0 +1,39 @@ +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class RuneTests +{ + [TestMethod] + public void RepeatShouldBeCorrect() + { + const string expected = "aaaaaaaaaa"; + var rune = new Rune('a'); + string actual = rune.Repeat(10); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void RepeatOneCountShouldBeLength1String() + { + string repeated = new Rune('a').Repeat(1); + Assert.AreEqual(1, repeated.Length); + Assert.AreEqual("a", repeated); + } + + [TestMethod] + public void RepeatZeroCountShouldBeEmpty() + { + Assert.AreEqual(string.Empty, new Rune('a').Repeat(0)); + } + + [TestMethod] + public void RepeatNegativeCountShouldThrow() + { + Assert.ThrowsException(() => new Rune('a').Repeat(-1)); + } +} diff --git a/X10D.Tests/src/Text/StringBuilderReaderTests.cs b/X10D.Tests/src/Text/StringBuilderReaderTests.cs new file mode 100644 index 0000000..bdd0848 --- /dev/null +++ b/X10D.Tests/src/Text/StringBuilderReaderTests.cs @@ -0,0 +1,271 @@ +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class StringBuilderReaderTests +{ + [TestMethod] + public void Peek_ShouldReturnNextChar_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Assert.AreEqual('H', reader.Peek()); + + reader.Close(); + } + + [TestMethod] + public void Read_ShouldReturnNextChar_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Assert.AreEqual('H', reader.Read()); + Assert.AreEqual('e', reader.Read()); + Assert.AreEqual('l', reader.Read()); + Assert.AreEqual('l', reader.Read()); + Assert.AreEqual('o', reader.Read()); + Assert.AreEqual('\n', reader.Read()); + Assert.AreEqual('W', reader.Read()); + Assert.AreEqual('o', reader.Read()); + Assert.AreEqual('r', reader.Read()); + Assert.AreEqual('l', reader.Read()); + Assert.AreEqual('d', reader.Read()); + Assert.AreEqual(-1, reader.Read()); + + reader.Close(); + } + + [TestMethod] + public void Read_ShouldPopulateArray_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[5]; + int read = reader.Read(array, 0, 5); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), array); + + reader.Close(); + } + + [TestMethod] + public void Read_ShouldReturnNegative1_GivenEndOfReader() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + var array = new char[11]; + reader.Read(array); + Assert.AreEqual(-1, reader.Read(array, 0, 1)); + reader.Close(); + } + + [TestMethod] + public void Read_ShouldThrow_GivenNullArray() + { + Assert.ThrowsException(() => + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + reader.Read(null!, 0, 5); + reader.Close(); + }); + } + + [TestMethod] + public void Read_ShouldThrow_GivenNegativeIndex() + { + Assert.ThrowsException(() => + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + var array = new char[5]; + reader.Read(array, -1, 5); + reader.Close(); + }); + } + + [TestMethod] + public void Read_ShouldThrow_GivenNegativeCount() + { + Assert.ThrowsException(() => + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + var array = new char[5]; + reader.Read(array, 0, -1); + reader.Close(); + }); + } + + [TestMethod] + public void Read_ShouldThrow_GivenSmallBuffer() + { + Assert.ThrowsException(() => + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + var array = new char[1]; + reader.Read(array, 0, 5); + reader.Close(); + }); + } + + [TestMethod] + public void Read_ShouldPopulateSpan_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Span span = stackalloc char[5]; + int read = reader.Read(span); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), span.ToArray()); + + reader.Close(); + } + + [TestMethod] + public void ReadAsync_ShouldPopulateArray_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[5]; + int read = reader.ReadAsync(array, 0, 5).GetAwaiter().GetResult(); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), array); + + reader.Close(); + } + + [TestMethod] + public void ReadAsync_ShouldPopulateMemory_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Memory memory = new char[5]; + int read = reader.ReadAsync(memory).GetAwaiter().GetResult(); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), memory.ToArray()); + + reader.Close(); + } + + [TestMethod] + public void ReadBlock_ShouldPopulateArray_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[5]; + int read = reader.ReadBlock(array, 0, 5); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), array); + + reader.Close(); + } + + [TestMethod] + public void ReadBlock_ShouldPopulateSpan_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Span span = stackalloc char[5]; + int read = reader.ReadBlock(span); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), span.ToArray()); + + reader.Close(); + } + + [TestMethod] + public void ReadBlock_ShouldReturnNegative1_GivenEndOfReader() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[11]; + reader.Read(array); + + int read = reader.ReadBlock(array, 0, 5); + Assert.AreEqual(-1, read); + + reader.Close(); + } + + [TestMethod] + public void ReadBlockAsync_ShouldPopulateArray_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[5]; + int read = reader.ReadBlockAsync(array, 0, 5).GetAwaiter().GetResult(); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), array); + + reader.Close(); + } + + [TestMethod] + public void ReadBlockAsync_ShouldPopulateMemory_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Memory memory = new char[5]; + int read = reader.ReadBlockAsync(memory).GetAwaiter().GetResult(); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), memory.ToArray()); + + reader.Close(); + } + + [TestMethod] + public void ReadToEnd_ShouldReturnSourceString_GivenBuilder() + { + const string value = "Hello World"; + using var reader = new StringBuilderReader(new StringBuilder(value)); + Assert.AreEqual(value, reader.ReadToEnd()); + + reader.Close(); + } + + [TestMethod] + public void ReadToEndAsync_ShouldReturnSourceString_GivenBuilder() + { + const string value = "Hello World"; + using var reader = new StringBuilderReader(new StringBuilder(value)); + Assert.AreEqual(value, reader.ReadToEndAsync().GetAwaiter().GetResult()); + + reader.Close(); + } + + [TestMethod] + public void ReadLine_ShouldReturnSourceString_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Assert.AreEqual("Hello", reader.ReadLine()); + Assert.AreEqual("World", reader.ReadLine()); + Assert.AreEqual(null, reader.ReadLine()); + + Assert.AreEqual(-1, reader.Peek()); + + reader.Close(); + } + + [TestMethod] + public void ReadLineAsync_ShouldReturnSourceString_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Assert.AreEqual("Hello", reader.ReadLineAsync().GetAwaiter().GetResult()); + Assert.AreEqual("World", reader.ReadLineAsync().GetAwaiter().GetResult()); + Assert.AreEqual(null, reader.ReadLineAsync().GetAwaiter().GetResult()); + + Assert.AreEqual(-1, reader.Peek()); + + reader.Close(); + } +} diff --git a/X10D.Tests/src/Text/StringTests.cs b/X10D.Tests/src/Text/StringTests.cs new file mode 100644 index 0000000..5d509f0 --- /dev/null +++ b/X10D.Tests/src/Text/StringTests.cs @@ -0,0 +1,449 @@ +using System.Text; +using System.Text.Json.Serialization; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class StringTests +{ + [TestMethod] + public void AsNullIfEmpty_ShouldBeCorrect() + { + const string sampleString = "Hello World"; + const string whitespaceString = " "; + const string emptyString = ""; + const string? nullString = null; + + string sampleResult = sampleString.AsNullIfEmpty(); + string whitespaceResult = whitespaceString.AsNullIfEmpty(); + string emptyResult = emptyString.AsNullIfEmpty(); + string? nullResult = nullString.AsNullIfEmpty(); + + Assert.AreEqual(sampleString, sampleResult); + Assert.AreEqual(whitespaceString, whitespaceResult); + Assert.AreEqual(nullString, emptyResult); + Assert.AreEqual(nullString, nullResult); + } + + [TestMethod] + public void AsNullIfWhiteSpace_ShouldBeCorrect() + { + const string sampleString = "Hello World"; + const string whitespaceString = " "; + const string emptyString = ""; + const string? nullString = null; + + string sampleResult = sampleString.AsNullIfWhiteSpace(); + string whitespaceResult = whitespaceString.AsNullIfWhiteSpace(); + string emptyResult = emptyString.AsNullIfWhiteSpace(); + string? nullResult = nullString.AsNullIfWhiteSpace(); + + Assert.AreEqual(sampleString, sampleResult); + Assert.AreEqual(nullString, whitespaceResult); + Assert.AreEqual(nullString, emptyResult); + Assert.AreEqual(nullString, nullResult); + } + + [TestMethod] + public void Base64Decode_ShouldReturnHelloWorld_GivenBase64String() + { + Assert.AreEqual("Hello World", "SGVsbG8gV29ybGQ=".Base64Decode()); + } + + + [TestMethod] + public void Base64Decode_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Base64Decode()); + } + + + [TestMethod] + public void Base64Encode_ShouldReturnBase64String_GivenHelloWorld() + { + Assert.AreEqual("SGVsbG8gV29ybGQ=", "Hello World".Base64Encode()); + } + + [TestMethod] + public void Base64Encode_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Base64Encode()); + } + + [TestMethod] + public void ChangeEncoding_ShouldReturnAsciiString_GivenUtf8() + { + Assert.AreEqual("Hello World", "Hello World".ChangeEncoding(Encoding.UTF8, Encoding.ASCII)); + } + + [TestMethod] + public void ChangeEncoding_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => value!.ChangeEncoding(Encoding.UTF8, Encoding.ASCII)); + } + + [TestMethod] + public void ChangeEncoding_ShouldThrow_GivenNullSourceEncoding() + { + Assert.ThrowsException(() => "Hello World".ChangeEncoding(null!, Encoding.ASCII)); + } + + [TestMethod] + public void ChangeEncoding_ShouldThrow_GivenNullDestinationEncoding() + { + Assert.ThrowsException(() => "Hello World".ChangeEncoding(Encoding.UTF8, null!)); + } + + [TestMethod] + public void EnumParse_ShouldReturnCorrectValue_GivenString() + { + Assert.AreEqual(DayOfWeek.Monday, "Monday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Tuesday, "Tuesday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Wednesday, "Wednesday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Thursday, "Thursday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Friday, "Friday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Saturday, "Saturday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Sunday, "Sunday".EnumParse(false)); + } + + [TestMethod] + public void EnumParse_ShouldTrim() + { + Assert.AreEqual(DayOfWeek.Monday, " Monday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Tuesday, " Tuesday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Wednesday, " Wednesday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Thursday, " Thursday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Friday, " Friday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Saturday, " Saturday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Sunday, " Sunday ".EnumParse()); + } + + [TestMethod] + public void EnumParse_ShouldReturnCorrectValue_GivenString_Generic() + { + Assert.AreEqual(DayOfWeek.Monday, "Monday".EnumParse()); + Assert.AreEqual(DayOfWeek.Tuesday, "Tuesday".EnumParse()); + Assert.AreEqual(DayOfWeek.Wednesday, "Wednesday".EnumParse()); + Assert.AreEqual(DayOfWeek.Thursday, "Thursday".EnumParse()); + Assert.AreEqual(DayOfWeek.Friday, "Friday".EnumParse()); + Assert.AreEqual(DayOfWeek.Saturday, "Saturday".EnumParse()); + Assert.AreEqual(DayOfWeek.Sunday, "Sunday".EnumParse()); + } + + [TestMethod] + public void EnumParse_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => value!.EnumParse()); + } + + [TestMethod] + public void EnumParse_ShouldThrow_GivenEmptyOrWhiteSpaceString() + { + Assert.ThrowsException(() => string.Empty.EnumParse()); + Assert.ThrowsException(() => " ".EnumParse()); + } + + [TestMethod] + public void FromJson_ShouldDeserializeCorrectly_GivenJsonString() + { + const string json = "{\"values\": [1, 2, 3]}"; + var target = json.FromJson(); + Assert.IsInstanceOfType(target, typeof(SampleStructure)); + Assert.IsNotNull(target); + Assert.IsNotNull(target.Values); + Assert.AreEqual(3, target.Values.Length); + Assert.AreEqual(1, target.Values[0]); + Assert.AreEqual(2, target.Values[1]); + Assert.AreEqual(3, target.Values[2]); + } + + [TestMethod] + public void GetBytes_ShouldReturnUtf8Bytes_GivenHelloWorld() + { + var expected = new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64}; + byte[] actual = "Hello World".GetBytes(); + + CollectionAssert.AreEqual(expected, actual); + } + + [TestMethod] + public void GetBytes_ShouldReturnAsciiBytes_GivenHelloWorld() + { + var expected = new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64}; + byte[] actual = "Hello World".GetBytes(Encoding.ASCII); + + CollectionAssert.AreEqual(expected, actual); + } + + [TestMethod] + public void GetBytes_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => value!.GetBytes()); + Assert.ThrowsException(() => value!.GetBytes(Encoding.ASCII)); + } + + [TestMethod] + public void GetBytes_ShouldThrow_GivenNullEncoding() + { + Assert.ThrowsException(() => "Hello World".GetBytes(null!)); + } + + [TestMethod] + public void IsLower_ShouldReturnTrue_GivenLowercaseString() + { + Assert.IsTrue("hello world".IsLower()); + } + + [TestMethod] + public void IsLower_ShouldReturnFalse_GivenMixedCaseString() + { + Assert.IsFalse("Hello World".IsLower()); + } + + [TestMethod] + public void IsLower_ShouldReturnFalse_GivenUppercaseString() + { + Assert.IsFalse("HELLO WORLD".IsLower()); + } + + [TestMethod] + public void IsLower_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.IsLower()); + } + + [TestMethod] + public void IsPalindrome_ShouldBeCorrect_GivenString() + { + const string inputA = "Race car"; + const string inputB = "Racecar"; + const string inputC = "A man, a plan, a canal, panama"; + const string inputD = "Jackdaws love my big sphinx of quartz"; + const string inputE = "Y"; + const string inputF = "1"; + + Assert.IsTrue(inputA.IsPalindrome(), inputA); + Assert.IsTrue(inputB.IsPalindrome(), inputB); + Assert.IsTrue(inputC.IsPalindrome(), inputC); + Assert.IsFalse(inputD.IsPalindrome(), inputD); + Assert.IsTrue(inputE.IsPalindrome(), inputE); + Assert.IsTrue(inputF.IsPalindrome(), inputF); + } + + [TestMethod] + public void IsPalindrome_ShouldReturnFalse_GivenEmptyString() + { + Assert.IsFalse(string.Empty.IsPalindrome()); + } + + [TestMethod] + public void IsPalindrome_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((string?)null)!.IsPalindrome()); + } + + [TestMethod] + public void IsUpper_ShouldReturnFalse_GivenLowercaseString() + { + Assert.IsFalse("hello world".IsUpper()); + } + + [TestMethod] + public void IsUpper_ShouldReturnFalse_GivenMixedCaseString() + { + Assert.IsFalse("Hello World".IsUpper()); + } + + [TestMethod] + public void IsUpper_ShouldReturnTrue_GivenUppercaseString() + { + Assert.IsTrue("HELLO WORLD".IsUpper()); + } + + [TestMethod] + public void IsUpper_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.IsUpper()); + } + + [TestMethod] + public void Randomize_ShouldReorder_GivenString() + { + const string input = "Hello World"; + var random = new Random(1); + Assert.AreEqual("le rooldeoH", input.Randomize(input.Length, random)); + } + + [TestMethod] + public void Randomize_ShouldReturnEmptyString_GivenLength1() + { + Assert.AreEqual(string.Empty, "Hello World".Randomize(0)); + } + + [TestMethod] + public void Randomize_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Randomize(1)); + } + + [TestMethod] + public void Randomize_ShouldThrow_GivenNegativeLength() + { + Assert.ThrowsException(() => string.Empty.Randomize(-1)); + } + + [TestMethod] + public void Repeat_ShouldReturnRepeatedString_GivenString() + { + const string expected = "aaaaaaaaaa"; + string actual = "a".Repeat(10); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void Repeat_ShouldReturnEmptyString_GivenCount0() + { + Assert.AreEqual(string.Empty, "a".Repeat(0)); + } + + [TestMethod] + public void Repeat_ShouldReturnItself_GivenCount1() + { + string repeated = "a".Repeat(1); + Assert.AreEqual(1, repeated.Length); + Assert.AreEqual("a", repeated); + } + + [TestMethod] + public void Repeat_ShouldThrow_GivenNegativeCount() + { + Assert.ThrowsException(() => "a".Repeat(-1)); + } + + [TestMethod] + public void Repeat_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Repeat(0)); + } + + [TestMethod] + public void Reverse_ShouldBeCorrect() + { + const string input = "Hello World"; + const string expected = "dlroW olleH"; + + string result = input.Reverse(); + + Assert.AreEqual(string.Empty.Reverse(), string.Empty); + Assert.AreEqual(" ".Reverse(), " "); + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void Reverse_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Reverse()); + } + + [TestMethod] + public void Shuffled_ShouldReorder_GivenString() + { + const string alphabet = "abcdefghijklmnopqrstuvwxyz"; + string shuffled = alphabet; + + Assert.AreEqual(alphabet, shuffled); + + shuffled = alphabet.Shuffled(); + + Assert.AreNotEqual(alphabet, shuffled); + } + + [TestMethod] + public void Shuffled_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Shuffled()); + } + + [TestMethod] + public void Split_ShouldYieldCorrectStrings_GivenString() + { + string[] chunks = "Hello World".Split(2).ToArray(); + Assert.AreEqual(6, chunks.Length); + Assert.AreEqual("He", chunks[0]); + Assert.AreEqual("ll", chunks[1]); + Assert.AreEqual("o ", chunks[2]); + Assert.AreEqual("Wo", chunks[3]); + Assert.AreEqual("rl", chunks[4]); + Assert.AreEqual("d", chunks[5]); + } + + [TestMethod] + public void Split_ShouldYieldEmptyString_GivenChunkSize0() + { + string[] chunks = "Hello World".Split(0).ToArray(); + Assert.AreEqual(1, chunks.Length); + Assert.AreEqual(string.Empty, chunks[0]); + } + + [TestMethod] + public void Split_ShouldThrow_GivenNullString() + { + string? value = null; + + // forcing enumeration with ToArray is required for the exception to be thrown + Assert.ThrowsException(() => value!.Split(0).ToArray()); + } + + [TestMethod] + public void WithEmptyAlternative_ShouldBeCorrect() + { + const string inputA = "Hello World"; + const string inputB = " "; + const string inputC = ""; + const string? inputD = null; + const string alternative = "ALTERNATIVE"; + + string resultA = inputA.WithEmptyAlternative(alternative); + string resultB = inputB.WithEmptyAlternative(alternative); + string resultC = inputC.WithEmptyAlternative(alternative); + string resultD = inputD.WithEmptyAlternative(alternative); + + Assert.AreEqual(resultA, inputA); + Assert.AreEqual(resultB, inputB); + Assert.AreEqual(resultC, alternative); + Assert.AreEqual(resultD, alternative); + Assert.AreEqual(alternative, ((string?)null).WithEmptyAlternative(alternative)); + } + + [TestMethod] + public void WithWhiteSpaceAlternative_ShouldBeCorrect() + { + const string input = " "; + const string alternative = "ALTERNATIVE"; + + string result = input.WithWhiteSpaceAlternative(alternative); + + Assert.AreEqual(result, alternative); + Assert.AreEqual(alternative, ((string?)null).WithWhiteSpaceAlternative(alternative)); + } + + private struct SampleStructure + { + [JsonPropertyName("values")] + public int[] Values { get; set; } + } +} diff --git a/X10D.Tests/src/Time/ByteTests.cs b/X10D.Tests/src/Time/ByteTests.cs new file mode 100644 index 0000000..705a149 --- /dev/null +++ b/X10D.Tests/src/Time/ByteTests.cs @@ -0,0 +1,73 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((byte)0).FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((byte)0).FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(((byte)100).IsLeapYear()); + Assert.IsFalse(((byte)200).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(((byte)1).IsLeapYear()); + Assert.IsFalse(((byte)101).IsLeapYear()); + Assert.IsFalse(((byte)201).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4() + { + Assert.IsTrue(((byte)4).IsLeapYear()); + Assert.IsTrue(((byte)104).IsLeapYear()); + Assert.IsTrue(((byte)204).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => ((byte)0).IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(((byte)1).Ticks() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Seconds() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Minutes() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Days() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Hours() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Ticks()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Seconds()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Minutes()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Days()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Hours()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Weeks()); + } +} diff --git a/X10D.Tests/src/Time/DateTimeOffsetTests.cs b/X10D.Tests/src/Time/DateTimeOffsetTests.cs new file mode 100644 index 0000000..4deba4b --- /dev/null +++ b/X10D.Tests/src/Time/DateTimeOffsetTests.cs @@ -0,0 +1,147 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class DateTimeOffsetTests +{ + [TestMethod] + public void Age_ShouldBeDifference_Given1Jan2000() + { + DateTimeOffset birthday = new DateTime(2000, 1, 1); + DateTimeOffset today = DateTime.Now.Date; + + Assert.AreEqual(today.Year - birthday.Year, birthday.Age()); + } + + [TestMethod] + public void First_ShouldBeSaturday_Given1Jan2000() + { + DateTimeOffset date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 1), date.First(DayOfWeek.Saturday)); + Assert.AreEqual(new DateTime(2000, 1, 2), date.First(DayOfWeek.Sunday)); + Assert.AreEqual(new DateTime(2000, 1, 3), date.First(DayOfWeek.Monday)); + Assert.AreEqual(new DateTime(2000, 1, 4), date.First(DayOfWeek.Tuesday)); + Assert.AreEqual(new DateTime(2000, 1, 5), date.First(DayOfWeek.Wednesday)); + Assert.AreEqual(new DateTime(2000, 1, 6), date.First(DayOfWeek.Thursday)); + Assert.AreEqual(new DateTime(2000, 1, 7), date.First(DayOfWeek.Friday)); + } + + [TestMethod] + public void FirstDayOfMonth_ShouldBe1st_GivenToday() + { + DateTimeOffset today = DateTime.Now.Date; + + Assert.AreEqual(new DateTime(today.Year, today.Month, 1), today.FirstDayOfMonth()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given1999() + { + DateTimeOffset date = new DateTime(1999, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_Given2000() + { + DateTimeOffset date = new DateTime(2000, 1, 1); + Assert.IsTrue(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given2001() + { + DateTimeOffset date = new DateTime(2001, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given2100() + { + DateTimeOffset date = new DateTime(2100, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void LastSaturday_ShouldBe29th_Given1Jan2000() + { + DateTimeOffset date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 29), date.Last(DayOfWeek.Saturday)); + Assert.AreEqual(new DateTime(2000, 1, 30), date.Last(DayOfWeek.Sunday)); + Assert.AreEqual(new DateTime(2000, 1, 31), date.Last(DayOfWeek.Monday)); + Assert.AreEqual(new DateTime(2000, 1, 25), date.Last(DayOfWeek.Tuesday)); + Assert.AreEqual(new DateTime(2000, 1, 26), date.Last(DayOfWeek.Wednesday)); + Assert.AreEqual(new DateTime(2000, 1, 27), date.Last(DayOfWeek.Thursday)); + Assert.AreEqual(new DateTime(2000, 1, 28), date.Last(DayOfWeek.Friday)); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe28th_GivenFebruary1999() + { + DateTimeOffset february = new DateTime(1999, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 28), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe29th_GivenFebruary2000() + { + DateTimeOffset february = new DateTime(2000, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 29), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe28th_GivenFebruary2001() + { + DateTimeOffset february = new DateTime(2001, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 28), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe30th_GivenAprilJuneSeptemberNovember() + { + DateTimeOffset april = new DateTime(2000, 4, 1); + DateTimeOffset june = new DateTime(2000, 6, 1); + DateTimeOffset september = new DateTime(2000, 9, 1); + DateTimeOffset november = new DateTime(2000, 11, 1); + + Assert.AreEqual(new DateTime(april.Year, april.Month, 30), april.LastDayOfMonth()); + Assert.AreEqual(new DateTime(june.Year, june.Month, 30), june.LastDayOfMonth()); + Assert.AreEqual(new DateTime(september.Year, september.Month, 30), september.LastDayOfMonth()); + Assert.AreEqual(new DateTime(november.Year, november.Month, 30), november.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe31st_GivenJanuaryMarchMayJulyAugustOctoberDecember() + { + DateTimeOffset january = new DateTime(2000, 1, 1); + DateTimeOffset march = new DateTime(2000, 3, 1); + DateTimeOffset may = new DateTime(2000, 5, 1); + DateTimeOffset july = new DateTime(2000, 7, 1); + DateTimeOffset august = new DateTime(2000, 8, 1); + DateTimeOffset october = new DateTime(2000, 10, 1); + DateTimeOffset december = new DateTime(2000, 12, 1); + + Assert.AreEqual(new DateTime(january.Year, january.Month, 31), january.LastDayOfMonth()); + Assert.AreEqual(new DateTime(march.Year, march.Month, 31), march.LastDayOfMonth()); + Assert.AreEqual(new DateTime(may.Year, may.Month, 31), may.LastDayOfMonth()); + Assert.AreEqual(new DateTime(july.Year, july.Month, 31), july.LastDayOfMonth()); + Assert.AreEqual(new DateTime(august.Year, august.Month, 31), august.LastDayOfMonth()); + Assert.AreEqual(new DateTime(october.Year, october.Month, 31), october.LastDayOfMonth()); + Assert.AreEqual(new DateTime(december.Year, december.Month, 31), december.LastDayOfMonth()); + } + + [TestMethod] + public void NextSaturday_ShouldBe8th_Given1Jan2000() + { + DateTimeOffset date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 8), date.Next(DayOfWeek.Saturday)); + } +} diff --git a/X10D.Tests/src/Time/DateTimeTests.cs b/X10D.Tests/src/Time/DateTimeTests.cs new file mode 100644 index 0000000..b6c3174 --- /dev/null +++ b/X10D.Tests/src/Time/DateTimeTests.cs @@ -0,0 +1,163 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class DateTimeTests +{ + [TestMethod] + public void Age_ShouldBeDifference_Given1Jan2000() + { + var birthday = new DateTime(2000, 1, 1); + DateTime today = DateTime.Now.Date; + + Assert.AreEqual(today.Year - birthday.Year, birthday.Age()); + } + + [TestMethod] + public void First_ShouldBeSaturday_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 1), date.First(DayOfWeek.Saturday)); + Assert.AreEqual(new DateTime(2000, 1, 2), date.First(DayOfWeek.Sunday)); + Assert.AreEqual(new DateTime(2000, 1, 3), date.First(DayOfWeek.Monday)); + Assert.AreEqual(new DateTime(2000, 1, 4), date.First(DayOfWeek.Tuesday)); + Assert.AreEqual(new DateTime(2000, 1, 5), date.First(DayOfWeek.Wednesday)); + Assert.AreEqual(new DateTime(2000, 1, 6), date.First(DayOfWeek.Thursday)); + Assert.AreEqual(new DateTime(2000, 1, 7), date.First(DayOfWeek.Friday)); + } + + [TestMethod] + public void FirstDayOfMonth_ShouldBe1st_GivenToday() + { + DateTime today = DateTime.Now.Date; + + Assert.AreEqual(new DateTime(today.Year, today.Month, 1), today.FirstDayOfMonth()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given1999() + { + var date = new DateTime(1999, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_Given2000() + { + var date = new DateTime(2000, 1, 1); + Assert.IsTrue(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given2001() + { + var date = new DateTime(2001, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given2100() + { + var date = new DateTime(2100, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void LastSaturday_ShouldBe29th_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 29), date.Last(DayOfWeek.Saturday)); + Assert.AreEqual(new DateTime(2000, 1, 30), date.Last(DayOfWeek.Sunday)); + Assert.AreEqual(new DateTime(2000, 1, 31), date.Last(DayOfWeek.Monday)); + Assert.AreEqual(new DateTime(2000, 1, 25), date.Last(DayOfWeek.Tuesday)); + Assert.AreEqual(new DateTime(2000, 1, 26), date.Last(DayOfWeek.Wednesday)); + Assert.AreEqual(new DateTime(2000, 1, 27), date.Last(DayOfWeek.Thursday)); + Assert.AreEqual(new DateTime(2000, 1, 28), date.Last(DayOfWeek.Friday)); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe28th_GivenFebruary1999() + { + var february = new DateTime(1999, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 28), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe29th_GivenFebruary2000() + { + var february = new DateTime(2000, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 29), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe28th_GivenFebruary2001() + { + var february = new DateTime(2001, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 28), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe30th_GivenAprilJuneSeptemberNovember() + { + var april = new DateTime(2000, 4, 1); + var june = new DateTime(2000, 6, 1); + var september = new DateTime(2000, 9, 1); + var november = new DateTime(2000, 11, 1); + + Assert.AreEqual(new DateTime(april.Year, april.Month, 30), april.LastDayOfMonth()); + Assert.AreEqual(new DateTime(june.Year, june.Month, 30), june.LastDayOfMonth()); + Assert.AreEqual(new DateTime(september.Year, september.Month, 30), september.LastDayOfMonth()); + Assert.AreEqual(new DateTime(november.Year, november.Month, 30), november.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe31st_GivenJanuaryMarchMayJulyAugustOctoberDecember() + { + var january = new DateTime(2000, 1, 1); + var march = new DateTime(2000, 3, 1); + var may = new DateTime(2000, 5, 1); + var july = new DateTime(2000, 7, 1); + var august = new DateTime(2000, 8, 1); + var october = new DateTime(2000, 10, 1); + var december = new DateTime(2000, 12, 1); + + Assert.AreEqual(new DateTime(january.Year, january.Month, 31), january.LastDayOfMonth()); + Assert.AreEqual(new DateTime(march.Year, march.Month, 31), march.LastDayOfMonth()); + Assert.AreEqual(new DateTime(may.Year, may.Month, 31), may.LastDayOfMonth()); + Assert.AreEqual(new DateTime(july.Year, july.Month, 31), july.LastDayOfMonth()); + Assert.AreEqual(new DateTime(august.Year, august.Month, 31), august.LastDayOfMonth()); + Assert.AreEqual(new DateTime(october.Year, october.Month, 31), october.LastDayOfMonth()); + Assert.AreEqual(new DateTime(december.Year, december.Month, 31), december.LastDayOfMonth()); + } + + [TestMethod] + public void NextSaturday_ShouldBe8th_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 8), date.Next(DayOfWeek.Saturday)); + } + + [TestMethod] + public void ToUnixTimeMilliseconds_ShouldBe946684800000_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(946684800000, date.ToUnixTimeMilliseconds()); + } + + [TestMethod] + public void ToUnixTimeSeconds_ShouldBe946684800_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(946684800, date.ToUnixTimeSeconds()); + } +} diff --git a/X10D.Tests/src/Time/DecimalTests.cs b/X10D.Tests/src/Time/DecimalTests.cs new file mode 100644 index 0000000..cd8cf8b --- /dev/null +++ b/X10D.Tests/src/Time/DecimalTests.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class DecimalTests +{ + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0m.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0m.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0m.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0m.Days()); + Assert.AreEqual(TimeSpan.Zero, 0m.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0m.Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1m.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1m.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1m.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1m.Days() > TimeSpan.Zero); + Assert.IsTrue(1m.Hours() > TimeSpan.Zero); + Assert.IsTrue(1m.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1m).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1m).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1m).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1m).Days() < TimeSpan.Zero); + Assert.IsTrue((-1m).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1m).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/DoubleTests.cs b/X10D.Tests/src/Time/DoubleTests.cs new file mode 100644 index 0000000..17c7f2e --- /dev/null +++ b/X10D.Tests/src/Time/DoubleTests.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class DoubleTests +{ + private Half _negativeOne; + private Half _one; + private Half _zero; + + [TestInitialize] + public void Initialize() + { + _negativeOne = (Half)(-1); + _one = (Half)1; + _zero = (Half)0; + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, _zero.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, _zero.Seconds()); + Assert.AreEqual(TimeSpan.Zero, _zero.Minutes()); + Assert.AreEqual(TimeSpan.Zero, _zero.Days()); + Assert.AreEqual(TimeSpan.Zero, _zero.Hours()); + Assert.AreEqual(TimeSpan.Zero, _zero.Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(_one.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(_one.Seconds() > TimeSpan.Zero); + Assert.IsTrue(_one.Minutes() > TimeSpan.Zero); + Assert.IsTrue(_one.Days() > TimeSpan.Zero); + Assert.IsTrue(_one.Hours() > TimeSpan.Zero); + Assert.IsTrue(_one.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((_negativeOne).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Seconds() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Minutes() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Days() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Hours() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/HalfTests.cs b/X10D.Tests/src/Time/HalfTests.cs new file mode 100644 index 0000000..a70aed2 --- /dev/null +++ b/X10D.Tests/src/Time/HalfTests.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class HalfTests +{ + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0.0.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Days()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1.0.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1.0.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1.0.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1.0.Days() > TimeSpan.Zero); + Assert.IsTrue(1.0.Hours() > TimeSpan.Zero); + Assert.IsTrue(1.0.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1.0).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Days() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/Int16Tests.cs b/X10D.Tests/src/Time/Int16Tests.cs new file mode 100644 index 0000000..f2434fe --- /dev/null +++ b/X10D.Tests/src/Time/Int16Tests.cs @@ -0,0 +1,90 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((short)0).FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((short)0).FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(((short)100).IsLeapYear()); + Assert.IsFalse(((short)-100).IsLeapYear()); + Assert.IsFalse(((short)1900).IsLeapYear()); + Assert.IsFalse(((short)2100).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(((short)1).IsLeapYear()); + Assert.IsFalse(((short)101).IsLeapYear()); + Assert.IsFalse(((short)-101).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue(((short)-401).IsLeapYear()); + Assert.IsTrue(((short)-105).IsLeapYear()); + Assert.IsTrue(((short)4).IsLeapYear()); + Assert.IsTrue(((short)104).IsLeapYear()); + Assert.IsTrue(((short)400).IsLeapYear()); + Assert.IsTrue(((short)2000).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => ((short)0).IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue(((short)-1).Ticks() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Seconds() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Minutes() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Days() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Hours() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Weeks() < TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(((short)1).Ticks() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Seconds() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Minutes() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Days() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Hours() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, ((short)0).Ticks()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Seconds()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Minutes()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Days()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Hours()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Weeks()); + } +} diff --git a/X10D.Tests/src/Time/Int32Tests.cs b/X10D.Tests/src/Time/Int32Tests.cs new file mode 100644 index 0000000..7d6670b --- /dev/null +++ b/X10D.Tests/src/Time/Int32Tests.cs @@ -0,0 +1,90 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0.FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0.FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(100.IsLeapYear()); + Assert.IsFalse((-100).IsLeapYear()); + Assert.IsFalse(1900.IsLeapYear()); + Assert.IsFalse(2100.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(1.IsLeapYear()); + Assert.IsFalse(101.IsLeapYear()); + Assert.IsFalse((-101).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue((-401).IsLeapYear()); + Assert.IsTrue((-105).IsLeapYear()); + Assert.IsTrue(4.IsLeapYear()); + Assert.IsTrue(104.IsLeapYear()); + Assert.IsTrue(400.IsLeapYear()); + Assert.IsTrue(2000.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => 0.IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1).Ticks() < TimeSpan.Zero); + Assert.IsTrue((-1).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1).Days() < TimeSpan.Zero); + Assert.IsTrue((-1).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1).Weeks() < TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1.Ticks() > TimeSpan.Zero); + Assert.IsTrue(1.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1.Days() > TimeSpan.Zero); + Assert.IsTrue(1.Hours() > TimeSpan.Zero); + Assert.IsTrue(1.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0.Ticks()); + Assert.AreEqual(TimeSpan.Zero, 0.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0.Days()); + Assert.AreEqual(TimeSpan.Zero, 0.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0.Weeks()); + } +} diff --git a/X10D.Tests/src/Time/Int64Tests.cs b/X10D.Tests/src/Time/Int64Tests.cs new file mode 100644 index 0000000..f0459eb --- /dev/null +++ b/X10D.Tests/src/Time/Int64Tests.cs @@ -0,0 +1,90 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0L.FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0L.FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(100L.IsLeapYear()); + Assert.IsFalse((-100L).IsLeapYear()); + Assert.IsFalse(1900L.IsLeapYear()); + Assert.IsFalse(2100L.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(1L.IsLeapYear()); + Assert.IsFalse(101L.IsLeapYear()); + Assert.IsFalse((-101L).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue((-401L).IsLeapYear()); + Assert.IsTrue((-105L).IsLeapYear()); + Assert.IsTrue(4L.IsLeapYear()); + Assert.IsTrue(104L.IsLeapYear()); + Assert.IsTrue(400L.IsLeapYear()); + Assert.IsTrue(2000L.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => 0L.IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1L).Ticks() < TimeSpan.Zero); + Assert.IsTrue((-1L).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1L).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1L).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1L).Days() < TimeSpan.Zero); + Assert.IsTrue((-1L).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1L).Weeks() < TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1L.Ticks() > TimeSpan.Zero); + Assert.IsTrue(1L.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1L.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1L.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1L.Days() > TimeSpan.Zero); + Assert.IsTrue(1L.Hours() > TimeSpan.Zero); + Assert.IsTrue(1L.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0L.Ticks()); + Assert.AreEqual(TimeSpan.Zero, 0L.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0L.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0L.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0L.Days()); + Assert.AreEqual(TimeSpan.Zero, 0L.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0L.Weeks()); + } +} diff --git a/X10D.Tests/src/Time/SByteTests.cs b/X10D.Tests/src/Time/SByteTests.cs new file mode 100644 index 0000000..8189c6f --- /dev/null +++ b/X10D.Tests/src/Time/SByteTests.cs @@ -0,0 +1,86 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((sbyte)0).FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((sbyte)0).FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(((sbyte)100).IsLeapYear()); + Assert.IsFalse(((sbyte)-100).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(((sbyte)1).IsLeapYear()); + Assert.IsFalse(((sbyte)101).IsLeapYear()); + Assert.IsFalse(((sbyte)-101).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4() + { + Assert.IsTrue(((sbyte)4).IsLeapYear()); + Assert.IsTrue(((sbyte)104).IsLeapYear()); + Assert.IsTrue(((sbyte)-105).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => ((sbyte)0).IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Ticks()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Seconds()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Minutes()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Days()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Hours()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(((sbyte)1).Ticks() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Seconds() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Minutes() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Days() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Hours() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue(((sbyte)-1).Ticks() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Seconds() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Minutes() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Days() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Hours() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/SingleTests.cs b/X10D.Tests/src/Time/SingleTests.cs new file mode 100644 index 0000000..6feb82f --- /dev/null +++ b/X10D.Tests/src/Time/SingleTests.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class SingleTests +{ + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0f.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0f.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0f.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0f.Days()); + Assert.AreEqual(TimeSpan.Zero, 0f.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0f.Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1f.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1f.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1f.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1f.Days() > TimeSpan.Zero); + Assert.IsTrue(1f.Hours() > TimeSpan.Zero); + Assert.IsTrue(1f.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1f).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1f).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1f).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1f).Days() < TimeSpan.Zero); + Assert.IsTrue((-1f).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1f).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/StringTests.cs b/X10D.Tests/src/Time/StringTests.cs new file mode 100644 index 0000000..9b5e68c --- /dev/null +++ b/X10D.Tests/src/Time/StringTests.cs @@ -0,0 +1,38 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class StringTests +{ + [TestMethod] + public void ToTimeSpan_ShouldReturnCorrectTimeSpan_GivenString() + { + const string value = "1y 1mo 1w 1d 1h 1m 1s 1ms"; + + TimeSpan expected = TimeSpan.FromMilliseconds(1); + expected += TimeSpan.FromSeconds(1); + expected += TimeSpan.FromMinutes(1); + expected += TimeSpan.FromHours(1); + expected += TimeSpan.FromDays(1); + expected += TimeSpan.FromDays(7); + expected += TimeSpan.FromDays(30); + expected += TimeSpan.FromDays(365); + + Assert.AreEqual(expected, value.ToTimeSpan()); + } + + [TestMethod] + public void ToTimeSpan_ShouldReturnZero_GivenInvalidString() + { + Assert.AreEqual(TimeSpan.Zero, "Hello World".ToTimeSpan()); + } + + [TestMethod] + public void ToTimeSpan_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => value!.ToTimeSpan()); + } +} diff --git a/X10D.Tests/src/Time/TimeSpanParserTests.cs b/X10D.Tests/src/Time/TimeSpanParserTests.cs new file mode 100644 index 0000000..b671070 --- /dev/null +++ b/X10D.Tests/src/Time/TimeSpanParserTests.cs @@ -0,0 +1,15 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class TimeSpanParserTests +{ + [TestMethod] + public void TryParse_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => TimeSpanParser.TryParse(value!, out _)); + } +} diff --git a/X10D.Tests/src/Time/TimeSpanTests.cs b/X10D.Tests/src/Time/TimeSpanTests.cs new file mode 100644 index 0000000..cc34618 --- /dev/null +++ b/X10D.Tests/src/Time/TimeSpanTests.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class TimeSpanTests +{ + private TimeSpan _timeSpan; + + [TestInitialize] + public void Initialize() + { + _timeSpan = new TimeSpan(1, 2, 3, 4, 5); + } + + [TestMethod] + public void Ago_ShouldBeInPast_GivenNow() + { + Assert.IsTrue(_timeSpan.Ago() < DateTime.Now); + } + + [TestMethod] + public void FromNow_ShouldBeInFuture_GivenNow() + { + Assert.IsTrue(_timeSpan.FromNow() > DateTime.Now); + } + + [TestMethod] + public void Ago_ShouldBeYesterday_GivenYesterday() + { + DateTime yesterday = DateTime.Now.AddDays(-1); + Assert.AreEqual(yesterday.Date, 1.Days().Ago().Date); + } + + [TestMethod] + public void FromNow_ShouldBeTomorrow_GivenTomorrow() + { + DateTime tomorrow = DateTime.Now.AddDays(1); + Assert.AreEqual(tomorrow.Date, 1.Days().FromNow().Date); + } +} diff --git a/X10D.Tests/src/Time/UInt16Tests.cs b/X10D.Tests/src/Time/UInt16Tests.cs new file mode 100644 index 0000000..e63d596 --- /dev/null +++ b/X10D.Tests/src/Time/UInt16Tests.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((ushort)0).FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((ushort)0).FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(((ushort)100).IsLeapYear()); + Assert.IsFalse(((ushort)1900).IsLeapYear()); + Assert.IsFalse(((ushort)2100).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(((ushort)1).IsLeapYear()); + Assert.IsFalse(((ushort)101).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue(((ushort)4).IsLeapYear()); + Assert.IsTrue(((ushort)104).IsLeapYear()); + Assert.IsTrue(((ushort)400).IsLeapYear()); + Assert.IsTrue(((ushort)2000).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => ((ushort)0).IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(((ushort)1).Ticks() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Seconds() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Minutes() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Days() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Hours() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Ticks()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Seconds()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Minutes()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Days()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Hours()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Weeks()); + } +} diff --git a/X10D.Tests/src/Time/UInt32Tests.cs b/X10D.Tests/src/Time/UInt32Tests.cs new file mode 100644 index 0000000..86eef87 --- /dev/null +++ b/X10D.Tests/src/Time/UInt32Tests.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0U.FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0U.FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(100U.IsLeapYear()); + Assert.IsFalse(1900U.IsLeapYear()); + Assert.IsFalse(2100U.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(1U.IsLeapYear()); + Assert.IsFalse(101U.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue(4U.IsLeapYear()); + Assert.IsTrue(104U.IsLeapYear()); + Assert.IsTrue(400U.IsLeapYear()); + Assert.IsTrue(2000U.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => 0U.IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1U.Ticks() > TimeSpan.Zero); + Assert.IsTrue(1U.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1U.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1U.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1U.Days() > TimeSpan.Zero); + Assert.IsTrue(1U.Hours() > TimeSpan.Zero); + Assert.IsTrue(1U.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0U.Ticks()); + Assert.AreEqual(TimeSpan.Zero, 0U.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0U.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0U.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0U.Days()); + Assert.AreEqual(TimeSpan.Zero, 0U.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0U.Weeks()); + } +} diff --git a/X10D.Tests/src/Time/UInt64Tests.cs b/X10D.Tests/src/Time/UInt64Tests.cs new file mode 100644 index 0000000..9720f3a --- /dev/null +++ b/X10D.Tests/src/Time/UInt64Tests.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0UL.FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0UL.FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(100UL.IsLeapYear()); + Assert.IsFalse(1900UL.IsLeapYear()); + Assert.IsFalse(2100UL.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(1UL.IsLeapYear()); + Assert.IsFalse(101UL.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue(4UL.IsLeapYear()); + Assert.IsTrue(104UL.IsLeapYear()); + Assert.IsTrue(400UL.IsLeapYear()); + Assert.IsTrue(2000UL.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => 0UL.IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1UL.Ticks() > TimeSpan.Zero); + Assert.IsTrue(1UL.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1UL.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1UL.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1UL.Days() > TimeSpan.Zero); + Assert.IsTrue(1UL.Hours() > TimeSpan.Zero); + Assert.IsTrue(1UL.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0UL.Ticks()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Days()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Weeks()); + } +} diff --git a/X10D.Tests/src/Unity/Vector3Tests.cs b/X10D.Tests/src/Unity/Vector3Tests.cs deleted file mode 100644 index 2e02cf0..0000000 --- a/X10D.Tests/src/Unity/Vector3Tests.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace X10D.Tests.Unity -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - using UnityEngine; - using X10D.Unity; - - /// - /// Tests for . - /// - [TestClass] - public class Vector3Tests - { - /// - /// Tests for by rounding to the nearest 0.5. - /// - [TestMethod] - public void TestRoundHalf() - { - var vector = new Vector3(1.8f, 2.1f, 3.37f); - Assert.AreEqual(new Vector3(2, 2, 3.5f), vector.Round(0.5f)); - } - - /// - /// Tests for by rounding to the nearest integer. - /// - [TestMethod] - public void TestRoundInteger() - { - var vector = new Vector3(1.8f, 2.1f, 3.37f); - Assert.AreEqual(new Vector3(2, 2, 3), vector.Round()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithX() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(4, 2, 3), vector.WithX(4)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithXY() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(8, 10, 3), vector.WithXY(8, 10)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithXZ() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(8, 2, 10), vector.WithXZ(8, 10)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithY() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(1, 4, 3), vector.WithY(4)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithYZ() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(1, 8, 10), vector.WithYZ(8, 10)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithZ() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(1, 2, 4), vector.WithZ(4)); - } - } -} diff --git a/X10D.Unity/X10D.Unity.csproj b/X10D.Unity/X10D.Unity.csproj deleted file mode 100644 index 8b4ce74..0000000 --- a/X10D.Unity/X10D.Unity.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - - netstandard2.0 - 8.0 - Oliver Booth - en - true - https://github.com/oliverbooth/X10D - git - Extension methods on crack. - LICENSE.md - icon.png - - dotnet extension-methods unity - 2.6.0 - ..\X10D.ruleset - true - 2.6.0 - 2.6.0 - 2.6.0 - - - - - True - - - - True - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - - - - - - - - - - True - True - Resource.resx - - - - - - ResXFileCodeGenerator - Resource.Designer.cs - - - - \ No newline at end of file diff --git a/X10D.Unity/src/BetterBehavior.cs b/X10D.Unity/src/BetterBehavior.cs deleted file mode 100644 index 6069676..0000000 --- a/X10D.Unity/src/BetterBehavior.cs +++ /dev/null @@ -1,107 +0,0 @@ -namespace X10D.Unity -{ - using System.Diagnostics.CodeAnalysis; - using System.Threading.Tasks; - using UnityEngine; - - /// - /// Represents a class which inherits to offer wider functionality. - /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "Unity property")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Unity property")] - [SuppressMessage("ReSharper", "SA1300", Justification = "Unity API-compliant property")] - [SuppressMessage("ReSharper", "CA2007", Justification = "Unnecessary")] - [SuppressMessage("ReSharper", "RCS1090", Justification = "Unnecessary")] - [SuppressMessage("ReSharper", "RCS1213", Justification = "Unity method")] - [SuppressMessage("ReSharper", "UnusedParameter.Global", Justification = "Override method")] - public abstract class BetterBehavior : MonoBehaviour - { - /// - /// Gets the component attached to this object. - /// - public new Transform transform { get; private set; } - - /// - /// Awake is called when the script instance is being loaded. - /// - protected virtual void Awake() - { - this.transform = this.GetComponent(); - } - - /// - /// Frame-rate independent messaging for physics calculations. - /// - /// A snapshot of timing values. - protected virtual void OnFixedUpdate(in GameTime gameTime) - { - } - - /// - /// Frame-rate independent messaging for physics calculations. - /// - /// A snapshot of timing values. - /// Returns a task representing the result of the operation. - protected virtual Task OnFixedUpdateAsync(GameTime gameTime) - { - return Task.CompletedTask; - } - - /// - /// Called once per frame, after all Update calls. - /// - /// A snapshot of timing values. - protected virtual void OnLateUpdate(in GameTime gameTime) - { - } - - /// - /// Called once per frame, after all Update calls. - /// - /// A snapshot of timing values. - /// Returns a task representing the result of the operation. - protected virtual Task OnLateUpdateAsync(GameTime gameTime) - { - return Task.CompletedTask; - } - - /// - /// Called once per frame. - /// - /// A snapshot of timing values. - protected virtual void OnUpdate(in GameTime gameTime) - { - } - - /// - /// Called once per frame. - /// - /// A snapshot of timing values. - /// Returns a task representing the result of the operation. - protected virtual Task OnUpdateAsync(GameTime gameTime) - { - return Task.CompletedTask; - } - - private async void FixedUpdate() - { - var time = GameTime.CreateFromCurrentTimes(); - this.OnFixedUpdate(time); - await this.OnFixedUpdateAsync(time); - } - - private async void LateUpdate() - { - var time = GameTime.CreateFromCurrentTimes(); - this.OnLateUpdate(time); - await this.OnLateUpdateAsync(time); - } - - private async void Update() - { - var time = GameTime.CreateFromCurrentTimes(); - this.OnUpdate(time); - await this.OnUpdateAsync(time); - } - } -} diff --git a/X10D.Unity/src/GameObjectExtensions.cs b/X10D.Unity/src/GameObjectExtensions.cs deleted file mode 100644 index 3341e87..0000000 --- a/X10D.Unity/src/GameObjectExtensions.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace X10D.Unity -{ - using System; - using JetBrains.Annotations; - using UnityEngine; - - /// - /// Extension methods for . - /// - public static class GameObjectExtensions - { - /// - /// Rotates the component on the current such that is is facing another - /// . - /// - /// The current game object. - /// The target. - /// - /// is null - /// - or - - /// is null. - /// - public static void LookAt([NotNull] this GameObject gameObject, [NotNull] GameObject other) - { - if (gameObject is null) - { - throw new ArgumentNullException(nameof(gameObject)); - } - - if (other is null) - { - throw new ArgumentNullException(nameof(other)); - } - - gameObject.LookAt(other.transform); - } - - /// - /// Rotates the component on the current such that is is facing another - /// . - /// - /// The current game object. - /// The target. - /// - /// is null - /// - or - - /// is null. - /// - public static void LookAt([NotNull] this GameObject gameObject, [NotNull] Transform other) - { - if (gameObject is null) - { - throw new ArgumentNullException(nameof(gameObject)); - } - - if (other is null) - { - throw new ArgumentNullException(nameof(other)); - } - - gameObject.transform.LookAt(other); - } - } -} diff --git a/X10D.Unity/src/GameTime.cs b/X10D.Unity/src/GameTime.cs deleted file mode 100644 index f59a6df..0000000 --- a/X10D.Unity/src/GameTime.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace X10D.Unity -{ - using System; - using UnityEngine; - - /// - /// Represents a struct which contains game timing information. - /// - public readonly struct GameTime - { - private GameTime(float totalTime, float deltaTime, float fixedDeltaTime, int frameCount, float timeScale) - { - this.TotalTime = TimeSpan.FromSeconds(totalTime); - this.DeltaTime = TimeSpan.FromSeconds(deltaTime); - this.FixedDeltaTime = TimeSpan.FromSeconds(fixedDeltaTime); - this.FrameCount = frameCount; - this.TimeScale = timeScale; - } - - /// - /// Gets the time since the last frame was rendered. - /// - public TimeSpan DeltaTime { get; } - - /// - /// Gets the time since the last physics time step. - /// - public TimeSpan FixedDeltaTime { get; } - - /// - /// Gets the total number of frames which have been rendered. - /// - public int FrameCount { get; } - - /// - /// Gets the total time for which the game has been running. - /// - public TimeSpan TotalTime { get; } - - /// - /// Gets the time scale. - /// - public float TimeScale { get; } - - /// - /// Creates a new instance of the struct by creating a snapshot of values offered by . - /// - /// An instance of . - public static GameTime CreateFromCurrentTimes() - { - return new GameTime(Time.time, Time.deltaTime, Time.fixedDeltaTime, Time.frameCount, Time.timeScale); - } - } -} diff --git a/X10D.Unity/src/TransformExtensions.cs b/X10D.Unity/src/TransformExtensions.cs deleted file mode 100644 index 6cdb7e3..0000000 --- a/X10D.Unity/src/TransformExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace X10D.Unity -{ - using System; - using JetBrains.Annotations; - using UnityEngine; - - /// - /// Extension methods for . - /// - public static class TransformExtensions - { - /// - /// Rotates the current such that is is facing another . - /// - /// The current transform. - /// The target. - /// - /// is null - /// - or - - /// is null. - /// - public static void LookAt([NotNull] this Transform transform, [NotNull] GameObject other) - { - if (transform is null) - { - throw new ArgumentNullException(nameof(transform)); - } - - if (other is null) - { - throw new ArgumentNullException(nameof(other)); - } - - transform.LookAt(other.transform); - } - } -} diff --git a/X10D.Unity/src/Vector3Extensions.cs b/X10D.Unity/src/Vector3Extensions.cs deleted file mode 100644 index 3f9b1eb..0000000 --- a/X10D.Unity/src/Vector3Extensions.cs +++ /dev/null @@ -1,111 +0,0 @@ -namespace X10D.Unity -{ - using UnityEngine; - - /// - /// Extension methods for . - /// - public static class Vector3Extensions - { - /// - /// Rounds a by calling on each of the components. - /// - /// The vector to round. - /// The nearest value. - /// rounded to the nearest . - public static Vector3 Round(this Vector3 vector, float nearest = 1) - { - return new Vector3(vector.x.Round(nearest), vector.y.Round(nearest), vector.z.Round(nearest)); - } - - /// - /// Returns a vector whose Y and Z components match that of a provided vector, and sets the X component to a provided value. - /// - /// The input vector. - /// The new X value. - /// - /// Returns a whose Y and Z components match that of , - /// but with the component set to . - /// - public static Vector3 WithX(this Vector3 vector, float x) - { - return new Vector3(x, vector.y, vector.z); - } - - /// - /// Returns a vector whose Z component matches that of a provided vector, and sets the X and Y components to provided values. - /// - /// The input vector. - /// The new X value. - /// The new Y value. - /// - /// Returns a whose Z component matches that of , - /// but with the and components set to and - /// respectively. - /// - public static Vector3 WithXY(this Vector3 vector, float x, float y) - { - return new Vector3(x, y, vector.z); - } - - /// - /// Returns a vector whose Y component matches that of a provided vector, and sets the X and Z components to provided values. - /// - /// The input vector. - /// The new X value. - /// The new Z value. - /// - /// Returns a whose Y component matches that of , - /// but with the and components set to and - /// respectively. - /// - public static Vector3 WithXZ(this Vector3 vector, float x, float z) - { - return new Vector3(x, vector.y, z); - } - - /// - /// Returns a vector whose X and Z components match that of a provided vector, and sets the Y component to a provided value. - /// - /// The input vector. - /// The new Y value. - /// - /// Returns a whose X and Z components match that of , - /// but with the component set to . - /// - public static Vector3 WithY(this Vector3 vector, float y) - { - return new Vector3(vector.x, y, vector.z); - } - - /// - /// Returns a vector whose X component matches that of a provided vector, and sets the Y and Z components to provided values. - /// - /// The input vector. - /// The new Y value. - /// The new Z value. - /// - /// Returns a whose X component matches that of , - /// but with the and components set to and - /// respectively. - /// - public static Vector3 WithYZ(this Vector3 vector, float y, float z) - { - return new Vector3(vector.x, y, z); - } - - /// - /// Returns a vector whose X and Y components match that of a provided vector, and sets the Z component to a provided value. - /// - /// The input vector. - /// The new Z value. - /// - /// Returns a whose X and Y components match that of , - /// but with the component set to . - /// - public static Vector3 WithZ(this Vector3 vector, float z) - { - return new Vector3(vector.x, vector.y, z); - } - } -} diff --git a/X10D.sln b/X10D.sln index d20ed46..067e80e 100644 --- a/X10D.sln +++ b/X10D.sln @@ -10,9 +10,15 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FF6E59AB-1A23-4981-834C-47BEB5A46DC1}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .gitignore = .gitignore + CHANGELOG.md = CHANGELOG.md + CONTRIBUTING.md = CONTRIBUTING.md + LICENSE.md = LICENSE.md + README.md = README.md + icon.png = icon.png EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.Unity", "X10D.Unity\X10D.Unity.csproj", "{C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.SourceValidator", "X10D.SourceValidator\X10D.SourceValidator.csproj", "{84750149-9068-4780-AFDE-CDA1AC57007D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -28,10 +34,10 @@ Global {DF228EA2-D8EC-4A40-8917-E1E62E3B7D8E}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF228EA2-D8EC-4A40-8917-E1E62E3B7D8E}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF228EA2-D8EC-4A40-8917-E1E62E3B7D8E}.Release|Any CPU.Build.0 = Release|Any CPU - {C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}.Release|Any CPU.Build.0 = Release|Any CPU + {84750149-9068-4780-AFDE-CDA1AC57007D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84750149-9068-4780-AFDE-CDA1AC57007D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84750149-9068-4780-AFDE-CDA1AC57007D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84750149-9068-4780-AFDE-CDA1AC57007D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/X10D/README.md b/X10D/README.md deleted file mode 100644 index 3596d22..0000000 --- a/X10D/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# X10D - -## About -The X10D library contains extension methods for standard .NET library types. - -## Extended Classes -Below is a list of the number of extension methods written for a given type. Overloaded methods are not counted. - -| Type | Library | Method count | -| :--- | :--- | :--- | -| `bool` | `X10D` | 1 | -| `byte` / `byte[]` | `X10D` | 8 | -| `char` | `X10D` | 1 | -| `IComparable` | `X10D` | 1 | -| `IConvertible` | `X10D` | 4 | -| `DateTime` | `X10D` | 11 | -| `EndPoint` | `X10D` | 2 | -| `double` | `X10D` | 7 | -| `enum` | `X10D` | 2 | -| `float` | `X10D` | 7 | -| `short` / `ushort` | `X10D` | 11 | -| `int` / `uint` | `X10D` | 25 | -| `long` / `ulong` | `X10D` | 13 | -| `IList` | `X10D` | 2 | -| `Random` | `X10D` | 2 | -| `string` / `SecureString` | `X10D` | 8 | -| `Dictionary` | `X10D` | 2 | \ No newline at end of file diff --git a/X10D/X10D.csproj b/X10D/X10D.csproj index e83b965..565369e 100644 --- a/X10D/X10D.csproj +++ b/X10D/X10D.csproj @@ -1,51 +1,57 @@  - netstandard2.0 - 8.0 + net6.0 + 10.0 + true + true Oliver Booth en - true https://github.com/oliverbooth/X10D git Extension methods on crack. LICENSE.md icon.png - + dotnet extension-methods - 2.6.0 - ..\X10D.ruleset true - 2.6.0 - 2.6.0 - 2.6.0 + 3.0.0 enable + true + true + + + + $(VersionPrefix)-$(VersionSuffix) + $(VersionPrefix).0 + $(VersionPrefix).0 + + + + $(VersionPrefix)-$(VersionSuffix).$(BuildNumber) + $(VersionPrefix).$(BuildNumber) + $(VersionPrefix).$(BuildNumber) + + + + $(VersionPrefix) + $(VersionPrefix).0 + $(VersionPrefix).0 True - + True - + + + + True + - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -54,6 +60,11 @@ True Resource.resx + + True + True + ExceptionMessages.resx + @@ -61,12 +72,10 @@ ResXFileCodeGenerator Resource.Designer.cs - - - - - CHANGELOG.md - + + ResXFileCodeGenerator + ExceptionMessages.Designer.cs + \ No newline at end of file diff --git a/X10D/X10D.csproj.DotSettings b/X10D/X10D.csproj.DotSettings new file mode 100644 index 0000000..04bd7e2 --- /dev/null +++ b/X10D/X10D.csproj.DotSettings @@ -0,0 +1,27 @@ + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + diff --git a/X10D/src/Assembly.cs b/X10D/src/Assembly.cs index 8c11453..f547610 100644 --- a/X10D/src/Assembly.cs +++ b/X10D/src/Assembly.cs @@ -1,3 +1 @@ -using System; - -[assembly: CLSCompliant(true)] +[assembly: CLSCompliant(true)] diff --git a/X10D/src/BooleanExtensions.cs b/X10D/src/BooleanExtensions.cs deleted file mode 100644 index fbdab76..0000000 --- a/X10D/src/BooleanExtensions.cs +++ /dev/null @@ -1,145 +0,0 @@ -namespace X10D -{ - /// - /// Extension methods for . - /// - public static class BooleanExtensions - { - /// - /// Performs logical AND on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if AND - /// evaluate to , or otherwise. - /// - public static bool And(this bool value, bool comparison) - { - return value && comparison; - } - - /// - /// Performs logical NAND on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if NAND - /// evaluate to , or otherwise. - /// - public static bool NAnd(this bool value, bool comparison) - { - return !(value && comparison); - } - - /// - /// Performs logical NOR on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if NOR - /// evaluate to , or otherwise. - /// - public static bool NOr(this bool value, bool comparison) - { - return !(value || comparison); - } - - /// - /// Performs logical NOT on this . - /// - /// The boolean. - /// - /// Returns if is , - /// or otherwise. - /// - public static bool Not(this bool value) - { - return !value; - } - - /// - /// Performs logical OR on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if OR - /// evaluate to , or otherwise. - /// - public static bool Or(this bool value, bool comparison) - { - return value || comparison; - } - - /// - /// Gets the value of this boolean as represented by . - /// - /// The boolean. - /// Returns 1 if is , or 0 otherwise. - public static byte ToByte(this bool value) - { - return (byte)value.ToInt32(); - } - - /// - /// Gets the value of this boolean as represented by . - /// - /// The boolean. - /// Returns 1 if is , or 0 otherwise. - public static short ToInt16(this bool value) - { - return (short)value.ToInt32(); - } - - /// - /// Gets the value of this boolean as represented by . - /// - /// The boolean. - /// Returns 1 if is , or 0 otherwise. - public static int ToInt32(this bool value) - { - return value ? 1 : 0; - } - - /// - /// Gets the value of this boolean as represented by . - /// - /// The boolean. - /// Returns 1 if is , 0 otherwise. - public static long ToInt64(this bool value) - { - return value.ToInt32(); - } - - /// - /// Performs logical XNOR on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if XNOR - /// evaluate to , or otherwise. - /// - public static bool XNOr(this bool value, bool comparison) - { - return !(value ^ comparison); - } - - /// - /// Performs logical XOR on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if XOR - /// evaluate to , or otherwise. - /// - public static bool XOr(this bool value, bool comparison) - { - return value ^ comparison; - } - } -} diff --git a/X10D/src/ByteExtensions.cs b/X10D/src/ByteExtensions.cs deleted file mode 100644 index dd1f634..0000000 --- a/X10D/src/ByteExtensions.cs +++ /dev/null @@ -1,116 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - /// - /// Extension methods for . - /// - public static class ByteExtensions - { - /// - /// Gets a literally representing the raw values in the []. - /// - /// The bytes to get. - /// Returns a . - public static string AsString(this IEnumerable bytes) - { - return BitConverter.ToString(bytes.ToArray()); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - public static short GetInt16(this IEnumerable bytes) - { - return BitConverter.ToInt16(bytes.ToArray(), 0); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - public static int GetInt32(this IEnumerable bytes) - { - return BitConverter.ToInt32(bytes.ToArray(), 0); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - public static long GetInt64(this IEnumerable bytes) - { - return BitConverter.ToInt64(bytes.ToArray(), 0); - } - - /// - /// Gets a representing the value the [] with - /// encoding. - /// - /// The bytes to convert. - /// Returns a . - public static string GetString(this IEnumerable bytes) - { - return bytes.GetString(Encoding.UTF8); - } - - /// - /// Gets a representing the value the [] with the provided encoding. - /// - /// The bytes to convert. - /// The encoding to use. - /// Returns a . - /// is . - public static string GetString(this IEnumerable bytes, Encoding encoding) - { - if (encoding is null) - { - throw new ArgumentNullException(nameof(encoding)); - } - - // ReSharper disable once SuggestVarOrType_Elsewhere - var array = bytes.ToArray(); - return encoding.GetString(array, 0, array.Length); - } - - /// - /// Converts the [] to a . - /// - /// The bytes to convert. - /// Returns an . - [CLSCompliant(false)] - public static ushort GetUInt16(this IEnumerable bytes) - { - return BitConverter.ToUInt16(bytes.ToArray(), 0); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - [CLSCompliant(false)] - public static uint GetUInt32(this IEnumerable bytes) - { - return BitConverter.ToUInt32(bytes.ToArray(), 0); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - [CLSCompliant(false)] - public static ulong GetUInt64(this IEnumerable bytes) - { - return BitConverter.ToUInt64(bytes.ToArray(), 0); - } - } -} diff --git a/X10D/src/CharExtensions.cs b/X10D/src/CharExtensions.cs deleted file mode 100644 index cbdd49e..0000000 --- a/X10D/src/CharExtensions.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - /// - /// Extension methods for . - /// - public static class CharExtensions - { - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// Returns a containing characters. - public static string Random(this char[] chars, int length) - { - return chars.Random(length, RandomExtensions.Random); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// The instance. - /// Returns a containing characters. - /// is . - public static string Random(this char[] chars, int length, Random random) - { - if (chars is null) - { - throw new ArgumentNullException(nameof(chars)); - } - - if (random is null) - { - throw new ArgumentNullException(nameof(random)); - } - - var builder = new StringBuilder(length); - for (var i = 0; i < length; i++) - { - builder.Append(chars[random.Next(0, chars.Length)]); - } - - return builder.ToString(); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// Returns a containing characters. - public static string Random(this IEnumerable chars, int length) - { - return chars.Random(length, RandomExtensions.Random); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// The instance. - /// Returns a containing characters. - public static string Random(this IEnumerable chars, int length, Random random) - { - return chars.ToArray().Random(length, random); - } - - /// - /// Repeats a character a specified number of times. - /// - /// The character to repeat. - /// The repeat count. - /// - /// Returns a whose value is repeated - /// times. - /// - public static string Repeat(this char c, int count) - { - return new string(c, count); - } - } -} diff --git a/X10D/src/Collections/ArrayExtensions.cs b/X10D/src/Collections/ArrayExtensions.cs new file mode 100644 index 0000000..053d27d --- /dev/null +++ b/X10D/src/Collections/ArrayExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Extension methods for . +/// +public static class ArrayExtensions +{ + /// + /// Returns a read-only wrapper for the array. + /// + /// The one-dimensional, zero-based array to wrap in a read-only wrapper. + /// The type of the elements in the array. + /// A wrapper for the specified array. + /// is . + [Pure] + public static IReadOnlyCollection AsReadOnly(this T[] array) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + return Array.AsReadOnly(array); + } + + /// + /// Clears the contents of an array. + /// + /// The array to clear. + /// The type of the elements in the array. + /// is . + public static void Clear(this T?[] array) + { + array.Clear(..); + } + + /// + /// Sets a range of elements in an array to the default value of each element type. + /// + /// The array whose elements need to be cleared. + /// A range defining the start index and number of elements to clear. + /// The type of the elements in the array. + /// is . + public static void Clear(this T?[] array, Range range) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + int index = range.Start.Value; + int end = range.End.Value; + if (range.End.IsFromEnd) + { + end = array.Length - end; + } + + array.Clear(index, end - index); + } + + /// + /// Sets a range of elements in an array to the default value of each element type. + /// + /// The array whose elements need to be cleared. + /// The starting index of the range of elements to clear. + /// The number of elements to clear. + /// The type of the elements in the array. + /// is . + /// + /// is less than the lower bound of . + /// -or- + /// is less zero. + /// -or- + /// The sum of and is greater than the size of array. + /// + public static void Clear(this T?[] array, int index, int length) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (length == 0 || array.Length == 0) + { + return; + } + + Array.Clear(array, index, length); + } +} diff --git a/X10D/src/Collections/BoolListExtensions.cs b/X10D/src/Collections/BoolListExtensions.cs new file mode 100644 index 0000000..365778a --- /dev/null +++ b/X10D/src/Collections/BoolListExtensions.cs @@ -0,0 +1,130 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for of . +/// +public static class BoolListExtensions +{ + /// + /// Packs a collection of booleans into a . + /// + /// The collection of booleans to pack. + /// An 8-bit unsigned integer containing the packed booleans. + /// is . + /// contains more than 8 elements. + /// Alpha Anar + [Pure] + public static byte PackByte(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source.Count > 8) + { + throw new ArgumentException("Source cannot contain more than than 8 elements.", nameof(source)); + } + + byte result = 0; + + for (var i = 0; i < source.Count; i++) + { + result |= (byte)(source[i] ? 1 << i : 0); + } + + return result; + } + + /// + /// Packs a collection of booleans into a . + /// + /// The collection of booleans to pack. + /// A 16-bit signed integer containing the packed booleans. + /// is . + /// contains more than 16 elements. + [Pure] + public static short PackInt16(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source.Count > 16) + { + throw new ArgumentException("Source cannot contain more than than 16 elements.", nameof(source)); + } + + short result = 0; + + for (var i = 0; i < source.Count; i++) + { + result |= (short)(source[i] ? 1 << i : 0); + } + + return result; + } + + /// + /// Packs a collection of booleans into a . + /// + /// The collection of booleans to pack. + /// A 32-bit signed integer containing the packed booleans. + /// is . + /// contains more than 32 elements. + [Pure] + public static int PackInt32(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source.Count > 32) + { + throw new ArgumentException("Source cannot contain more than than 32 elements.", nameof(source)); + } + + var result = 0; + + for (var i = 0; i < source.Count; i++) + { + result |= source[i] ? 1 << i : 0; + } + + return result; + } + + /// + /// Packs a collection of booleans into a . + /// + /// The collection of booleans to pack. + /// A 64-bit signed integer containing the packed booleans. + /// is . + /// contains more than 64 elements. + [Pure] + public static long PackInt64(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source.Count > 64) + { + throw new ArgumentException("Source cannot contain more than than 64 elements.", nameof(source)); + } + + var result = 0L; + + for (var i = 0; i < source.Count; i++) + { + result |= source[i] ? 1L << i : 0; + } + + return result; + } +} diff --git a/X10D/src/Collections/ByteExtensions.cs b/X10D/src/Collections/ByteExtensions.cs new file mode 100644 index 0000000..be77738 --- /dev/null +++ b/X10D/src/Collections/ByteExtensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class ByteExtensions +{ + private const int Size = sizeof(byte) * 8; + + /// + /// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with length 8. + [Pure] + public static bool[] Unpack(this byte value) + { + Span buffer = stackalloc bool[Size]; + value.Unpack(buffer); + return buffer.ToArray(); + } + + /// + /// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + public static void Unpack(this byte value, Span destination) + { + if (destination.Length < Size) + { + throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); + } + + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } + } +} diff --git a/X10D/src/Collections/DictionaryExtensions.cs b/X10D/src/Collections/DictionaryExtensions.cs new file mode 100644 index 0000000..fe0db27 --- /dev/null +++ b/X10D/src/Collections/DictionaryExtensions.cs @@ -0,0 +1,435 @@ +using System.Diagnostics.Contracts; +using System.Web; + +namespace X10D.Collections; + +/// +/// Extension methods for and similar types. +/// +public static class DictionaryExtensions +{ + /// + /// Adds a key/value pair to the if the key does not already exist, or updates a + /// key/value pair in the by using the specified function if the key already + /// exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The value to be added for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The new value for the key. This will be either be (if the key was absent) or the result + /// of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this IDictionary dictionary, TKey key, TValue addValue, + Func updateValueFactory) + where TKey : notnull + { + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } + + if (dictionary.ContainsKey(key)) + { + dictionary[key] = updateValueFactory(key, dictionary[key]); + } + else + { + dictionary.Add(key, addValue); + } + + return dictionary[key]; + } + + /// + /// Uses the specified functions to add a key/value pair to the if the key does + /// not already exist, or to update a key/value pair in the if the key already + /// exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The function used to generate a value for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The new value for the key. This will be either be the result of (if the key was + /// absent) or the result of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this IDictionary dictionary, TKey key, + Func addValueFactory, Func updateValueFactory) + where TKey : notnull + { + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (addValueFactory is null) + { + throw new ArgumentNullException(nameof(addValueFactory)); + } + + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } + + if (dictionary.ContainsKey(key)) + { + dictionary[key] = updateValueFactory(key, dictionary[key]); + } + else + { + dictionary.Add(key, addValueFactory(key)); + } + + return dictionary[key]; + } + + /// + /// Uses the specified functions and argument to add a key/value pair to the if + /// the key does not already exist, or to update a key/value pair in the if th + /// key already exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The function used to generate a value for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// + /// An argument to pass into and . + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The type of an argument to pass into and . + /// + /// + /// The new value for the key. This will be either be the result of (if the key was + /// absent) or the result of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this IDictionary dictionary, TKey key, + Func addValueFactory, Func updateValueFactory, TArg factoryArgument) + where TKey : notnull + { + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (addValueFactory is null) + { + throw new ArgumentNullException(nameof(addValueFactory)); + } + + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } + + if (dictionary.ContainsKey(key)) + { + dictionary[key] = updateValueFactory(key, dictionary[key], factoryArgument); + } + else + { + dictionary.Add(key, addValueFactory(key, factoryArgument)); + } + + return dictionary[key]; + } + + /// + /// Converts an of to a data connection + /// string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// A representing the dictionary as a key=value set, concatenated with ;. + /// is . + [Pure] + public static string ToConnectionString(this IEnumerable> source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + static string SanitizeValue(string? value) + { + if (value is null) + { + return string.Empty; + } + + return value.Contains(' ') ? $"\"{value}\"" : value; + } + + static string GetQueryParameter(KeyValuePair pair) + { + return $"{pair.Key}={SanitizeValue(pair.Value?.ToString())}"; + } + + return string.Join(';', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to a data connection + /// string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// + /// A transform function to apply to the of each element. + /// + /// A representing the dictionary as a key=value set, concatenated with ;. + /// + /// is . + /// -or- + /// is . + /// + [Pure] + public static string ToConnectionString(this IEnumerable> source, + Func selector) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + static string SanitizeValue(string? value) + { + if (value is null) + { + return string.Empty; + } + + return value.Contains(' ') ? $"\"{value}\"" : value; + } + + string GetQueryParameter(KeyValuePair pair) + { + return $"{pair.Key}={SanitizeValue(selector(pair.Value))}"; + } + + return string.Join(';', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to an data connection + /// string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// + /// A transform function to apply to the of each element. + /// + /// + /// A transform function to apply to the of each element. + /// + /// A representing the dictionary as a key=value set, concatenated with ;. + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + [Pure] + public static string ToConnectionString(this IEnumerable> source, + Func keySelector, Func valueSelector) + where TKey : notnull + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector is null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (valueSelector is null) + { + throw new ArgumentNullException(nameof(valueSelector)); + } + + static string SanitizeValue(string? value) + { + if (value is null) + { + return string.Empty; + } + + return value.Contains(' ') ? $"\"{value}\"" : value; + } + + string GetQueryParameter(KeyValuePair pair) + { + return $"{keySelector(pair.Key)}={SanitizeValue(valueSelector(pair.Value))}"; + } + + return string.Join(';', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to a HTTP GET query string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// A representing the dictionary as a key=value set, concatenated with &. + /// is . + [Pure] + public static string ToGetParameters(this IEnumerable> source) + where TKey : notnull + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + static string GetQueryParameter(KeyValuePair pair) + { + string key = HttpUtility.UrlEncode(pair.Key.ToString())!; + string? value = HttpUtility.UrlEncode(pair.Value?.ToString()); + return $"{key}={value}"; + } + + return string.Join('&', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to a HTTP GET query string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// + /// A transform function to apply to the of each element. + /// + /// A representing the dictionary as a key=value set, concatenated with &. + /// + /// is . + /// -or- + /// is . + /// + [Pure] + public static string ToGetParameters(this IEnumerable> source, + Func selector) + where TKey : notnull + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + // can't static here because of 'selector' parameter + string GetQueryParameter(KeyValuePair pair) + { + string key = HttpUtility.UrlEncode(pair.Key.ToString())!; + string? value = HttpUtility.UrlEncode(selector(pair.Value)); + return $"{key}={value}"; + } + + return string.Join('&', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to a HTTP GET query string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// + /// A transform function to apply to the of each element. + /// + /// + /// A transform function to apply to the of each element. + /// + /// A representing the dictionary as a key=value set, concatenated with &. + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + [Pure] + public static string ToGetParameters(this IEnumerable> source, + Func keySelector, Func valueSelector) + where TKey : notnull + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector is null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (valueSelector is null) + { + throw new ArgumentNullException(nameof(valueSelector)); + } + + // can't static here because of selector parameters + string GetQueryParameter(KeyValuePair pair) + { + string key = HttpUtility.UrlEncode(keySelector(pair.Key)); + string? value = HttpUtility.UrlEncode(valueSelector(pair.Value)); + return $"{key}={value}"; + } + + return string.Join('&', source.Select(GetQueryParameter)); + } +} diff --git a/X10D/src/Collections/EnumerableExtensions.cs b/X10D/src/Collections/EnumerableExtensions.cs new file mode 100644 index 0000000..baa8d19 --- /dev/null +++ b/X10D/src/Collections/EnumerableExtensions.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Extension methods for . +/// +public static class EnumerableExtensions +{ + /// + /// Reorganizes the elements in an enumerable by implementing a Fisher-Yates shuffle, and returns th shuffled result. + /// + /// The element type. + /// The to shuffle. + /// Optional. The instance to use for the shuffling. + [Pure] + public static IReadOnlyCollection Shuffled(this IEnumerable source, Random? random = null) + { + var list = new List(source); + list.Shuffle(random); + return list.AsReadOnly(); + } +} diff --git a/X10D/src/Collections/Int16Extensions.cs b/X10D/src/Collections/Int16Extensions.cs new file mode 100644 index 0000000..366cb12 --- /dev/null +++ b/X10D/src/Collections/Int16Extensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class Int16Extensions +{ + private const int Size = sizeof(short) * 8; + + /// + /// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with length 16. + [Pure] + public static bool[] Unpack(this short value) + { + Span buffer = stackalloc bool[Size]; + value.Unpack(buffer); + return buffer.ToArray(); + } + + /// + /// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + public static void Unpack(this short value, Span destination) + { + if (destination.Length < Size) + { + throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); + } + + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } + } +} diff --git a/X10D/src/Collections/Int32Extensions.cs b/X10D/src/Collections/Int32Extensions.cs new file mode 100644 index 0000000..82da040 --- /dev/null +++ b/X10D/src/Collections/Int32Extensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class Int32Extensions +{ + private const int Size = sizeof(int) * 8; + + /// + /// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with length 32. + [Pure] + public static bool[] Unpack(this int value) + { + Span buffer = stackalloc bool[Size]; + value.Unpack(buffer); + return buffer.ToArray(); + } + + /// + /// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + public static void Unpack(this int value, Span destination) + { + if (destination.Length < Size) + { + throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); + } + + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } + } +} diff --git a/X10D/src/Collections/Int64Extensions.cs b/X10D/src/Collections/Int64Extensions.cs new file mode 100644 index 0000000..d9c07ba --- /dev/null +++ b/X10D/src/Collections/Int64Extensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class Int64Extensions +{ + private const int Size = sizeof(long) * 8; + + /// + /// Unpacks this 64-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with length 64. + [Pure] + public static bool[] Unpack(this long value) + { + Span buffer = stackalloc bool[Size]; + value.Unpack(buffer); + return buffer.ToArray(); + } + + /// + /// Unpacks this 64-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + public static void Unpack(this long value, Span destination) + { + if (destination.Length < Size) + { + throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); + } + + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1L << index)) != 0; + } + } +} diff --git a/X10D/src/Collections/ListExtensions.cs b/X10D/src/Collections/ListExtensions.cs new file mode 100644 index 0000000..d943395 --- /dev/null +++ b/X10D/src/Collections/ListExtensions.cs @@ -0,0 +1,136 @@ +using System.Diagnostics.Contracts; +using X10D.Core; + +namespace X10D.Collections; + +/// +/// Extension methods for and . +/// +public static class ListExtensions +{ + /// + /// Assigns the given value to each element of the list. + /// + /// The list to be filled. + /// The value to assign to each list element. + /// The type of the elements in the list. + /// is . + public static void Fill(this IList source, T value) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + for (var i = 0; i < source.Count; i++) + { + source[i] = value; + } + } + + /// + /// Assigns the given value to the elements of the list which are within the range of + /// (inclusive) and the next number of indices. + /// + /// The list to be filled. + /// The value to assign to each list element. + /// A 32-bit integer that represents the index in the list at which filling begins. + /// The number of elements to fill. + /// The type of the elements in the list. + /// is . + /// + /// is less than 0. + /// -or- + /// is less than 0. + /// -or- + /// + exceeds the bounds of the list. + /// + public static void Fill(this IList source, T value, int startIndex, int count) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (startIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (startIndex + count > source.Count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (count == 0 || source.Count == 0) + { + return; + } + + for (int index = startIndex; index < startIndex + count; index++) + { + source[index] = value; + } + } + + /// + /// Returns a random element from the current list using a specified instance. + /// + /// The element type. + /// The source collection from which to draw. + /// + /// The instance to use for the shuffling. If is specified, + /// is used. + /// + /// A random element of type from . + /// is . + /// + /// + /// var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + /// var number = list.Random(); + /// + /// + [Pure] + public static T Random(this IReadOnlyList source, Random? random = null) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + random ??= System.Random.Shared; + return random.NextFrom(source); + } + + /// + /// Reorganizes the elements in a list by implementing a Fisher-Yates shuffle. + /// + /// The element type. + /// The to shuffle. + /// + /// The instance to use for the shuffling. If is specified, + /// is used. + /// + /// is . + public static void Shuffle(this IList source, Random? random = null) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + random ??= System.Random.Shared; + + int count = source.Count; + while (count > 0) + { + int index = random.Next(count--); + (source[count], source[index]) = (source[index], source[count]); + } + } +} diff --git a/X10D/src/ComparableExtensions.cs b/X10D/src/ComparableExtensions.cs deleted file mode 100644 index 798112b..0000000 --- a/X10D/src/ComparableExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class ComparableExtensions - { - /// - /// Determines if is between and . - /// - /// The comparable type. - /// The value to compare. - /// The exclusive lower bound. - /// The exclusive upper bound. - /// - /// Returns if the value is between the bounds, - /// otherwise. - /// - public static bool Between(this T actual, T lower, T upper) - where T : IComparable - { - return actual.CompareTo(lower) > 0 && actual.CompareTo(upper) < 0; - } - } -} diff --git a/X10D/src/ConvertibleExtensions.cs b/X10D/src/ConvertibleExtensions.cs deleted file mode 100644 index bf2a3ce..0000000 --- a/X10D/src/ConvertibleExtensions.cs +++ /dev/null @@ -1,161 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class ConvertibleExtensions - { - /// - /// Converts the object to another type. - /// - /// The type to convert to. - /// The object to convert. - /// An object that supplies culture-specific formatting information. - /// Returns the value converted to . - /// - /// This conversion is not supported. - /// -or- - /// is and is a value type. - /// - [CLSCompliant(false)] - public static T To(this IConvertible value, IFormatProvider provider = null) - { - if (value is null) - { - return default; - } - - return (T)Convert.ChangeType(value, typeof(T), provider); - } - - /// - /// Converts the object to another type, returning the default value on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The format provider. - /// Returns the value converted to . - /// This conversion is not supported. - [CLSCompliant(false)] - public static T ToOrDefault(this IConvertible value, IFormatProvider provider = null) - { - return value is null ? default : To(value, provider); - } - - /// - /// Converts the object to another type, returning the default value on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The parameter where the result should be sent. - /// An object that supplies culture-specific formatting information. - /// Returns on success, on failure. - [CLSCompliant(false)] - public static bool ToOrDefault(this IConvertible value, out T newObj, IFormatProvider provider = null) - { - if (value is null) - { - newObj = default; - return false; - } - - try - { - newObj = To(value, provider); - return true; - } - catch (InvalidCastException) - { - newObj = default; - return false; - } - } - - /// - /// Converts the object to another type, returning on failure. - /// - /// The type to convert to. - /// The object to convert. - /// An object that supplies culture-specific formatting information. - /// Returns a or . - [CLSCompliant(false)] - public static T ToOrNull(this IConvertible value, IFormatProvider provider = null) - where T : class - { - return value.ToOrNull(out T v, provider) ? v : null; - } - - /// - /// Converts the object to another type, returning on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The parameter where the result should be sent. - /// An object that supplies culture-specific formatting information. - /// Returns a or . - [CLSCompliant(false)] - public static bool ToOrNull(this IConvertible value, out T newObj, IFormatProvider provider = null) - where T : class - { - return ToOrOther(value, out newObj, null, provider); - } - - /// - /// Converts the object to another type, returning a different value on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The backup value. - /// An object that supplies culture-specific formatting information. - /// Returns the value converted to . - [CLSCompliant(false)] - public static T ToOrOther(this IConvertible value, T other, IFormatProvider provider = null) - { - if (value is null) - { - return other; - } - - try - { - return To(value, provider); - } - catch (Exception ex) when (ex is InvalidCastException || ex is FormatException) - { - return other; - } - } - - /// - /// Converts the object to another type, returning a different value on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The parameter where the result should be sent. - /// The backup value. - /// An object that supplies culture-specific formatting information. - /// Returns on success, on failure. - [CLSCompliant(false)] - public static bool ToOrOther(this IConvertible value, out T newObj, T other, IFormatProvider provider = null) - { - if (value is null) - { - newObj = other; - return false; - } - - try - { - newObj = To(value, provider); - return true; - } - catch (Exception ex) when (ex is InvalidCastException || ex is FormatException) - { - newObj = other; - return false; - } - } - } -} diff --git a/X10D/src/Core/EnumExtensions.cs b/X10D/src/Core/EnumExtensions.cs new file mode 100644 index 0000000..fbba9b4 --- /dev/null +++ b/X10D/src/Core/EnumExtensions.cs @@ -0,0 +1,89 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Core; + +/// +/// Extension methods for types. +/// +public static class EnumExtensions +{ + /// + /// Returns the value which is defined proceeding this value in the enumeration. + /// + /// The type of the enumeration. + /// The value whose proceeding value to retrieve. + /// + /// A value of that is considered to be the next value defined after , + /// or the first value if is the final field of the enumeration. + /// + [Pure] + public static T Next(this T value) + where T : struct, Enum + { + T[] values = Enum.GetValues(); + int index = Array.IndexOf(values, value) + 1; + index %= values.Length; + return values[index]; + } + + /// + /// Returns the value which is defined proceeding this value in the enumeration. + /// + /// The type of the enumeration. + /// The value whose proceeding value to retrieve. + /// + /// A value of that is considered to be the next value defined after + /// . + /// + /// is the final field of the enumeration. + [Pure] + public static T NextUnchecked(this T value) + where T : struct, Enum + { + T[] values = Enum.GetValues(); + int index = Array.IndexOf(values, value) + 1; + return values[index]; + } + + /// + /// Returns the value which is defined preceeding this value in the enumeration. + /// + /// The type of the enumeration. + /// The value whose preceeding value to retrieve. + /// + /// A value of that is considered to be the previous value defined after + /// , or the last value if is the first field of the enumeration. + /// + [Pure] + public static T Previous(this T value) + where T : struct, Enum + { + T[] values = Enum.GetValues(); + int index = Array.IndexOf(values, value) - 1; + int length = values.Length; + + // negative modulo is not supported in C#. workaround: https://stackoverflow.com/a/1082938/1467293 + // sure, simply checking for index < 0 is enough, but this expression is so fucking cool! + index = (index % length + length) % length; + return values[index]; + } + + /// + /// Returns the value which is defined preceeding this value in the enumeration. + /// + /// The type of the enumeration. + /// The value whose preceeding value to retrieve. + /// + /// A value of that is considered to be the previous value defined after + /// , or the last value if is the first field of the enumeration. + /// + /// is the first field of the enumeration. + [Pure] + public static T PreviousUnchecked(this T value) + where T : struct, Enum + { + T[] values = Enum.GetValues(); + int index = Array.IndexOf(values, value) - 1; + return values[index]; + } +} diff --git a/X10D/src/Core/Extensions.cs b/X10D/src/Core/Extensions.cs new file mode 100644 index 0000000..21348d6 --- /dev/null +++ b/X10D/src/Core/Extensions.cs @@ -0,0 +1,59 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Core; + +/// +/// Extension methods which apply to all types. +/// +public static class Extensions +{ + /// + /// Returns an array containing the specified value. + /// + /// The value to encapsulate. + /// The type of . + /// + /// An array of type with length 1, whose only element is . + /// + [Pure] + public static T?[] AsArrayValue(this T? value) + { + return new[] {value}; + } + + /// + /// Returns an enumerable collection containing the specified value. + /// + /// The value to encapsulate. + /// The type of . + /// + /// An enumerable collection of type , whose only element is . + /// + [Pure] + public static IEnumerable AsEnumerableValue(this T? value) + { + yield return value; + } + + /// + /// Returns an enumerable collection containing the current value repeated a specified number of times. + /// + /// The value to repeat. + /// The number of times to repeat . + /// The type of . + /// An enumerable collection containing repeated times. + /// is less than 0. + [Pure] + public static IEnumerable RepeatValue(this T value, int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), ExceptionMessages.CountMustBeGreaterThanOrEqualTo0); + } + + for (var i = 0; i < count; i++) + { + yield return value; + } + } +} diff --git a/X10D/src/Core/RandomExtensions.cs b/X10D/src/Core/RandomExtensions.cs new file mode 100644 index 0000000..a2c4ac9 --- /dev/null +++ b/X10D/src/Core/RandomExtensions.cs @@ -0,0 +1,418 @@ +using System.Globalization; +using System.Text; +using X10D.Math; + +namespace X10D.Core; + +/// +/// Extension methods for . +/// +public static class RandomExtensions +{ + /// + /// Returns a random value that defined in a specified enum. + /// + /// The instance. + /// An enum type. + /// + /// A value at index n where n = . + /// + /// is . + public static T Next(this Random random) + where T : struct, Enum + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + var values = Enum.GetValues(typeof(T)); + return (T)values.GetValue(random.Next(values.Length))!; + } + + /// + /// Returns either or based on the next generation of the current + /// . + /// + /// The instance. + /// + /// if the return value from is greater than or + /// equal to 0.5 + /// -or- + /// otherwise. + /// + /// is . + public static bool NextBoolean(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return random.NextDouble() >= 0.5; + } + + /// + /// Returns a non-negative random double-precision floating point number that is less than the specified maximum. + /// + /// The instance. + /// + /// The exclusive upper bound of the random number returned. This value must be greater than or equal to 0. + /// + /// + /// A random double-precision floating point number that is greater than or equal to 0, and less than + /// . + /// + /// is . + /// is less than 0. + public static double NextDouble(this Random random, double maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException(ExceptionMessages.MaxValueGreaterThanEqualTo0); + } + + return random.NextDouble(0, maxValue); + } + + /// + /// Returns a random double-precision floating point number that is within a specified range. + /// + /// The instance. + /// The inclusive lower bound of the random number returned. + /// + /// The exclusive upper bound of the random number returned. This value must be greater than or equal to + /// . + /// + /// + /// A random double-precision floating point number between and + /// . + /// + /// is . + /// + /// is less than . + /// + public static double NextDouble(this Random random, double minValue, double maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < minValue) + { + throw new ArgumentException(ExceptionMessages.MaxValueGreaterThanEqualToMinValue); + } + + return MathUtility.Lerp(minValue, maxValue, random.NextDouble()); + } + + /// + /// Returns a random element from using the instance. + /// + /// The element type. + /// The instance. + /// The source collection from which to draw. + /// A random element of type from . + /// + /// is is + /// -or- + /// is . + /// + /// + /// + /// var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + /// var random = new Random(); + /// var number = random.NextFrom(list); + /// + /// + public static T NextFrom(this Random random, IEnumerable source) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source is T[] array) + { + return array[random.Next(array.Length)]; + } + + if (source is not IReadOnlyList list) + { + list = source.ToList(); + } + + return list[random.Next(list.Count)]; + } + + /// + /// Returns a non-negative random integer. + /// + /// The instance. + /// + /// An 8-bit unsigned integer that is greater than or equal to 0, and less than . + /// + /// is . + public static byte NextByte(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return random.NextByte(byte.MaxValue); + } + + /// + /// Returns a non-negative random integer. + /// + /// The instance. + /// + /// The exclusive upper bound of the random number to be generated. must be greater than or + /// equal to 0. + /// + /// + /// An 8-bit unsigned integer that is greater than or equal to 0, and less than ; that is, the + /// range of return values ordinarily includes 0 but not . However, if + /// equals 0, is returned. + /// + /// is . + public static byte NextByte(this Random random, byte maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return random.NextByte(0, maxValue); + } + + /// + /// Returns a non-negative random integer. + /// + /// The instance. + /// The inclusive lower bound of the random number to be generated. + /// + /// The exclusive upper bound of the random number to be generated. must be greater than or + /// equal to . + /// + /// + /// An 8-bit unsigned integer greater than or equal to and less than + /// ; that is, the range of return values includes but not + /// . If equals , + /// is returned. + /// + /// is . + /// + /// is greater than . + /// + public static byte NextByte(this Random random, byte minValue, byte maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return (byte)random.Next(minValue, maxValue); + } + + /// + /// Returns a non-negative random integer. + /// + /// The instance. + /// + /// An 16-bit signed integer that is greater than or equal to 0, and less than . + /// + /// is . + public static short NextInt16(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return random.NextInt16(short.MaxValue); + } + + /// + /// Returns a non-negative random integer that is less than the specified maximum. + /// + /// The instance. + /// + /// The exclusive upper bound of the random number to be generated. must be greater than or + /// equal to 0. + /// + /// + /// A 16-bit signed integer that is greater than or equal to 0, and less than ; that is, the + /// range of return values ordinarily includes 0 but not . However, if + /// equals 0, is returned. + /// + /// is . + /// is less than 0. + public static short NextInt16(this Random random, short maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxValue)); + } + + return random.NextInt16(0, maxValue); + } + + /// + /// Returns a random integer that is within a specified range. + /// + /// The instance. + /// The inclusive lower bound of the random number to be generated. + /// + /// The exclusive upper bound of the random number to be generated. must be greater than or + /// equal to . + /// + /// + /// An 8-bit unsigned integer greater than or equal to and less than + /// ; that is, the range of return values includes but not + /// . If equals , + /// is returned. + /// + /// + /// is greater than . + /// + /// is . + public static short NextInt16(this Random random, short minValue, short maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return (short)random.Next(minValue, maxValue); + } + + /// + /// Returns a non-negative random single-precision floating point number that is less than the specified maximum. + /// + /// The instance. + /// + /// The exclusive upper bound of the random number returned. This value must be greater than or equal to 0. + /// + /// + /// A random single-precision floating point number that is greater than or equal to 0, and less than + /// . + /// + /// is . + /// is less than 0. + public static float NextSingle(this Random random, float maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxValue)); + } + + return random.NextSingle(0, maxValue); + } + + /// + /// Returns a random single-precision floating point number that is within a specified range. + /// + /// The instance. + /// The inclusive lower bound of the random number returned. + /// + /// The exclusive upper bound of the random number returned. This value must be greater than or equal to + /// . + /// + /// + /// A random single-precision floating point number between and + /// . + /// + /// is . + /// + /// is less than . + /// + public static float NextSingle(this Random random, float minValue, float maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < minValue) + { + throw new ArgumentException(ExceptionMessages.MaxValueGreaterThanEqualToMinValue); + } + + return MathUtility.Lerp(minValue, maxValue, random.NextSingle()); + } + + /// + /// Returns a new string of a specified length which is composed of specified characters. + /// + /// The instance. + /// The source collection of characters to poll. + /// The length of the new string to generate. + /// + /// A whose length is equal to that of , composed of characters + /// specified by the characters in . + /// + /// + /// is . + /// -or- + /// is . + /// + /// is less than 0. + public static string NextString(this Random random, IReadOnlyList source, int length) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), ExceptionMessages.LengthGreaterThanOrEqualTo0); + } + + if (length == 0) + { + return string.Empty; + } + + if (length == 1) + { + return source[random.Next(0, source.Count)].ToString(CultureInfo.InvariantCulture); + } + + var builder = new StringBuilder(length); + for (var i = 0; i < length; i++) + { + builder.Append(source[random.Next(0, source.Count)]); + } + + return builder.ToString(); + } +} diff --git a/X10D/src/DateTimeExtensions.cs b/X10D/src/DateTimeExtensions.cs deleted file mode 100644 index 84a559d..0000000 --- a/X10D/src/DateTimeExtensions.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class DateTimeExtensions - { - /// - /// Returns a rounded integer of the number of years since a given date as of today. - /// - /// The date from which to start. - /// Returns the number of years since as of today. - public static int Age(this DateTime date) - { - return date.Age(DateTime.Today); - } - - /// - /// Returns a rounded integer of the number of years since a given date as of another given date. - /// - /// The date from which to start. - /// The date at which to stop counting. - /// - /// Returns the integer number of years since as of - /// . - /// - public static int Age(this DateTime date, DateTime asOf) - { - return (int)(((asOf.Date - TimeSpan.FromDays(1) - date.Date).TotalDays + 1) / 365.2425); - } - - /// - /// Gets a DateTime representing the first occurence of a specified day in the current month. - /// - /// The current day. - /// The current day of week. - /// Returns a date representing the first occurence of . - public static DateTime First(this DateTime current, DayOfWeek dayOfWeek) - { - var first = current.FirstDayOfMonth(); - - if (first.DayOfWeek != dayOfWeek) - { - first = first.Next(dayOfWeek); - } - - return first; - } - - /// - /// Gets a representing the first day in the current month. - /// - /// The current date. - /// Returns a date representing the first day of the month>. - public static DateTime FirstDayOfMonth(this DateTime current) - { - return current.AddDays(1 - current.Day); - } - - /// - /// Gets a representing the last specified day in the current month. - /// - /// The current date. - /// The current day of week. - /// Returns a date representing the final occurence of . - public static DateTime Last(this DateTime current, DayOfWeek dayOfWeek) - { - var last = current.LastDayOfMonth(); - var lastDayOfWeek = last.DayOfWeek; - - var diff = dayOfWeek - lastDayOfWeek; - var offset = diff > 0 ? diff - 7 : diff; - - return last.AddDays(offset); - } - - /// - /// Gets a representing the last day in the current month. - /// - /// The current date. - /// Returns a date representing the last day of the month>. - public static DateTime LastDayOfMonth(this DateTime current) - { - var daysInMonth = DateTime.DaysInMonth(current.Year, current.Month); - return new DateTime(current.Year, current.Month, daysInMonth); - } - - /// - /// Gets a representing the first date following the current date which falls on the - /// given day of the week. - /// - /// The current date. - /// The day of week for the next date to get. - /// Returns a date representing the next occurence of . - public static DateTime Next(this DateTime current, DayOfWeek dayOfWeek) - { - var offsetDays = dayOfWeek - current.DayOfWeek; - - if (offsetDays <= 0) - { - offsetDays += 7; - } - - return current.AddDays(offsetDays); - } - - /// - /// Converts the to a Unix timestamp. - /// - /// The instance. - /// - /// Optional. Whether or not the return value should be represented as milliseconds. - /// Defaults to . - /// - /// Returns a Unix timestamp representing the provided . - public static long ToUnixTimeStamp(this DateTime time, bool useMillis = false) - { - DateTimeOffset offset = time; - return useMillis ? offset.ToUnixTimeMilliseconds() : offset.ToUnixTimeSeconds(); - } - } -} diff --git a/X10D/src/DictionaryExtensions.cs b/X10D/src/DictionaryExtensions.cs deleted file mode 100644 index 8f12bee..0000000 --- a/X10D/src/DictionaryExtensions.cs +++ /dev/null @@ -1,101 +0,0 @@ -namespace X10D -{ - using System.Collections.Generic; - using System.Linq; - using System.Text.RegularExpressions; - using System.Web; - - /// - /// A set of extension methods for . - /// - public static class DictionaryExtensions - { - /// - /// Converts a to an object-relational-safe connection string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value; set. - public static string ToConnectionString(this IReadOnlyDictionary dictionary) - { - static string SanitizeValue(T value) - { - return value is string str && - Regex.IsMatch(str, "\\s") - ? $"\"{str}\"" - : value.ToString(); - } - - var strings = dictionary.Select(o => $"{o.Key}={SanitizeValue(o.Value)}"); - return string.Join(";", strings); - } - - /// - /// Converts a to an object-relational-safe connection string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value; set. - public static string ToConnectionString(this IDictionary dictionary) - { - return ((IReadOnlyDictionary)dictionary).ToConnectionString(); - } - - /// - /// Converts a to an object-relational-safe connection string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value; set. - public static string ToConnectionString(this Dictionary dictionary) - { - return ((IReadOnlyDictionary)dictionary).ToConnectionString(); - } - - /// - /// Converts an to a HTTP GET parameter string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value& set. - public static string ToGetParameters(this IReadOnlyDictionary dictionary) - { - static string Sanitize(KeyValuePair kvp) - { - var key = HttpUtility.UrlEncode(kvp.Key.ToString()); - var value = HttpUtility.UrlEncode(kvp.Value.ToString()); - return $"{key}={value}"; - } - - return string.Join("&", dictionary.Select(Sanitize)); - } - - /// - /// Converts an to a HTTP GET parameter string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value& set. - public static string ToGetParameters(this IDictionary dictionary) - { - return ((IReadOnlyDictionary)dictionary).ToGetParameters(); - } - - /// - /// Converts a to a HTTP GET parameter string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value& set. - public static string ToGetParameters(this Dictionary dictionary) - { - return ((IReadOnlyDictionary)dictionary).ToGetParameters(); - } - } -} diff --git a/X10D/src/DoubleExtensions.cs b/X10D/src/DoubleExtensions.cs deleted file mode 100644 index bd6e531..0000000 --- a/X10D/src/DoubleExtensions.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class DoubleExtensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static double Clamp(this double value, double min, double max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts an angle from degrees to radians. - /// - /// The angle in degrees. - /// Returns in radians. - public static double DegreesToRadians(this double angle) - { - return (Math.PI * angle) / 180.0; - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this double number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this double number) - { - return Math.Abs(number % 2.0) < double.Epsilon; - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this double number) - { - return !number.IsEven(); - } - - /// - /// Converts an angle from radians to degrees. - /// - /// The angle in radians. - /// Returns in degrees. - public static double RadiansToDegrees(this double angle) - { - return angle * (180.0 / Math.PI); - } - - /// - /// Rounds to the nearest value. - /// - /// The value to round. - /// The nearest value. - /// Returns the rounded value. - public static double Round(this double v, double nearest = 1) - { - return Math.Round(v / nearest) * nearest; - } - } -} diff --git a/X10D/src/Drawing/RandomExtensions.cs b/X10D/src/Drawing/RandomExtensions.cs new file mode 100644 index 0000000..32cf1e8 --- /dev/null +++ b/X10D/src/Drawing/RandomExtensions.cs @@ -0,0 +1,43 @@ +using System.Drawing; + +namespace X10D.Drawing; + +/// +/// Extension methods for . +/// +public static class RandomExtensions +{ + /// + /// Returns a color of random components for red, green, and blue. + /// + /// The instance. + /// A whose red, green, and blue components are all random, and whose alpha is 255 + /// is . + public static Color NextColorRgb(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int rgb = random.Next(); + return Color.FromArgb(0xFF, (byte)(rgb >> 16 & 0xFF), (byte)(rgb >> 8 & 0xFF), (byte)(rgb & 0xFF)); + } + + /// + /// Returns a color composed of random components for apha, red, green, and blue. + /// + /// The instance. + /// A whose alpha, red, green, and blue components are all random. + /// is . + public static Color NextColorArgb(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int argb = random.Next(); + return Color.FromArgb(argb); + } +} diff --git a/X10D/src/EndPointExtensions.cs b/X10D/src/EndPointExtensions.cs deleted file mode 100644 index 7da5099..0000000 --- a/X10D/src/EndPointExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace X10D -{ - using System.Net; - - /// - /// Extension methods for and derived types. - /// - public static class EndPointExtensions - { - /// - /// Gets the endpoint hostname. - /// - /// The endpoint whose hostname to get. - /// - /// Returns a representing the hostname, which may be an IP or a DNS, or empty - /// string on failure. - /// - public static string GetHost(this EndPoint endPoint) - { - return endPoint switch - { - IPEndPoint ip => ip.Address.ToString(), - DnsEndPoint dns => dns.Host, - var _ => string.Empty, - }; - } - - /// - /// Gets the endpoint port. - /// - /// The endpoint whose port to get. - /// Returns an representing the port, or 0 on failure. - public static int GetPort(this EndPoint endPoint) - { - return endPoint switch - { - IPEndPoint ip => ip.Port, - DnsEndPoint dns => dns.Port, - var _ => 0, - }; - } - } -} diff --git a/X10D/src/Endianness.cs b/X10D/src/Endianness.cs new file mode 100644 index 0000000..3b23c07 --- /dev/null +++ b/X10D/src/Endianness.cs @@ -0,0 +1,19 @@ +using System.ComponentModel; + +namespace X10D; + +/// +/// Represents an enumeration of endianness values. +/// +public enum Endianness +{ + /// + /// The value should be read as though it uses little endian encoding. + /// + [Description("The value should be read as though it uses little endian encoding.")] LittleEndian, + + /// + /// The value should be read as though it uses big endian encoding. + /// + [Description("The value should be read as though it uses big endian encoding.")] BigEndian +} diff --git a/X10D/src/EnumerableExtensions.cs b/X10D/src/EnumerableExtensions.cs deleted file mode 100644 index 9d09e43..0000000 --- a/X10D/src/EnumerableExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace X10D -{ - using System.Collections.Generic; - using System.Linq; - - /// - /// Extension methods for . - /// - public static class EnumerableExtensions - { - /// - /// Splits into chunks of size . - /// - /// Any type. - /// The collection to split. - /// The maximum length of the nested collection. - /// - /// Returns an of of - /// values. - /// - public static IEnumerable> Split(this IEnumerable value, int chunkSize) - { - var enumerable = value.ToArray(); - var count = enumerable.LongCount(); - chunkSize = chunkSize.Clamp(1, enumerable.Length); - - for (var i = 0; i < (int)(count / chunkSize); i++) - { - yield return enumerable.Skip(i * chunkSize).Take(chunkSize); - } - } - } -} diff --git a/X10D/src/ExceptionMessages.Designer.cs b/X10D/src/ExceptionMessages.Designer.cs new file mode 100644 index 0000000..7a62adb --- /dev/null +++ b/X10D/src/ExceptionMessages.Designer.cs @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace X10D { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ExceptionMessages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ExceptionMessages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("X10D.src.ExceptionMessages", typeof(ExceptionMessages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The buffer is too small to contain the data.. + /// + internal static string BufferTooSmall { + get { + return ResourceManager.GetString("BufferTooSmall", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to count must be greater than or equal to 0.. + /// + internal static string CountMustBeGreaterThanOrEqualTo0 { + get { + return ResourceManager.GetString("CountMustBeGreaterThanOrEqualTo0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HashAlgorithm's Create method returned null reference.. + /// + internal static string HashAlgorithmCreateReturnedNull { + get { + return ResourceManager.GetString("HashAlgorithmCreateReturnedNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HashAlgorithm does not offer Create method.. + /// + internal static string HashAlgorithmNoCreateMethod { + get { + return ResourceManager.GetString("HashAlgorithmNoCreateMethod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Length must be greater than or equal to 0.. + /// + internal static string LengthGreaterThanOrEqualTo0 { + get { + return ResourceManager.GetString("LengthGreaterThanOrEqualTo0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} cannot be greater than {1}. + /// + internal static string LowerCannotBeGreaterThanUpper { + get { + return ResourceManager.GetString("LowerCannotBeGreaterThanUpper", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to maxValue must be greater than or equal to 0. + /// + internal static string MaxValueGreaterThanEqualTo0 { + get { + return ResourceManager.GetString("MaxValueGreaterThanEqualTo0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to maxValue must be greater than or equal to minValue. + /// + internal static string MaxValueGreaterThanEqualToMinValue { + get { + return ResourceManager.GetString("MaxValueGreaterThanEqualToMinValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The stream does not support reading.. + /// + internal static string StreamDoesNotSupportReading { + get { + return ResourceManager.GetString("StreamDoesNotSupportReading", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The stream does not support writing.. + /// + internal static string StreamDoesNotSupportWriting { + get { + return ResourceManager.GetString("StreamDoesNotSupportWriting", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The length of the stream is too large.. + /// + internal static string StreamTooLarge { + get { + return ResourceManager.GetString("StreamTooLarge", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} does not inherit {1}. + /// + internal static string TypeDoesNotInheritAttribute { + get { + return ResourceManager.GetString("TypeDoesNotInheritAttribute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not a class.. + /// + internal static string TypeIsNotClass { + get { + return ResourceManager.GetString("TypeIsNotClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not an interface.. + /// + internal static string TypeIsNotInterface { + get { + return ResourceManager.GetString("TypeIsNotInterface", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Year cannot be zero.. + /// + internal static string YearCannotBeZero { + get { + return ResourceManager.GetString("YearCannotBeZero", resourceCulture); + } + } + } +} diff --git a/X10D/src/ExceptionMessages.resx b/X10D/src/ExceptionMessages.resx new file mode 100644 index 0000000..e5beb21 --- /dev/null +++ b/X10D/src/ExceptionMessages.resx @@ -0,0 +1,71 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + The buffer is too small to contain the data. + + + {0} is not a class. + + + {0} is not an interface. + + + {0} does not inherit {1} + + + HashAlgorithm does not offer Create method. + + + HashAlgorithm's Create method returned null reference. + + + Length must be greater than or equal to 0. + + + The stream does not support reading. + + + The stream does not support writing. + + + The length of the stream is too large. + + + maxValue must be greater than or equal to 0 + + + maxValue must be greater than or equal to minValue + + + {0} cannot be greater than {1} + + + count must be greater than or equal to 0. + + + Year cannot be zero. + + \ No newline at end of file diff --git a/X10D/src/IO/BooleanExtensions.cs b/X10D/src/IO/BooleanExtensions.cs new file mode 100644 index 0000000..f5bc878 --- /dev/null +++ b/X10D/src/IO/BooleanExtensions.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// Extension methods for . +/// +public static class BooleanExtensions +{ + /// + /// Returns the current boolean value as an array of bytes. + /// + /// The value to convert. + /// An array of bytes with length 1. + [Pure] + public static byte[] GetBytes(this bool value) + { + return BitConverter.GetBytes(value); + } + + /// + /// Converts a into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this bool value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } +} diff --git a/X10D/src/IO/ByteExtensions.cs b/X10D/src/IO/ByteExtensions.cs new file mode 100644 index 0000000..eace285 --- /dev/null +++ b/X10D/src/IO/ByteExtensions.cs @@ -0,0 +1,38 @@ +using System.Diagnostics.Contracts; +using X10D.Core; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class ByteExtensions +{ + /// + /// Returns the current 8-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 1. + [Pure] + public static byte[] GetBytes(this byte value) + { + return value.AsArrayValue(); + } + + /// + /// Converts a into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this byte value, Span destination) + { + if (destination.Length < 1) + { + return false; + } + + destination[0] = value; + return true; + } +} diff --git a/X10D/src/IO/DoubleExtensions.cs b/X10D/src/IO/DoubleExtensions.cs new file mode 100644 index 0000000..b33df1e --- /dev/null +++ b/X10D/src/IO/DoubleExtensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class DoubleExtensions +{ + /// + /// Returns the current double-precision floating-point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this double value) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current double-precision floating-point value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this double value, Endianness endianness) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current double-precision floating-point into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this double value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current double-precision floating-point into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this double value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteDoubleBigEndian(destination, value) + : BinaryPrimitives.TryWriteDoubleLittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/FileInfoExtensions.cs b/X10D/src/IO/FileInfoExtensions.cs new file mode 100644 index 0000000..272c99a --- /dev/null +++ b/X10D/src/IO/FileInfoExtensions.cs @@ -0,0 +1,76 @@ +using System.Diagnostics.Contracts; +using System.Security.Cryptography; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class FileInfoExtensions +{ + /// + /// Computes the hash of a file using the specified hash algorithm. + /// + /// The file whose hash to compute. + /// + /// The type of the whose is to be used for + /// computing the hash. + /// + /// The hash of represented as an array of bytes. + /// is . + /// The specified file was not found. + /// The opened file stream cannot be read. + /// + /// The specified does not offer a public, static. parameterless Create method, or its + /// Create method returns a type that is not assignable to . + /// + /// The stream has already been disposed. + [Pure] + public static byte[] GetHash(this FileInfo value) + where T : HashAlgorithm + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + using FileStream stream = value.OpenRead(); + return stream.GetHash(); + } + + /// + /// Computes the hash of a file using the specified hash algorithm. + /// + /// The file whose hash to compute. + /// When this method returns, contains the computed hash of . + /// + /// When this method returns, the total number of bytes written into destination. This parameter is treated as + /// uninitialized. + /// + /// + /// The type of the whose is to be used for + /// computing the hash. + /// + /// + /// if the destination is long enough to receive the hash; otherwise, . + /// + /// is . + /// The specified file was not found. + /// The opened file stream cannot be read. + /// + /// The specified does not offer a public, static. parameterless Create method, or its + /// Create method returns a type that is not assignable to . + /// + /// The stream has already been disposed. + public static bool TryWriteHash(this FileInfo value, Span destination, out int bytesWritten) + where T : HashAlgorithm + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + using FileStream stream = value.OpenRead(); + return stream.TryWriteHash(destination, out bytesWritten); + } +} diff --git a/X10D/src/IO/Int16Extensions.cs b/X10D/src/IO/Int16Extensions.cs new file mode 100644 index 0000000..61be94a --- /dev/null +++ b/X10D/src/IO/Int16Extensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Returns the current 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + [Pure] + public static byte[] GetBytes(this short value) + { + Span buffer = stackalloc byte[2]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 2. + [Pure] + public static byte[] GetBytes(this short value, Endianness endianness) + { + Span buffer = stackalloc byte[2]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 16-bit signed integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this short value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 16-bit signed integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this short value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteInt16BigEndian(destination, value) + : BinaryPrimitives.TryWriteInt16LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/Int32Extensions.cs b/X10D/src/IO/Int32Extensions.cs new file mode 100644 index 0000000..57668fb --- /dev/null +++ b/X10D/src/IO/Int32Extensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Returns the current 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this int value) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this int value, Endianness endianness) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 32-bit signed integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this int value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 32-bit signed integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this int value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteInt32BigEndian(destination, value) + : BinaryPrimitives.TryWriteInt32LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/Int64Extensions.cs b/X10D/src/IO/Int64Extensions.cs new file mode 100644 index 0000000..2fd7f05 --- /dev/null +++ b/X10D/src/IO/Int64Extensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Returns the current 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this long value) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this long value, Endianness endianness) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 64-bit signed integer a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this long value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 64-bit signed integer a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this long value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteInt64BigEndian(destination, value) + : BinaryPrimitives.TryWriteInt64LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/ListOfByteExtensions.cs b/X10D/src/IO/ListOfByteExtensions.cs new file mode 100644 index 0000000..dbec6e1 --- /dev/null +++ b/X10D/src/IO/ListOfByteExtensions.cs @@ -0,0 +1,290 @@ +using System.Text; + +namespace X10D.IO; + +/// +/// Extension methods for array. +/// +public static class ListOfByteExtensions +{ + /// + /// Converts the numeric value of each element of a specified list of bytes to its equivalent hexadecimal string + /// representation. + /// + /// The source list of bytes. + /// + /// A string of hexadecimal pairs separated by hyphens, where each pair represents the corresponding element in + /// ; for example, "7F-2C-4A-00". + /// + /// is . + public static string AsString(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToString(source.ToArray()); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes. + /// + /// The source list of bytes. + /// A double-precision floating point number formed by eight bytes. + /// is . + public static double ToDouble(this IReadOnlyList source) + { + return ToDouble(source, 0); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes at a specified position in a list of + /// bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// + /// A double-precision floating point number formed by eight bytes beginning at . + /// + /// is . + public static double ToDouble(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToDouble(source.ToArray(), startIndex); + } + + /// + /// Returns a 16-bit signed integer converted from two bytes. + /// + /// The source list of bytes. + /// A 16-bit signed integer formed by two bytes. + /// is . + public static short ToInt16(this IReadOnlyList source) + { + return ToInt16(source, 0); + } + + /// + /// Returns a 16-bit signed integer converted from two bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 16-bit signed integer formed by two bytes beginning at . + /// is . + public static short ToInt16(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToInt16(source.ToArray(), startIndex); + } + + /// + /// Returns a 32-bit signed integer converted from four bytes. + /// + /// The source list of bytes. + /// A 32-bit signed integer formed by four bytes. + /// is . + public static int ToInt32(this IReadOnlyList source) + { + return ToInt32(source, 0); + } + + /// + /// Returns a 32-bit signed integer converted from four bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 32-bit signed integer formed by four bytes beginning at . + /// is . + public static int ToInt32(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToInt32(source.ToArray(), startIndex); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes. + /// + /// The source list of bytes. + /// A 64-bit signed integer formed by eight bytes. + /// is . + public static long ToInt64(this IReadOnlyList source) + { + return ToInt64(source, 0); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 64-bit signed integer formed by eight bytes beginning at . + /// is . + public static long ToInt64(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToInt64(source.ToArray(), startIndex); + } + + /// + /// Returns a single-precision floating point number converted from four bytes. + /// + /// The source list of bytes. + /// A single-precision floating point number formed by four bytes. + /// is . + public static float ToSingle(this IReadOnlyList source) + { + return ToSingle(source, 0); + } + + /// + /// Returns a single-precision floating point number converted from four bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// + /// A single-precision floating point number formed by four bytes beginning at . + /// + /// is . + public static float ToSingle(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToSingle(source.ToArray(), startIndex); + } + + /// + /// Decodes all the bytes within the current list of bytes to a string, using a specified encoding. + /// + /// The source list of bytes. + /// The encoding which should be used to decode . + /// A string that contains the results of decoding the specified sequence of bytes. + /// + /// is . + /// -or- + /// is . + /// + public static string ToString(this IReadOnlyList source, Encoding encoding) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (encoding is null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return encoding.GetString(source.ToArray()); + } + + /// + /// Returns a 16-bit unsigned integer converted from two bytes. + /// + /// The source list of bytes. + /// A 16-bit unsigned integer formed by two bytes. + /// is . + [CLSCompliant(false)] + public static ushort ToUInt16(this IReadOnlyList source) + { + return ToUInt16(source, 0); + } + + /// + /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 16-bit unsigned integer formed by two bytes beginning at . + /// is . + [CLSCompliant(false)] + public static ushort ToUInt16(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToUInt16(source.ToArray(), startIndex); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes. + /// + /// The source list of bytes. + /// A 32-bit unsigned integer formed by four bytes. + /// is . + [CLSCompliant(false)] + public static uint ToUInt32(this IReadOnlyList source) + { + return ToUInt32(source, 0); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 32-bit unsigned integer formed by four bytes beginning at . + /// is . + [CLSCompliant(false)] + public static uint ToUInt32(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToUInt32(source.ToArray(), startIndex); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes. + /// + /// The source list of bytes. + /// A 64-bit unsigned integer formed by eight bytes. + /// is . + [CLSCompliant(false)] + public static ulong ToUInt64(this IReadOnlyList source) + { + return ToUInt64(source, 0); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 64-bit unsigned integer formed by eight bytes beginning at . + /// is . + [CLSCompliant(false)] + public static ulong ToUInt64(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToUInt64(source.ToArray(), startIndex); + } +} diff --git a/X10D/src/IO/SByteExtensions.cs b/X10D/src/IO/SByteExtensions.cs new file mode 100644 index 0000000..d3e02c5 --- /dev/null +++ b/X10D/src/IO/SByteExtensions.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +[CLSCompliant(false)] +public static class SByteExtensions +{ + /// + /// Returns the current 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 1. + [Pure] + public static byte[] GetBytes(this sbyte value) + { + Span buffer = stackalloc byte[1]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Converts the current 16-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this sbyte value, Span destination) + { + if (destination.Length < 1) + { + return false; + } + + destination[0] = (byte)value; + return true; + } +} diff --git a/X10D/src/IO/SingleExtensions.cs b/X10D/src/IO/SingleExtensions.cs new file mode 100644 index 0000000..ea9ae4b --- /dev/null +++ b/X10D/src/IO/SingleExtensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class SingleExtensions +{ + /// + /// Returns the current single-precision floating-point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this float value) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current single-precision floating-point value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this float value, Endianness endianness) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current single-precision floating-point into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this float value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current single-precision floating-point into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this float value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteSingleBigEndian(destination, value) + : BinaryPrimitives.TryWriteSingleLittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/StreamExtensions.cs b/X10D/src/IO/StreamExtensions.cs new file mode 100644 index 0000000..ab353de --- /dev/null +++ b/X10D/src/IO/StreamExtensions.cs @@ -0,0 +1,877 @@ +using System.Buffers.Binary; +using System.Reflection; +using System.Security.Cryptography; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class StreamExtensions +{ + private static readonly Endianness DefaultEndianness = + BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; + + /// + /// Returns the hash of the current stream as an array of bytes using the specified hash algorithm. + /// + /// The stream whose hash is to be computed. + /// + /// The type of the whose is to be used for + /// computing the hash. + /// + /// The hash of represented as an array of bytes. + /// is + /// does not support reading. + /// + /// The specified does not offer a public, static. parameterless Create method, or its + /// Create method returns a type that is not assignable to . + /// + /// The stream has already been disposed. + public static byte[] GetHash(this Stream stream) + where T : HashAlgorithm + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanRead) + { + throw new IOException(ExceptionMessages.StreamDoesNotSupportReading); + } + + Type type = typeof(T); + + MethodInfo? createMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(c => c.Name == "Create" && c.GetParameters().Length == 0); + if (createMethod is null) + { + throw new TypeInitializationException(type.FullName, + new ArgumentException(ExceptionMessages.HashAlgorithmNoCreateMethod)); + } + + using var crypt = createMethod.Invoke(null, null) as T; + if (crypt is null) + { + throw new TypeInitializationException(type.FullName, + new ArgumentException(ExceptionMessages.HashAlgorithmCreateReturnedNull)); + } + + return crypt.ComputeHash(stream); + } + + /// + /// Reads a decimal value from the current stream using the system's default endian encoding, and advances the stream + /// position by sixteen bytes. + /// + /// The stream to read. + /// A sixteen-byte decimal value read from the stream. + public static decimal ReadDecimal(this Stream stream) + { + return stream.ReadDecimal(DefaultEndianness); + } + + /// + /// Reads a decimal value from the current stream using a specified endian encoding, and advances the stream position + /// by sixteen bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// A decimal value read from the stream. + public static decimal ReadDecimal(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + const int decimalSize = sizeof(decimal); + const int int32Size = sizeof(int); + const int partitionSize = decimalSize / int32Size; + + var bits = new int[partitionSize]; + for (var index = 0; index < partitionSize; index++) + { + bits[index] = stream.ReadInt32(endianness); + } + + if (endianness != DefaultEndianness) + { + Array.Reverse(bits); + } + + return new decimal(bits); + } + + /// + /// Reads a double-precision floating point value from the current stream using the system's default endian encoding, + /// and advances the stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// A double-precision floating point value read from the stream. + public static double ReadDouble(this Stream stream) + { + return stream.ReadDouble(DefaultEndianness); + } + + /// + /// Reads a double-precision floating point value from the current stream using a specified endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// A double-precision floating point value read from the stream. + public static double ReadDouble(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(double)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadDoubleLittleEndian(buffer) + : BinaryPrimitives.ReadDoubleBigEndian(buffer); + } + + /// + /// Reads a two-byte signed integer from the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream from which the value should be read. + /// An two-byte signed integer read from the stream. + public static short ReadInt16(this Stream stream) + { + return stream.ReadInt16(DefaultEndianness); + } + + /// + /// Reads a two-byte signed integer from the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An two-byte unsigned integer read from the stream. + public static short ReadInt16(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(short)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt16LittleEndian(buffer) + : BinaryPrimitives.ReadInt16BigEndian(buffer); + } + + /// + /// Reads a four-byte signed integer from the current stream using the system's default endian encoding, and advances + /// the stream position by four bytes. + /// + /// The stream from which the value should be read. + /// An four-byte signed integer read from the stream. + public static int ReadInt32(this Stream stream) + { + return stream.ReadInt32(DefaultEndianness); + } + + /// + /// Reads a four-byte signed integer from the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An four-byte unsigned integer read from the stream. + public static int ReadInt32(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(int)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt32LittleEndian(buffer) + : BinaryPrimitives.ReadInt32BigEndian(buffer); + } + + /// + /// Reads an eight-byte signed integer from the current stream using the system's default endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// An eight-byte signed integer read from the stream. + public static long ReadInt64(this Stream stream) + { + return stream.ReadInt64(DefaultEndianness); + } + + /// + /// Reads an eight-byte signed integer from the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An eight-byte unsigned integer read from the stream. + public static long ReadInt64(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(long)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt64LittleEndian(buffer) + : BinaryPrimitives.ReadInt64BigEndian(buffer); + } + + /// + /// Reads a single-precision floating point value from the current stream using the system's default endian encoding, + /// and advances the stream position by four bytes. + /// + /// The stream from which the value should be read. + /// A single-precision floating point value read from the stream. + public static double ReadSingle(this Stream stream) + { + return stream.ReadSingle(DefaultEndianness); + } + + /// + /// Reads a double-precision floating point value from the current stream using a specified endian encoding, and + /// advances the stream position by four bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// A single-precision floating point value read from the stream. + public static double ReadSingle(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(float)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadSingleLittleEndian(buffer) + : BinaryPrimitives.ReadSingleBigEndian(buffer); + } + + /// + /// Reads a two-byte unsigned integer from the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream from which the value should be read. + /// An two-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static ushort ReadUInt16(this Stream stream) + { + return stream.ReadUInt16(DefaultEndianness); + } + + /// + /// Reads a two-byte unsigned integer from the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An two-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static ushort ReadUInt16(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(ushort)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt16LittleEndian(buffer) + : BinaryPrimitives.ReadUInt16BigEndian(buffer); + } + + /// + /// Reads a four-byte unsigned integer from the current stream using the system's default endian encoding, and + /// advances the stream position by four bytes. + /// + /// The stream from which the value should be read. + /// An four-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static uint ReadUInt32(this Stream stream) + { + return stream.ReadUInt32(DefaultEndianness); + } + + /// + /// Reads a four-byte unsigned integer from the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An four-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static uint ReadUInt32(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(uint)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt32LittleEndian(buffer) + : BinaryPrimitives.ReadUInt32BigEndian(buffer); + } + + /// + /// Reads an eight-byte unsigned integer from the current stream using the system's default endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// An eight-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static ulong ReadUInt64(this Stream stream) + { + return stream.ReadUInt64(DefaultEndianness); + } + + /// + /// Reads an eight-byte unsigned integer from the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An eight-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static ulong ReadUInt64(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(ulong)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt64LittleEndian(buffer) + : BinaryPrimitives.ReadUInt64BigEndian(buffer); + } + + /// + /// Returns the hash of the current stream as an array of bytes using the specified hash algorithm. + /// + /// The stream whose hash is to be computed. + /// When this method returns, contains the computed hash of . + /// + /// When this method returns, the total number of bytes written into destination. This parameter is treated as + /// uninitialized. + /// + /// + /// The type of the whose is to be used for + /// computing the hash. + /// + /// + /// if the destination is long enough to receive the hash; otherwise, . + /// + /// is + /// does not support reading. + /// + /// The specified does not offer a public, static. parameterless Create method, or its + /// Create method returns a type that is not assignable to . + /// + /// The stream has already been disposed. + public static bool TryWriteHash(this Stream stream, Span destination, out int bytesWritten) + where T : HashAlgorithm + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanRead) + { + throw new IOException(ExceptionMessages.StreamDoesNotSupportReading); + } + + Type type = typeof(T); + + MethodInfo? createMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(c => c.Name == "Create" && c.GetParameters().Length == 0); + if (createMethod is null) + { + throw new TypeInitializationException(type.FullName, + new ArgumentException(ExceptionMessages.HashAlgorithmNoCreateMethod)); + } + + using var crypt = createMethod.Invoke(null, null) as T; + if (crypt is null) + { + throw new TypeInitializationException(type.FullName, + new ArgumentException(ExceptionMessages.HashAlgorithmCreateReturnedNull)); + } + + if (stream.Length > int.MaxValue) + { + throw new ArgumentException(ExceptionMessages.StreamTooLarge); + } + + Span buffer = stackalloc byte[(int)stream.Length]; + _ = stream.Read(buffer); // we don't care about the number of bytes read. we can ignore MustUseReturnValue + return crypt.TryComputeHash(buffer, destination, out bytesWritten); + } + + /// + /// Writes a two-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, short value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a two-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, short value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(short)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt16LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteInt16BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a four-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, int value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a four-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, int value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(int)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt32LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteInt32BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, long value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, long value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(long)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt64LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteInt64BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a two-byte unsigned integer to the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ushort value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a two-byte unsigned integer to the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte unsigned integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ushort value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(ushort)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt16LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteUInt16BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a four-byte unsigned integer to the current stream using the system's default endian encoding, and advances + /// the stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, uint value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a four-byte unsigned integer to the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte unsigned integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, uint value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(uint)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes an eight-byte unsigned integer to the current stream using the system's default endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ulong value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ulong value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(ulong)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a single-precision floating point value to the current stream using the specified endian encoding, and + /// advances the stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The single-precision floating point value to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, float value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(float)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteSingleLittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteSingleBigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a double-precision floating point value to the current stream using the specified endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The double-precision floating point value to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, double value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(double)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteDoubleLittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteDoubleBigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a decimal value to the current stream using the specified endian encoding, and advances the stream position + /// by sixteen bytes. + /// + /// The stream to which the value should be written. + /// The decimal value to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, decimal value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + int[] bits = decimal.GetBits(value); + long preWritePosition = stream.Position; + + if (endianness != DefaultEndianness) + { + Array.Reverse(bits); + } + + foreach (int section in bits) + { + stream.Write(section, endianness); + } + + return (int)(stream.Position - preWritePosition); + } + + private static int WriteInternal(this Stream stream, Span value) + { + long preWritePosition = stream.Position; + stream.Write(value); + return (int)(stream.Position - preWritePosition); + } +} diff --git a/X10D/src/IO/UInt16Extensions.cs b/X10D/src/IO/UInt16Extensions.cs new file mode 100644 index 0000000..5eb12ae --- /dev/null +++ b/X10D/src/IO/UInt16Extensions.cs @@ -0,0 +1,63 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt16Extensions +{ + /// + /// Returns the current 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + [Pure] + public static byte[] GetBytes(this ushort value) + { + Span buffer = stackalloc byte[2]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 2. + [Pure] + public static byte[] GetBytes(this ushort value, Endianness endianness) + { + Span buffer = stackalloc byte[2]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 16-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this ushort value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 16-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this ushort value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteUInt16BigEndian(destination, value) + : BinaryPrimitives.TryWriteUInt16LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/UInt32Extensions.cs b/X10D/src/IO/UInt32Extensions.cs new file mode 100644 index 0000000..c4f3c30 --- /dev/null +++ b/X10D/src/IO/UInt32Extensions.cs @@ -0,0 +1,63 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt32Extensions +{ + /// + /// Returns the current 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this uint value) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this uint value, Endianness endianness) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 32-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this uint value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 32-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this uint value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteUInt32BigEndian(destination, value) + : BinaryPrimitives.TryWriteUInt32LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/UInt64Extensions.cs b/X10D/src/IO/UInt64Extensions.cs new file mode 100644 index 0000000..1f48869 --- /dev/null +++ b/X10D/src/IO/UInt64Extensions.cs @@ -0,0 +1,63 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt64Extensions +{ + /// + /// Returns the current 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this ulong value) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this ulong value, Endianness endianness) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 64-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this ulong value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 64-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this ulong value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteUInt64BigEndian(destination, value) + : BinaryPrimitives.TryWriteUInt64LittleEndian(destination, value); + } +} diff --git a/X10D/src/Int16Extensions.cs b/X10D/src/Int16Extensions.cs deleted file mode 100644 index 264891f..0000000 --- a/X10D/src/Int16Extensions.cs +++ /dev/null @@ -1,189 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class Int16Extensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static short Clamp(this short value, short min, short max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - [CLSCompliant(false)] - public static ushort Clamp(this ushort value, ushort min, ushort max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts the to a treating it as a Unix timestamp. - /// - /// The timestamp. - /// - /// Optional. Whether or not the input value should be treated as milliseconds. Defaults - /// to .. - /// - /// - /// Returns a representing seconds since the Unix - /// epoch. - /// - public static DateTime FromUnixTimestamp(this short timestamp, bool isMillis = false) - { - return ((long)timestamp).FromUnixTimestamp(isMillis); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - [CLSCompliant(false)] - public static byte[] GetBytes(this ushort number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this short number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this short number) - { - return ((long)number).IsEven(); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsEven(this ushort number) - { - return ((ulong)number).IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this short number) - { - return !number.IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsOdd(this ushort number) - { - return !number.IsEven(); - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - public static bool IsPrime(this short number) - { - return ((long)number).IsPrime(); - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsPrime(this ushort number) - { - return ((ulong)number).IsPrime(); - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - public static bool ToBoolean(this short value) - { - return ((long)value).ToBoolean(); - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool ToBoolean(this ushort value) - { - return ((ulong)value).ToBoolean(); - } - } -} diff --git a/X10D/src/Int32Extensions.cs b/X10D/src/Int32Extensions.cs deleted file mode 100644 index 309cfe5..0000000 --- a/X10D/src/Int32Extensions.cs +++ /dev/null @@ -1,175 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class Int32Extensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static int Clamp(this int value, int min, int max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - [CLSCompliant(false)] - public static uint Clamp(this uint value, uint min, uint max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts the to a treating it as a Unix timestamp. - /// - /// The timestamp. - /// - /// Optional. Whether or not the input value should be treated as milliseconds. Defaults - /// to .. - /// - /// - /// Returns a representing seconds since the Unix - /// epoch. - /// - public static DateTime FromUnixTimestamp(this int timestamp, bool isMillis = false) - { - return ((long)timestamp).FromUnixTimestamp(isMillis); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - [CLSCompliant(false)] - public static byte[] GetBytes(this uint number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this int number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this int number) - { - return ((long)number).IsEven(); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsEven(this uint number) - { - return ((ulong)number).IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this int number) - { - return !number.IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsOdd(this uint number) - { - return !number.IsEven(); - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - public static bool IsPrime(this int number) - { - return ((long)number).IsPrime(); - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - public static bool ToBoolean(this int value) - { - return ((long)value).ToBoolean(); - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool ToBoolean(this uint value) - { - return ((ulong)value).ToBoolean(); - } - } -} diff --git a/X10D/src/Int64Extensions.cs b/X10D/src/Int64Extensions.cs deleted file mode 100644 index 8a54030..0000000 --- a/X10D/src/Int64Extensions.cs +++ /dev/null @@ -1,241 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class Int64Extensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static long Clamp(this long value, long min, long max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - [CLSCompliant(false)] - public static ulong Clamp(this ulong value, ulong min, ulong max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts the to a treating it as a Unix timestamp. - /// - /// The timestamp. - /// - /// Optional. Whether or not the input value should be treated as milliseconds. Defaults - /// to . - /// - /// - /// Returns a representing seconds since the Unix - /// epoch. - /// - public static DateTime FromUnixTimestamp(this long timestamp, bool isMillis = false) - { - var offset = isMillis - ? DateTimeOffset.FromUnixTimeMilliseconds(timestamp) - : DateTimeOffset.FromUnixTimeSeconds(timestamp); - - return offset.DateTime; - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - [CLSCompliant(false)] - public static byte[] GetBytes(this ulong number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this long number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this long number) - { - return Math.Abs(number % 2.0) < double.Epsilon; - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsEven(this ulong number) - { - return Math.Abs(number % 2.0) < double.Epsilon; - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this long number) - { - return !IsEven(number); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsOdd(this ulong number) - { - return !IsEven(number); - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - public static bool IsPrime(this long number) - { - if (number <= 1) - { - return false; - } - - if (number == 2) - { - return true; - } - - if (number % 2 == 0) - { - return false; - } - - var boundary = (long)Math.Floor(Math.Sqrt(number)); - for (var i = 3; i <= boundary; i += 2) - { - if (number % i == 0) - { - return false; - } - } - - return true; - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsPrime(this ulong number) - { - if (number <= 1) - { - return false; - } - - if (number == 2) - { - return true; - } - - if (number % 2 == 0) - { - return false; - } - - var boundary = (ulong)Math.Floor(Math.Sqrt(number)); - for (uint i = 3; i <= boundary; i += 2) - { - if (number % i == 0) - { - return false; - } - } - - return true; - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - public static bool ToBoolean(this long value) - { - return value != 0; - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool ToBoolean(this ulong value) - { - return value != 0; - } - } -} diff --git a/X10D/src/Linq/ByteExtensions.cs b/X10D/src/Linq/ByteExtensions.cs new file mode 100644 index 0000000..ee049ff --- /dev/null +++ b/X10D/src/Linq/ByteExtensions.cs @@ -0,0 +1,137 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class ByteExtensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static byte Product(this IEnumerable source) + { + return source.Aggregate((byte)1, (current, value) => (byte)(current * value)); + } + + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + [CLSCompliant(false)] + public static sbyte Product(this IEnumerable source) + { + return source.Aggregate((sbyte)1, (current, value) => (sbyte)(current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static byte Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + [CLSCompliant(false)] + public static sbyte Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Returns an enumerable sequence of 8-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 8-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this byte value, byte end) + { + byte start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (byte current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 16-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 16-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this byte value, short end) + { + short start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (short current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 32-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 32-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this byte value, int end) + { + int start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (int current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 64-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 64-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this byte value, long end) + { + long start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (long current = start; current < end; current++) + { + yield return current; + } + } +} diff --git a/X10D/src/Linq/DecimalExtensions.cs b/X10D/src/Linq/DecimalExtensions.cs new file mode 100644 index 0000000..bcdf193 --- /dev/null +++ b/X10D/src/Linq/DecimalExtensions.cs @@ -0,0 +1,30 @@ +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class DecimalExtensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static decimal Product(this IEnumerable source) + { + return source.Aggregate(1m, (current, value) => (current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static decimal Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } +} diff --git a/X10D/src/Linq/DoubleExtensions.cs b/X10D/src/Linq/DoubleExtensions.cs new file mode 100644 index 0000000..70ac1ca --- /dev/null +++ b/X10D/src/Linq/DoubleExtensions.cs @@ -0,0 +1,30 @@ +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class DoubleExtensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static double Product(this IEnumerable source) + { + return source.Aggregate(1.0, (current, value) => (current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static double Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } +} diff --git a/X10D/src/Linq/Int16Extensions.cs b/X10D/src/Linq/Int16Extensions.cs new file mode 100644 index 0000000..c6c3872 --- /dev/null +++ b/X10D/src/Linq/Int16Extensions.cs @@ -0,0 +1,117 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class Int16Extensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static short Product(this IEnumerable source) + { + return source.Aggregate((short)1, (current, value) => (short)(current * value)); + } + + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + [CLSCompliant(false)] + public static ushort Product(this IEnumerable source) + { + return source.Aggregate((ushort)1, (current, value) => (ushort)(current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static short Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + [CLSCompliant(false)] + public static ushort Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Returns an enumerable sequence of 16-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 16-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this short value, short end) + { + short start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (short current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 32-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 32-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this short value, int end) + { + int start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (int current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 64-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 64-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this short value, long end) + { + long start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (long current = start; current < end; current++) + { + yield return current; + } + } +} diff --git a/X10D/src/Linq/Int32Extensions.cs b/X10D/src/Linq/Int32Extensions.cs new file mode 100644 index 0000000..923470e --- /dev/null +++ b/X10D/src/Linq/Int32Extensions.cs @@ -0,0 +1,97 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class Int32Extensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static int Product(this IEnumerable source) + { + return source.Aggregate(1, (current, value) => current * value); + } + + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + [CLSCompliant(false)] + public static uint Product(this IEnumerable source) + { + return source.Aggregate(1u, (current, value) => current * value); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function on + /// each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static int Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function on + /// each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + [CLSCompliant(false)] + public static uint Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Returns an enumerable sequence of 32-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 32-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this int value, int end) + { + int start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (int current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 64-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 64-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this int value, long end) + { + long start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (long current = start; current < end; current++) + { + yield return current; + } + } +} diff --git a/X10D/src/Linq/Int64Extensions.cs b/X10D/src/Linq/Int64Extensions.cs new file mode 100644 index 0000000..4870456 --- /dev/null +++ b/X10D/src/Linq/Int64Extensions.cs @@ -0,0 +1,77 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class Int64Extensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static long Product(this IEnumerable source) + { + return source.Aggregate(1L, (current, value) => current * value); + } + + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + [CLSCompliant(false)] + public static ulong Product(this IEnumerable source) + { + return source.Aggregate(1UL, (current, value) => current * value); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function on + /// each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static long Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + [CLSCompliant(false)] + public static ulong Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Returns an enumerable sequence of 64-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 64-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this long value, long end) + { + long start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (long current = start; current < end; current++) + { + yield return current; + } + } +} diff --git a/X10D/src/Linq/ReadOnlySpanExtensions.cs b/X10D/src/Linq/ReadOnlySpanExtensions.cs new file mode 100644 index 0000000..3bbbfca --- /dev/null +++ b/X10D/src/Linq/ReadOnlySpanExtensions.cs @@ -0,0 +1,79 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// Extension methods for . +/// +public static class ReadOnlySpanExtensions +{ + /// + /// Determines whether all elements of a read-only span satisfy a condition. + /// + /// A that contains the elements to apply the predicate to. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// if every element of the source sequence passes the test in the specified predicate, or if the + /// span is empty; otherwise, . + /// + /// is . + [Pure] + public static bool All(this ReadOnlySpan source, Predicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (source.IsEmpty) + { + return true; + } + + for (var index = 0; index < source.Length; index++) + { + if (!predicate(source[index])) + { + return false; + } + } + + return true; + } + + /// + /// Determines whether any element of a read-only span satisfies a condition. + /// + /// A that contains the elements to apply the predicate to. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// if the source span is not empty and at least one of its elements passes the test in the + /// specified predicate; otherwise, . + /// + /// is . + [Pure] + public static bool Any(this ReadOnlySpan source, Predicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (source.IsEmpty) + { + return false; + } + + for (var index = 0; index < source.Length; index++) + { + if (predicate(source[index])) + { + return true; + } + } + + return false; + } +} diff --git a/X10D/src/Linq/SingleExtensions.cs b/X10D/src/Linq/SingleExtensions.cs new file mode 100644 index 0000000..1797eaf --- /dev/null +++ b/X10D/src/Linq/SingleExtensions.cs @@ -0,0 +1,30 @@ +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class SingleExtensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static float Product(this IEnumerable source) + { + return source.Aggregate(1f, (current, value) => (current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static float Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } +} diff --git a/X10D/src/Linq/SpanExtensions.cs b/X10D/src/Linq/SpanExtensions.cs new file mode 100644 index 0000000..6de52f3 --- /dev/null +++ b/X10D/src/Linq/SpanExtensions.cs @@ -0,0 +1,79 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// Extension methods for . +/// +public static class SpanExtensions +{ + /// + /// Determines whether all elements of a span satisfy a condition. + /// + /// A that contains the elements to apply the predicate to. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// if every element of the source sequence passes the test in the specified predicate, or if the + /// span is empty; otherwise, . + /// + /// is . + [Pure] + public static bool All(this Span source, Predicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (source.IsEmpty) + { + return true; + } + + for (var index = 0; index < source.Length; index++) + { + if (!predicate(source[index])) + { + return false; + } + } + + return true; + } + + /// + /// Determines whether any element of a span satisfies a condition. + /// + /// A that contains the elements to apply the predicate to. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// if the source span is not empty and at least one of its elements passes the test in the + /// specified predicate; otherwise, . + /// + /// is . + [Pure] + public static bool Any(this Span source, Predicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (source.IsEmpty) + { + return false; + } + + for (var index = 0; index < source.Length; index++) + { + if (predicate(source[index])) + { + return true; + } + } + + return false; + } +} diff --git a/X10D/src/ListExtensions.cs b/X10D/src/ListExtensions.cs deleted file mode 100644 index e4e5086..0000000 --- a/X10D/src/ListExtensions.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Extension methods for . - /// - public static class ListExtensions - { - /// - /// Returns a random element from using a new instance. - /// - /// The collection type. - /// The collection to draw from. - /// Returns a random element of type from . - public static T OneOf(this IEnumerable source) - { - return source.OneOf(new Random()); - } - - /// - /// Returns a random element from using the instance. - /// - /// The collection type. - /// The collection to draw from. - /// The instance. - /// Returns a random element of type from . - public static T OneOf(this IEnumerable source, Random random) - { - return source.ToList().OneOf(random); - } - - /// - /// Returns a random element from using a new instance. - /// - /// The collection type. - /// The collection to draw from. - /// Returns a random element of type from . - public static T OneOf(this IList source) - { - return source.OneOf(new Random()); - } - - /// - /// Returns a random element from using the instance. - /// - /// The collection type. - /// The collection to draw from. - /// The instance. - /// Returns a random element of type from . - public static T OneOf(this IList source, Random random) - { - return random.OneOf(source); - } - - /// - /// Shuffles an enumerable. - /// - /// The collection type. - /// The collection to shuffle. - /// Returns shuffled. - public static IEnumerable Shuffle(this IEnumerable source) - { - return source.Shuffle(new Random()); - } - - /// - /// Shuffles an enumerable. - /// - /// The collection type. - /// The collection to shuffle. - /// The instance. - /// Returns shuffled. - public static IEnumerable Shuffle(this IEnumerable source, Random random) - { - return source.OrderBy(_ => random.Next()); - } - - /// - /// Shuffles a list. - /// - /// The collection type. - /// The collection to shuffle. - /// Returns shuffled. - public static IEnumerable Shuffle(this IList source) - { - return source.Shuffle(new Random()); - } - - /// - /// Shuffles a list. - /// - /// The collection type. - /// The collection to shuffle. - /// The instance. - /// Returns shuffled. - public static IEnumerable Shuffle(this IList source, Random random) - { - return source.OrderBy(_ => random.Next()); - } - } -} diff --git a/X10D/src/Math/ByteExtensions.cs b/X10D/src/Math/ByteExtensions.cs new file mode 100644 index 0000000..8e25088 --- /dev/null +++ b/X10D/src/Math/ByteExtensions.cs @@ -0,0 +1,109 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Math-related extension methods for . +/// +public static class ByteExtensions +{ + /// + /// Computes the digital root of this 16-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte DigitalRoot(this byte value) + { + int root = value % 9; + return (byte)(root == 0 ? 9 : root); + } + + /// + /// Returns the factorial of the current 8-bit unsigned integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this byte value) + { + if (value == 0) + { + return 1; + } + + var result = 1L; + for (byte i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this byte value) + { + return value % 2 == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this byte value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + public static bool IsPrime(this byte value) + { + return ((long)value).IsPrime(); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this byte value) + { + return ((long)value).MultiplicativePersistence(); + } +} diff --git a/X10D/src/Math/ComparableExtensions.cs b/X10D/src/Math/ComparableExtensions.cs new file mode 100644 index 0000000..53bbc0e --- /dev/null +++ b/X10D/src/Math/ComparableExtensions.cs @@ -0,0 +1,321 @@ +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class ComparableExtensions +{ + /// + /// Determines if a specified value falls exclusively between a specified lower bound and upper bound. + /// + /// An type. + /// The first comparison operand type. + /// The second comparison operand type. + /// The value to compare. + /// The exclusive lower bound. + /// The exclusive upper bound. + /// The comparison clusivity. + /// + /// if is between the and + /// + /// -or- + /// otherwise. + /// + /// + /// + /// int firstValue = 42; + /// int secondValue = 15; + /// + /// int lower = 0; + /// int upper = 20; + /// + /// Console.WriteLine($"{firstValue} between {lower} and {upper}?"); + /// Console.WriteLine(firstValue.Between(lower, upper)); + /// + /// Console.WriteLine($"{secondValue} between {lower} and {upper}?"); + /// Console.WriteLine(secondValue.Between(lower, upper)); + /// + /// // This will output the following: + /// // 42 between 0 and 20? + /// // False + /// // 15 between 0 and 20? + /// // True + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Between(this T1 value, T2 lower, T3 upper, + InclusiveOptions inclusiveOptions = InclusiveOptions.None) + where T1 : IComparable, IComparable + where T2 : IComparable + where T3 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (lower.GreaterThan(upper)) + { + throw new ArgumentException( + string.Format(CultureInfo.CurrentCulture, ExceptionMessages.LowerCannotBeGreaterThanUpper, lower, upper), + nameof(lower)); + } + + bool lowerComparison = (inclusiveOptions & InclusiveOptions.LowerInclusive) != 0 + ? value.CompareTo(lower) >= 0 + : value.CompareTo(lower) > 0; + + bool upperComparison = (inclusiveOptions & InclusiveOptions.UpperInclusive) != 0 + ? value.CompareTo(upper) <= 0 + : value.CompareTo(upper) < 0; + + return lowerComparison && upperComparison; + } + + /// + /// Returns the current value clamped to the inclusive range of and . + /// + /// The value to be clamped. + /// The lower bound of the result. + /// The upper bound of the result. + /// An type. + /// + /// if . + /// -or- + /// if < . + /// -or- + /// if < . + /// + /// is greater than . + /// + /// + /// int value = 42; + /// int lower = 0; + /// int upper = 20; + /// + /// int clamped = value.Clamp(lower, upper); + /// // clamped will be 20 + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T Clamp(this T value, T lower, T upper) + where T : IComparable + { + if (lower.GreaterThan(upper)) + { + throw new ArgumentException( + string.Format(ExceptionMessages.LowerCannotBeGreaterThanUpper, lower, upper), + nameof(lower)); + } + + return value.Max(lower).Min(upper); + } + + /// + /// Determines if the current value is greater than another value. + /// + /// The first value. + /// The second value. + /// An type. + /// The comparison operand type. + /// + /// if is greater than + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// bool result = first.GreaterThan(second); + /// // result will be False + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool GreaterThan(this T1 value, T2 other) + where T1 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.CompareTo(other) > 0; + } + + /// + /// Determines if the current value is greater than or equal to another value. + /// + /// The first value. + /// The second value. + /// An type. + /// The comparison operand type. + /// + /// if is greater than or equal to + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// bool result = first.GreaterThanOrEqualTo(second); + /// // result will be False + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool GreaterThanOrEqualTo(this T1 value, T2 other) + where T1 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.CompareTo(other) >= 0; + } + + /// + /// Determines if the current value is less than another value. + /// + /// The first value. + /// The second value. + /// An type. + /// The comparison operand type. + /// + /// if is less than + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// bool result = first.LessThan(second); + /// // result will be True + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool LessThan(this T1 value, T2 other) + where T1 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.CompareTo(other) < 0; + } + + /// + /// Determines if the current value is less than or equal to another value. + /// + /// The first value. + /// The second value. + /// An type. + /// The comparison operand type. + /// + /// if is less than or equal to + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// bool result = first.LessThanOrEqualTo(second); + /// // result will be True + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool LessThanOrEqualTo(this T1 value, T2 other) + where T1 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.CompareTo(other) <= 0; + } + + /// + /// Returns the maximum of two values. + /// + /// The first value. + /// The second value. + /// A type which implements . + /// + /// if is greater than + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// int max = first.Max(second); + /// // max will be 10 + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T Max(this T value, T other) + where T : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.GreaterThan(other) ? value : other; + } + + /// + /// Returns the minimum of two values. + /// + /// The first value. + /// The second value. + /// A type which implements . + /// + /// if is less than + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// int min = first.Min(second); + /// // min will be 5 + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T Min(this T value, T other) + where T : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.LessThan(other) ? value : other; + } +} diff --git a/X10D/src/Math/DecimalExtensions.cs b/X10D/src/Math/DecimalExtensions.cs new file mode 100644 index 0000000..a6dfe60 --- /dev/null +++ b/X10D/src/Math/DecimalExtensions.cs @@ -0,0 +1,173 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class DecimalExtensions +{ + /// + /// Returns the complex square root of this decimal number. + /// + /// The number whose square root is to be found. + /// The square root of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Complex ComplexSqrt(this decimal value) + { + return Complex.Sqrt((double)value); + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this decimal value) + { + return value % 2.0m == 0.0m; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this decimal value) + { + return !value.IsEven(); + } + + /// + /// Rounds the current value to the nearest whole number. + /// + /// The value to round. + /// rounded to the nearest whole number. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static decimal Round(this decimal value) + { + return value.Round(1.0m); + } + + /// + /// Rounds the current value to the nearest multiple of a specified number. + /// + /// The value to round. + /// The nearest multiple to which should be rounded. + /// rounded to the nearest multiple of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static decimal Round(this decimal value, decimal nearest) + { + return System.Math.Round(value / nearest) * nearest; + } + + /// + /// Returns an integer that indicates the sign of this decimal number. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this decimal value) + { + return System.Math.Sign(value); + } + + /// + /// Returns the square root of this double-precision floating-point number. + /// + /// The number whose square root is to be found. + /// + /// One of the values in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// The positive square root of . + /// is greater than or equal to 0. + /// + /// + /// + /// is equal to or is negative. + /// + /// + /// + /// is equal to . + /// + /// + /// + /// is negative. + /// + /// For negative input, this method returns . To receive a complex number, see + /// . + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static decimal Sqrt(this decimal value) + { + switch (value) + { + case 0: + return 0; + case < 0: + throw new ArgumentException("value cannot be negative", nameof(value)); + } + + decimal previous; + var current = (decimal)System.Math.Sqrt((double)value); + do + { + previous = current; + if (previous == 0.0m) + { + return 0; + } + + current = (previous + value / previous) / 2; + } while (System.Math.Abs(previous - current) > 0.0m); + + return current; + } +} diff --git a/X10D/src/Math/DoubleExtensions.cs b/X10D/src/Math/DoubleExtensions.cs new file mode 100644 index 0000000..5b393a7 --- /dev/null +++ b/X10D/src/Math/DoubleExtensions.cs @@ -0,0 +1,420 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Mathematical extension methods. +/// +public static class DoubleExtensions +{ + /// + /// Returns the arccosine of the specified value. + /// + /// + /// The value representing a cosine, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arccosine of , θ, measured in radians; such that 0 ≤ θ ≤ π. If + /// is equal to , less than -1, or greater than 1, is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Acos(this double value) + { + return System.Math.Acos(value); + } + + /// + /// Returns the hyperbolic arccosine of the specified value. + /// + /// + /// The value representing a hyperbolic cosine, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arccosine of , θ, measured in radians; such that 0 ≤ θ ≤ ∞. If + /// is less than 1 or equal to , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Acosh(this double value) + { + return System.Math.Acosh(value); + } + + /// + /// Returns the arcsine of the specified value. + /// + /// + /// The value representing a sine, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arccosine of , θ, measured in radians; such that π/2 ≤ θ ≤ π/2. If + /// is equal to , less than -1, or greater than 1, + /// is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Asin(this double value) + { + return System.Math.Asin(value); + } + + /// + /// Returns the hyperbolic arcsine of the specified value. + /// + /// + /// The value representing a hyperbolic sine, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arccosine of , measured in radians. If is equal to + /// , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Asinh(this double value) + { + return System.Math.Asinh(value); + } + + /// + /// Returns the arctangent of the specified value. + /// + /// + /// The value representing a tangent, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arctangent of , θ, measured in radians; such that π/2 ≤ θ ≤ π/2. If + /// is equal to , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Atan(this double value) + { + return System.Math.Atan(value); + } + + /// + /// Returns the hyperbolic arctangent of the specified value. + /// + /// + /// The value representing a hyperbolic tangent, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arctangent of , θ, measured in radians; such that -∞ < θ < -1, or 1 < + /// θ < ∞. If is equal to , less than -1, or greater than 1, + /// is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Atanh(this double value) + { + return System.Math.Atanh(value); + } + + /// + /// Returns the complex square root of this double-precision floating-point number. + /// + /// The number whose square root is to be found. + /// The square root of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Complex ComplexSqrt(this double value) + { + switch (value) + { + case double.PositiveInfinity: + case double.NegativeInfinity: + return Complex.Infinity; + case double.NaN: + return Complex.NaN; + + case 0: + return Complex.Zero; + case > 0: + return new Complex(System.Math.Sqrt(value), 0); + case < 0: + return new Complex(0, System.Math.Sqrt(-value)); + } + } + + /// + /// Returns the cosine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The cosine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Cos(this double value) + { + return System.Math.Cos(value); + } + + /// + /// Returns the hyperbolic cosine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic cosine of . If is equal to + /// or , + /// is returned. If is equal to + /// , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Cosh(this double value) + { + return System.Math.Cosh(value); + } + + /// + /// Converts the current angle in degrees to its equivalent represented in radians. + /// + /// The angle in degrees to convert. + /// The result of π * / 180. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double DegreesToRadians(this double value) + { + return value * (System.Math.PI / 180.0); + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this double value) + { + return System.Math.Abs(value % 2.0) < double.Epsilon; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this double value) + { + return !value.IsEven(); + } + + /// + /// Converts the current angle in radians to its equivalent represented in degrees. + /// + /// The angle in radians to convert. + /// The result of π * / 180. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double RadiansToDegrees(this double value) + { + return value * (180.0 / System.Math.PI); + } + + /// + /// Rounds the current value to the nearest whole number. + /// + /// The value to round. + /// rounded to the nearest whole number. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Round(this double value) + { + return value.Round(1.0); + } + + /// + /// Rounds the current value to the nearest multiple of a specified number. + /// + /// The value to round. + /// The nearest multiple to which should be rounded. + /// rounded to the nearest multiple of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Round(this double value, double nearest) + { + return System.Math.Round(value / nearest) * nearest; + } + + /// + /// Returns the sine of the specified angle. + /// + /// The angle, in radians. + /// + /// The sine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Sin(this double value) + { + return System.Math.Sin(value); + } + + /// + /// Returns the hyperbolic sine of the specified angle. + /// + /// The angle, in radians. + /// + /// The hyperbolic sine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Sinh(this double value) + { + return System.Math.Sinh(value); + } + + /// + /// Returns an integer that indicates the sign of this double-precision floating-point number. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + /// is equal to . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this double value) + { + return System.Math.Sign(value); + } + + /// + /// Returns the square root of this double-precision floating-point number. + /// + /// The number whose square root is to be found. + /// + /// One of the values in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// The positive square root of . + /// is greater than or equal to 0. + /// + /// + /// + /// is equal to or is negative. + /// + /// + /// + /// is equal to . + /// + /// + /// + /// + /// For negative input, this method returns . To receive a complex number, see + /// . + /// + /// + /// SLenik https://stackoverflow.com/a/6755197/1467293 + /// CC BY-SA 3.0 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Sqrt(this double value) + { + switch (value) + { + case 0: + return 0; + case < 0 or double.NaN: + return double.NaN; + case double.PositiveInfinity: + return double.PositiveInfinity; + } + + double previous; + double current = System.Math.Sqrt(value); + do + { + previous = current; + if (previous == 0.0) + { + return 0; + } + + current = (previous + value / previous) / 2; + } while (System.Math.Abs(previous - current) > double.Epsilon); + + return current; + } + + /// + /// Returns the tangent of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The tangent of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Tan(this double value) + { + return System.Math.Tan(value); + } + + /// + /// Returns the hyperbolic tangent of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic tangent of . If is equal to + /// , this method returns -1. If is equal to + /// , this method returns 1. If is equal to + /// , this method returns . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Tanh(this double value) + { + return System.Math.Tanh(value); + } +} diff --git a/X10D/src/Math/InclusiveOptions.cs b/X10D/src/Math/InclusiveOptions.cs new file mode 100644 index 0000000..1c78197 --- /dev/null +++ b/X10D/src/Math/InclusiveOptions.cs @@ -0,0 +1,28 @@ +namespace X10D.Math; + +/// +/// Provides options for clusivity. +/// +[Flags] +public enum InclusiveOptions : byte +{ + /// + /// Indicates that the comparison will be exclusive. + /// + None = 0, + + /// + /// Indicates that the comparison will treat the upper bound as exclusive. + /// + UpperInclusive = 1, + + /// + /// Indicates that the comparison will treat the lower bound as exclusive. + /// + LowerInclusive = 1 << 1, + + /// + /// Indicates that the comparison will treat both the upper and lower bound as exclusive. + /// + Inclusive = UpperInclusive | LowerInclusive +} diff --git a/X10D/src/Math/Int16Extensions.cs b/X10D/src/Math/Int16Extensions.cs new file mode 100644 index 0000000..731c285 --- /dev/null +++ b/X10D/src/Math/Int16Extensions.cs @@ -0,0 +1,171 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Computes the digital root of this 16-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short DigitalRoot(this short value) + { + short root = System.Math.Abs(value).Mod(9); + return root < 1 ? (short)(9 - root) : root; + } + + /// + /// Returns the factorial of the current 16-bit signed integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this short value) + { + if (value < 0) + { + throw new ArithmeticException(nameof(value)); + } + + if (value == 0) + { + return 1; + } + + var result = 1L; + for (short i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this short value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this short value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this short value) + { + return ((long)value).IsPrime(); + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short Mod(this short dividend, short divisor) + { + int r = dividend % divisor; + return (short)(r < 0 ? r + divisor : r); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this short value) + { + return ((long)value).MultiplicativePersistence(); + } + + /// + /// Returns an integer that indicates the sign of this 16-bit signed integer. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this short value) + { + return System.Math.Sign(value); + } +} diff --git a/X10D/src/Math/Int32Extensions.cs b/X10D/src/Math/Int32Extensions.cs new file mode 100644 index 0000000..faaeb5d --- /dev/null +++ b/X10D/src/Math/Int32Extensions.cs @@ -0,0 +1,171 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Computes the digital root of this 32-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int DigitalRoot(this int value) + { + int root = System.Math.Abs(value).Mod(9); + return root < 1 ? 9 - root : root; + } + + /// + /// Returns the factorial of the current 32-bit signed integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this int value) + { + if (value < 0) + { + throw new ArithmeticException(nameof(value)); + } + + if (value == 0) + { + return 1; + } + + var result = 1L; + for (var i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this int value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this int value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this int value) + { + return ((long)value).IsPrime(); + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Mod(this int dividend, int divisor) + { + int r = dividend % divisor; + return r < 0 ? r + divisor : r; + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this int value) + { + return ((long)value).MultiplicativePersistence(); + } + + /// + /// Returns an integer that indicates the sign of this 32-bit signed integer. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this int value) + { + return System.Math.Sign(value); + } +} diff --git a/X10D/src/Math/Int64Extensions.cs b/X10D/src/Math/Int64Extensions.cs new file mode 100644 index 0000000..48d5ed3 --- /dev/null +++ b/X10D/src/Math/Int64Extensions.cs @@ -0,0 +1,227 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Computes the digital root of this 64-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long DigitalRoot(this long value) + { + long root = System.Math.Abs(value).Mod(9L); + return root < 1L ? 9L - root : root; + } + + /// + /// Returns the factorial of the current 64-bit signed integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this long value) + { + if (value < 0) + { + throw new ArithmeticException(nameof(value)); + } + + if (value == 0) + { + return 1; + } + + var result = 1L; + for (var i = 1L; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this long value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this long value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this long value) + { + switch (value) + { + case < 2: return false; + case 2: + case 3: return true; + } + + if (value % 2 == 0 || value % 3 == 0) + { + return false; + } + + if ((value + 1) % 6 != 0 && (value - 1) % 6 != 0) + { + return false; + } + + for (var iterator = 5L; iterator * iterator <= value; iterator += 6) + { + if (value % iterator == 0 || value % (iterator + 2) == 0) + { + return false; + } + } + + return true; + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Mod(this long dividend, long divisor) + { + long r = dividend % divisor; + return r < 0 ? r + divisor : r; + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this long value) + { + var persistence = 0; + long product = System.Math.Abs(value); + + while (product > 9) + { + if (value % 10 == 0) + { + return persistence + 1; + } + + while (value > 9) + { + value /= 10; + if (value % 10 == 0) + { + return persistence + 1; + } + } + + long newProduct = 1; + long currentProduct = product; + while (currentProduct > 0) + { + newProduct *= currentProduct % 10; + currentProduct /= 10; + } + + product = newProduct; + persistence++; + } + + return persistence; + } + + /// + /// Returns an integer that indicates the sign of this 64-bit signed integer. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this long value) + { + return System.Math.Sign(value); + } +} diff --git a/X10D/src/Math/MathUtility.cs b/X10D/src/Math/MathUtility.cs new file mode 100644 index 0000000..a7aa41c --- /dev/null +++ b/X10D/src/Math/MathUtility.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Provides static helpers methods for mathematical functions not found in the .NET class. +/// +public static class MathUtility +{ + /// + /// Linearly interpolates from one value to a target using a specified alpha. + /// + /// The interpolation source. + /// The interpolation target. + /// The interpolation alpha. + /// + /// The interpolation result as determined by (1 - alpha) * value + alpha * target. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Lerp(float value, float target, float alpha) + { + // rookie mistake: a + t * (b - a) + // "precise" method: (1 - t) * a + t * b + return ((1.0f - alpha) * value) + (alpha * target); + } + + /// + /// Linearly interpolates from one value to a target using a specified alpha. + /// + /// The interpolation source. + /// The interpolation target. + /// The interpolation alpha. + /// + /// The interpolation result as determined by (1 - alpha) * value + alpha * target. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Lerp(double value, double target, double alpha) + { + // rookie mistake: a + t * (b - a) + // "precise" method: (1 - t) * a + t * b + return ((1.0 - alpha) * value) + (alpha * target); + } +} diff --git a/X10D/src/Math/SByteExtensions.cs b/X10D/src/Math/SByteExtensions.cs new file mode 100644 index 0000000..8e640a0 --- /dev/null +++ b/X10D/src/Math/SByteExtensions.cs @@ -0,0 +1,172 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Math-related extension methods for . +/// +[CLSCompliant(false)] +public static class SByteExtensions +{ + /// + /// Computes the digital root of this 32-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static sbyte DigitalRoot(this sbyte value) + { + int root = System.Math.Abs(value).Mod(9); + return (sbyte)(root < 1 ? 9 - root : root); + } + + /// + /// Returns the factorial of the current 8-bit signed integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this sbyte value) + { + if (value < 0) + { + throw new ArithmeticException(nameof(value)); + } + + if (value == 0) + { + return 1; + } + + var result = 1L; + for (ushort i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this sbyte value) + { + return value % 2 == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this sbyte value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this sbyte value) + { + return ((long)value).IsPrime(); + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static sbyte Mod(this sbyte dividend, sbyte divisor) + { + int r = dividend % divisor; + return (sbyte)(r < 0 ? r + divisor : r); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this sbyte value) + { + return ((long)value).MultiplicativePersistence(); + } + + /// + /// Returns an integer that indicates the sign of this 8-bit signed integer. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this sbyte value) + { + return System.Math.Sign(value); + } +} diff --git a/X10D/src/Math/SingleExtensions.cs b/X10D/src/Math/SingleExtensions.cs new file mode 100644 index 0000000..df6b8b8 --- /dev/null +++ b/X10D/src/Math/SingleExtensions.cs @@ -0,0 +1,419 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class SingleExtensions +{ + /// + /// Returns the arccosine of the specified value. + /// + /// + /// The value representing a cosine, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arccosine of , θ, measured in radians; such that 0 ≤ θ ≤ π. If + /// is equal to , less than -1, or greater than 1, is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Acos(this float value) + { + return MathF.Acos(value); + } + + /// + /// Returns the hyperbolic arccosine of the specified value. + /// + /// + /// The value representing a hyperbolic cosine, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arccosine of , θ, measured in radians; such that 0 ≤ θ ≤ ∞. If + /// is less than 1 or equal to , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Acosh(this float value) + { + return MathF.Acosh(value); + } + + /// + /// Returns the arcsine of the specified value. + /// + /// + /// The value representing a sine, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arccosine of , θ, measured in radians; such that π/2 ≤ θ ≤ π/2. If + /// is equal to , less than -1, or greater than 1, + /// is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Asin(this float value) + { + return MathF.Asin(value); + } + + /// + /// Returns the hyperbolic arcsine of the specified value. + /// + /// + /// The value representing a hyperbolic sine, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arccosine of , measured in radians. If is equal to + /// , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Asinh(this float value) + { + return MathF.Asinh(value); + } + + /// + /// Returns the arctangent of the specified value. + /// + /// + /// The value representing a tangent, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arctangent of , θ, measured in radians; such that π/2 ≤ θ ≤ π/2. If + /// is equal to , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Atan(this float value) + { + return MathF.Atan(value); + } + + /// + /// Returns the hyperbolic arctangent of the specified value. + /// + /// + /// The value representing a hyperbolic tangent, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arctangent of , θ, measured in radians; such that -∞ < θ < -1, or 1 < + /// θ < ∞. If is equal to , less than -1, or greater than 1, + /// is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Atanh(this float value) + { + return MathF.Atanh(value); + } + + /// + /// Returns the complex square root of this single-precision floating-point number. + /// + /// The number whose square root is to be found. + /// The square root of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Complex ComplexSqrt(this float value) + { + switch (value) + { + case float.PositiveInfinity: + case float.NegativeInfinity: + return Complex.Infinity; + case float.NaN: + return Complex.NaN; + + case 0: + return Complex.Zero; + case > 0: + return new Complex(MathF.Sqrt(value), 0); + case < 0: + return new Complex(0, MathF.Sqrt(-value)); + } + } + + /// + /// Returns the cosine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The cosine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Cos(this float value) + { + return MathF.Cos(value); + } + + /// + /// Returns the hyperbolic cosine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic cosine of . If is equal to + /// or , + /// is returned. If is equal to + /// , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Cosh(this float value) + { + return MathF.Cosh(value); + } + + /// + /// Converts the current angle in degrees to its equivalent represented in radians. + /// + /// The angle in degrees to convert. + /// The result of π * / 180. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float DegreesToRadians(this float value) + { + return value * (MathF.PI / 180.0f); + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this float value) + { + return value % 2 == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this float value) + { + return !value.IsEven(); + } + + /// + /// Converts the current angle in radians to its equivalent represented in degrees. + /// + /// The angle in radians to convert. + /// The result of π * / 180. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float RadiansToDegrees(this float value) + { + return value * (180.0f / MathF.PI); + } + + /// + /// Rounds the current value to the nearest whole number. + /// + /// The value to round. + /// rounded to the nearest whole number. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Round(this float value) + { + return value.Round(1.0f); + } + + /// + /// Rounds the current value to the nearest multiple of a specified number. + /// + /// The value to round. + /// The nearest multiple to which should be rounded. + /// rounded to the nearest multiple of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Round(this float value, float nearest) + { + return MathF.Round(value / nearest) * nearest; + } + + /// + /// Returns an integer that indicates the sign of this single-precision floating-point number. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this float value) + { + return MathF.Sign(value); + } + + /// + /// Returns the square root of this single-precision floating-point number. + /// + /// The number whose square root is to be found. + /// + /// One of the values in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// The positive square root of . + /// is greater than or equal to 0. + /// + /// + /// + /// is equal to or is negative. + /// + /// + /// + /// is equal to . + /// + /// + /// + /// + /// For negative input, this method returns . To receive a complex number, see + /// . + /// + /// + /// SLenik https://stackoverflow.com/a/6755197/1467293 + /// CC BY-SA 3.0 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Sqrt(this float value) + { + switch (value) + { + case 0: + return 0; + case < 0 or float.NaN: + return float.NaN; + case float.PositiveInfinity: + return float.PositiveInfinity; + } + + float previous; + float current = MathF.Sqrt(value); + do + { + previous = current; + if (previous == 0.0f) + { + return 0; + } + + current = (previous + value / previous) / 2; + } while (MathF.Abs(previous - current) > float.Epsilon); + + return current; + } + + /// + /// Returns the sine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The sine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Sin(this float value) + { + return MathF.Sin(value); + } + + /// + /// Returns the hyperbolic sine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic sine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Sinh(this float value) + { + return MathF.Sinh(value); + } + + /// + /// Returns the tangent of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The tangent of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Tan(this float value) + { + return MathF.Sin(value); + } + + /// + /// Returns the hyperbolic tangent of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic tangent of . If is equal to + /// , this method returns -1. If is equal to + /// , this method returns 1. If is equal to + /// , this method returns . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Tanh(this float value) + { + return MathF.Tanh(value); + } +} diff --git a/X10D/src/Math/UInt16Extensions.cs b/X10D/src/Math/UInt16Extensions.cs new file mode 100644 index 0000000..e781942 --- /dev/null +++ b/X10D/src/Math/UInt16Extensions.cs @@ -0,0 +1,110 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +[CLSCompliant(false)] +public static class UInt16Extensions +{ + /// + /// Computes the digital root of the current 16-bit unsigned integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ushort DigitalRoot(this ushort value) + { + var root = (ushort)(value % 9); + return (ushort)(root == 0 ? 9 : root); + } + + /// + /// Returns the factorial of the current 16-bit unsigned integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong Factorial(this ushort value) + { + if (value == 0) + { + return 1; + } + + var result = 1UL; + for (ushort i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this ushort value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this ushort value) + { + return ((ulong)value).IsPrime(); + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this ushort value) + { + return !value.IsEven(); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this ushort value) + { + return ((ulong)value).MultiplicativePersistence(); + } +} diff --git a/X10D/src/Math/UInt32Extensions.cs b/X10D/src/Math/UInt32Extensions.cs new file mode 100644 index 0000000..65217a4 --- /dev/null +++ b/X10D/src/Math/UInt32Extensions.cs @@ -0,0 +1,110 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +[CLSCompliant(false)] +public static class UInt32Extensions +{ + /// + /// Computes the digital root of the current 32-bit unsigned integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static uint DigitalRoot(this uint value) + { + uint root = value % 9; + return root == 0 ? 9 : root; + } + + /// + /// Returns the factorial of the current 32-bit unsigned integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong Factorial(this uint value) + { + if (value == 0) + { + return 1; + } + + var result = 1UL; + for (var i = 1U; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this uint value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this uint value) + { + return ((ulong)value).IsPrime(); + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this uint value) + { + return !value.IsEven(); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this uint value) + { + return ((ulong)value).MultiplicativePersistence(); + } +} diff --git a/X10D/src/Math/UInt64Extensions.cs b/X10D/src/Math/UInt64Extensions.cs new file mode 100644 index 0000000..08f88ba --- /dev/null +++ b/X10D/src/Math/UInt64Extensions.cs @@ -0,0 +1,166 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +[CLSCompliant(false)] +public static class UInt64Extensions +{ + /// + /// Computes the digital root of the current 64-bit unsigned integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong DigitalRoot(this ulong value) + { + ulong root = value % 9; + return root == 0 ? 9 : root; + } + + /// + /// Returns the factorial of the current 64-bit unsigned integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong Factorial(this ulong value) + { + if (value == 0) + { + return 1; + } + + var result = 1UL; + for (var i = 1UL; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this ulong value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this ulong value) + { + switch (value) + { + case < 2: return false; + case 2: + case 3: return true; + } + + if (value % 2 == 0 || value % 3 == 0) + { + return false; + } + + if ((value + 1) % 6 != 0 && (value - 1) % 6 != 0) + { + return false; + } + + for (var iterator = 5UL; iterator * iterator <= value; iterator += 6) + { + if (value % iterator == 0 || value % (iterator + 2) == 0) + { + return false; + } + } + + return true; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this ulong value) + { + return !value.IsEven(); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this ulong value) + { + var persistence = 0; + ulong product = value; + + while (product > 9) + { + if (value % 10 == 0) + { + return persistence + 1; + } + + while (value > 9) + { + value /= 10; + if (value % 10 == 0) + { + return persistence + 1; + } + } + + ulong newProduct = 1; + ulong currentProduct = product; + while (currentProduct > 0) + { + newProduct *= currentProduct % 10; + currentProduct /= 10; + } + + product = newProduct; + persistence++; + } + + return persistence; + } +} diff --git a/X10D/src/Net/EndPointExtensions.cs b/X10D/src/Net/EndPointExtensions.cs new file mode 100644 index 0000000..b7ef2d7 --- /dev/null +++ b/X10D/src/Net/EndPointExtensions.cs @@ -0,0 +1,69 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// Extension methods for and derived types. +/// +public static class EndPointExtensions +{ + /// + /// Returns the hostname for the current . + /// + /// The endpoint whose hostname to get. + /// + /// if is . + /// -or- + /// if is . + /// -or- + /// otherwise. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string GetHost(this EndPoint endPoint) + { + if (endPoint is null) + { + throw new ArgumentNullException(nameof(endPoint)); + } + + return endPoint switch + { + IPEndPoint ip => ip.Address.ToString(), + DnsEndPoint dns => dns.Host, + _ => string.Empty + }; + } + + /// + /// Returns the port number for the current . + /// + /// The endpoint whose port number to get. + /// + /// if is . + /// -or- + /// if is . + /// -or- + /// 0 otherwise. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int GetPort(this EndPoint endPoint) + { + if (endPoint is null) + { + throw new ArgumentNullException(nameof(endPoint)); + } + + return endPoint switch + { + IPEndPoint ip => ip.Port, + DnsEndPoint dns => dns.Port, + _ => 0 + }; + } +} diff --git a/X10D/src/Net/IPAddressExtensions.cs b/X10D/src/Net/IPAddressExtensions.cs new file mode 100644 index 0000000..0b8358d --- /dev/null +++ b/X10D/src/Net/IPAddressExtensions.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// Extension methods for . +/// +public static class IPAddressExtensions +{ + /// + /// Returns a value indicating whether the specified IP address is a valid IPv4 address. + /// + /// The IP address to check. + /// + /// if the specified IP address is a valid IPv4 address; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsIPv4(this IPAddress address) + { + return address.AddressFamily == AddressFamily.InterNetwork; + } + + /// + /// Returns a value indicating whether the specified IP address is a valid IPv6 address. + /// + /// The IP address to check. + /// + /// if the specified IP address is a valid IPv6 address; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsIPv6(this IPAddress address) + { + return address.AddressFamily == AddressFamily.InterNetworkV6; + } +} diff --git a/X10D/src/Net/Int16Extensions.cs b/X10D/src/Net/Int16Extensions.cs new file mode 100644 index 0000000..60b648b --- /dev/null +++ b/X10D/src/Net/Int16Extensions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// Network-related extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Converts a 16-bit signed integer value from host byte order to network byte order. + /// + /// The value to convert, expressed in host byte order. + /// An integer value, expressed in network byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short HostToNetworkOrder(this short value) + { + return IPAddress.HostToNetworkOrder(value); + } + + /// + /// Converts a 16-bit signed integer value from network byte order to host byte order. + /// + /// The value to convert, expressed in network byte order. + /// An integer value, expressed in host byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short NetworkToHostOrder(this short value) + { + return IPAddress.NetworkToHostOrder(value); + } +} diff --git a/X10D/src/Net/Int32Extensions.cs b/X10D/src/Net/Int32Extensions.cs new file mode 100644 index 0000000..7c7acbe --- /dev/null +++ b/X10D/src/Net/Int32Extensions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// IO-related extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Converts a 32-bit signed integer value from host byte order to network byte order. + /// + /// The value to convert, expressed in host byte order. + /// An integer value, expressed in network byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int HostToNetworkOrder(this int value) + { + return IPAddress.HostToNetworkOrder(value); + } + + /// + /// Converts a 32-bit signed integer value from network byte order to host byte order. + /// + /// The value to convert, expressed in network byte order. + /// An integer value, expressed in host byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int NetworkToHostOrder(this int value) + { + return IPAddress.NetworkToHostOrder(value); + } +} diff --git a/X10D/src/Net/Int64Extensions.cs b/X10D/src/Net/Int64Extensions.cs new file mode 100644 index 0000000..a586ca8 --- /dev/null +++ b/X10D/src/Net/Int64Extensions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// IO-related extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Converts a 64-bit signed integer value from host byte order to network byte order. + /// + /// The value to convert, expressed in host byte order. + /// An integer value, expressed in network byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long HostToNetworkOrder(this long value) + { + return IPAddress.HostToNetworkOrder(value); + } + + /// + /// Converts a 64-bit signed integer value from network byte order to host byte order. + /// + /// The value to convert, expressed in network byte order. + /// An integer value, expressed in host byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long NetworkToHostOrder(this long value) + { + return IPAddress.NetworkToHostOrder(value); + } +} diff --git a/X10D/src/Numerics/ByteExtensions.cs b/X10D/src/Numerics/ByteExtensions.cs new file mode 100644 index 0000000..ccd97a3 --- /dev/null +++ b/X10D/src/Numerics/ByteExtensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +public static class ByteExtensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..7] is treated as congruent mod 8. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte RotateLeft(this byte value, int count) + { + count = count.Mod(8); + return (byte)((value << count) | (value >> (8 - count))); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..7] is treated as congruent mod 8. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte RotateRight(this byte value, int count) + { + count = count.Mod(8); + return (byte)((value >> count) | (value << (8 - count))); + } +} diff --git a/X10D/src/Numerics/Int16Extensions.cs b/X10D/src/Numerics/Int16Extensions.cs new file mode 100644 index 0000000..789eeb5 --- /dev/null +++ b/X10D/src/Numerics/Int16Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..15] is treated as congruent mod 16. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short RotateLeft(this short value, int count) + { + var unsigned = unchecked((ushort)value); + return unchecked((short)unsigned.RotateLeft(count)); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..15] is treated as congruent mod 16. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short RotateRight(this short value, int count) + { + var unsigned = unchecked((ushort)value); + return unchecked((short)unsigned.RotateRight(count)); + } +} diff --git a/X10D/src/Numerics/Int32Extensions.cs b/X10D/src/Numerics/Int32Extensions.cs new file mode 100644 index 0000000..75dfea8 --- /dev/null +++ b/X10D/src/Numerics/Int32Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..31] is treated as congruent mod 32. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int RotateLeft(this int value, int count) + { + var unsigned = unchecked((uint)value); + return unchecked((int)unsigned.RotateLeft(count)); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..31] is treated as congruent mod 32. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int RotateRight(this int value, int count) + { + var unsigned = unchecked((uint)value); + return unchecked((int)unsigned.RotateRight(count)); + } +} diff --git a/X10D/src/Numerics/Int64Extensions.cs b/X10D/src/Numerics/Int64Extensions.cs new file mode 100644 index 0000000..5f25a59 --- /dev/null +++ b/X10D/src/Numerics/Int64Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..63] is treated as congruent mod 64. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long RotateLeft(this long value, int count) + { + var unsigned = unchecked((ulong)value); + return unchecked((long)unsigned.RotateLeft(count)); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..63] is treated as congruent mod 64. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long RotateRight(this long value, int count) + { + var unsigned = unchecked((ulong)value); + return unchecked((long)unsigned.RotateRight(count)); + } +} diff --git a/X10D/src/Numerics/RandomExtensions.cs b/X10D/src/Numerics/RandomExtensions.cs new file mode 100644 index 0000000..b89a5e2 --- /dev/null +++ b/X10D/src/Numerics/RandomExtensions.cs @@ -0,0 +1,117 @@ +using System.Numerics; +using X10D.Core; + +namespace X10D.Numerics; + +/// +/// Extension methods for . +/// +public static class RandomExtensions +{ + /// + /// Returns a randomly generated rotation as represented by a . + /// + /// The instance. + /// + /// A constructed from 3 random single-precision floating point numbers representing the + /// yaw, pitch, and roll. + /// + /// is . + public static Quaternion NextRotation(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int seed = random.Next(); + var seededRandom = new Random(seed); + + float x = seededRandom.NextSingle(0, 360); + float y = seededRandom.NextSingle(0, 360); + float z = seededRandom.NextSingle(0, 360); + + return Quaternion.CreateFromYawPitchRoll(y, x, z); + } + + /// + /// Returns a randomly generated rotation with uniform distribution. + /// + /// The instance. + /// A constructed with uniform distribution. + /// is . + public static Quaternion NextRotationUniform(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int seed = random.Next(); + var seededRandom = new Random(seed); + float normal, w, x, y, z; + + do + { + w = seededRandom.NextSingle(-1f, 1f); + x = seededRandom.NextSingle(-1f, 1f); + y = seededRandom.NextSingle(-1f, 1f); + z = seededRandom.NextSingle(-1f, 1f); + normal = (w * w) + (x * x) + (y * y) + (z * z); + } while (normal is 0f or > 1f); + + normal = MathF.Sqrt(normal); + return new Quaternion(x / normal, y / normal, z / normal, w / normal); + } + + /// + /// Returns a with magnitude 1 whose components indicate a random point on the unit circle. + /// + /// The instance + /// + /// A whose returns 1, and whose components indicate a random + /// point on the unit circle. + /// + public static Vector2 NextUnitVector2(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + // no need to construct a seeded random here, since we only call Next once + + float angle = random.NextSingle(0, MathF.PI * 2.0f); + float x = MathF.Cos(angle); + float y = MathF.Sin(angle); + + return new Vector2(x, y); + } + + /// + /// Returns a with magnitude 1 whose components indicate a random point on the unit sphere. + /// + /// The instance + /// + /// A whose returns 1, and whose components indicate a random + /// point on the unit sphere. + /// + public static Vector3 NextUnitVector3(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int seed = random.Next(); + var seededRandom = new Random(seed); + + float angle = seededRandom.NextSingle(0, MathF.PI * 2.0f); + float z = seededRandom.NextSingle(-1, 1); + float mp = MathF.Sqrt(1 - (z * z)); + float x = mp * MathF.Cos(angle); + float y = mp * MathF.Sin(angle); + + return new Vector3(x, y, z); + } +} diff --git a/X10D/src/Numerics/SByteExtensions.cs b/X10D/src/Numerics/SByteExtensions.cs new file mode 100644 index 0000000..e3d9d24 --- /dev/null +++ b/X10D/src/Numerics/SByteExtensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +[CLSCompliant(false)] +public static class SByteExtensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..7] is treated as congruent mod 8. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static sbyte RotateLeft(this sbyte value, int count) + { + var signed = unchecked((byte)value); + return unchecked((sbyte)signed.RotateLeft(count)); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..7] is treated as congruent mod 8. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static sbyte RotateRight(this sbyte value, int count) + { + var signed = unchecked((byte)value); + return unchecked((sbyte)signed.RotateRight(count)); + } +} diff --git a/X10D/src/Numerics/UInt16Extensions.cs b/X10D/src/Numerics/UInt16Extensions.cs new file mode 100644 index 0000000..4798deb --- /dev/null +++ b/X10D/src/Numerics/UInt16Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt16Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..15] is treated as congruent mod 16. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ushort RotateLeft(this ushort value, int count) + { + return (ushort)((ushort)(value << count) | (ushort)(value >> (16 - count))); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..15] is treated as congruent mod 16. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ushort RotateRight(this ushort value, int count) + { + return (ushort)((ushort)(value >> count) | (ushort)(value << (16 - count))); + } +} diff --git a/X10D/src/Numerics/UInt32Extensions.cs b/X10D/src/Numerics/UInt32Extensions.cs new file mode 100644 index 0000000..d5b619c --- /dev/null +++ b/X10D/src/Numerics/UInt32Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt32Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..31] is treated as congruent mod 32. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static uint RotateLeft(this uint value, int count) + { + return BitOperations.RotateLeft(value, count); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..31] is treated as congruent mod 32. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static uint RotateRight(this uint value, int count) + { + return BitOperations.RotateRight(value, count); + } +} diff --git a/X10D/src/Numerics/UInt64Extensions.cs b/X10D/src/Numerics/UInt64Extensions.cs new file mode 100644 index 0000000..709ca93 --- /dev/null +++ b/X10D/src/Numerics/UInt64Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt64Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..63] is treated as congruent mod 64. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong RotateLeft(this ulong value, int count) + { + return BitOperations.RotateLeft(value, count); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..63] is treated as congruent mod 64. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong RotateRight(this ulong value, int count) + { + return BitOperations.RotateRight(value, count); + } +} diff --git a/X10D/src/RandomExtensions.cs b/X10D/src/RandomExtensions.cs deleted file mode 100644 index 0008338..0000000 --- a/X10D/src/RandomExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Extension methods for . - /// - public static class RandomExtensions - { - /// - /// Gets the instance to which other extension methods may refer, when one is - /// needed but not provided. - /// - internal static Random Random { get; } = new Random(); - - /// - /// Returns either or based on 's next - /// generation. - /// - /// The instance. - /// - /// Returns or depending on the return value - /// from . - /// - /// is . - public static bool CoinToss(this Random random) - { - if (random is null) - { - throw new ArgumentNullException(nameof(random)); - } - - return random.Next(2) == 0; - } - - /// - /// Returns a random element from using the instance. - /// - /// The collection type. - /// The instance. - /// The collection from which to draw. - /// Returns a random element of type from . - public static T OneOf(this Random random, params T[] source) - { - return source.ToList().OneOf(random); - } - - /// - /// Returns a random element from using the instance. - /// - /// The collection type. - /// The instance. - /// The collection from which to draw. - /// Returns a random element of type from . - /// - /// or is - /// . - /// - public static T OneOf(this Random random, IList source) - { - if (random is null) - { - throw new ArgumentNullException(nameof(random)); - } - - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - return source[random.Next(source.Count)]; - } - } -} diff --git a/X10D/src/Reflection/MemberInfoExtensions.cs b/X10D/src/Reflection/MemberInfoExtensions.cs new file mode 100644 index 0000000..9fd389e --- /dev/null +++ b/X10D/src/Reflection/MemberInfoExtensions.cs @@ -0,0 +1,125 @@ +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace X10D.Reflection; + +/// +/// Extension methods for . +/// +public static class MemberInfoExtensions +{ + /// + /// Returns a value indicating whether or not the current member has been decorated with a specified attribute. + /// + /// The member attributes to check. + /// The attribute type. + /// + /// if the current member has been decorated with a specified attribute, or + /// otherwise. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool HasCustomAttribute(this MemberInfo member) + where T : Attribute + { + if (member is null) + { + throw new ArgumentNullException(nameof(member)); + } + + return member.HasCustomAttribute(typeof(T)); + } + + /// + /// Returns a value indicating whether or not the current member has been decorated with a specified attribute. + /// + /// The member attributes to check. + /// The attribute type. + /// + /// if the current member has been decorated with a specified attribute, or + /// otherwise. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool HasCustomAttribute(this MemberInfo member, Type attribute) + { + if (member is null) + { + throw new ArgumentNullException(nameof(member)); + } + + if (attribute is null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + if (!attribute.Inherits()) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionMessages.TypeDoesNotInheritAttribute, + attribute, typeof(Attribute)), nameof(attribute)); + } + + return member.GetCustomAttribute(attribute) is not null; + } + + /// + /// Retrieves a custom attribute that is decorated by the current member, and projects it into to a new form. + /// + /// The attribute type. + /// The return type of the delegate. + /// The member. + /// A transform function to apply to the attribute. + /// + /// An instance of as provided from . + /// + /// + /// is + /// -or- + /// is . + /// + public static TReturn? SelectFromCustomAttribute(this MemberInfo member, + Func selector) + where TAttribute : Attribute + { + return member.SelectFromCustomAttribute(selector, default); + } + + /// + /// Retrieves a custom attribute that is decorated by the current member, and projects it into to a new form. + /// + /// The attribute type. + /// The return type of the delegate. + /// The member. + /// A transform function to apply to the attribute. + /// The default value to return when the specified attribute is not found. + /// + /// An instance of as provided from . + /// + /// + /// is + /// -or- + /// is . + /// + public static TReturn? SelectFromCustomAttribute(this MemberInfo member, + Func selector, TReturn? defaultValue) + where TAttribute : Attribute + { + if (member is null) + { + throw new ArgumentNullException(nameof(member)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return member.GetCustomAttribute() is { } attribute + ? selector(attribute) + : defaultValue; + } +} diff --git a/X10D/src/Reflection/TypeExtensions.cs b/X10D/src/Reflection/TypeExtensions.cs new file mode 100644 index 0000000..301d1d2 --- /dev/null +++ b/X10D/src/Reflection/TypeExtensions.cs @@ -0,0 +1,114 @@ +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace X10D.Reflection; + +/// +/// Extension methods for . +/// +public static class TypeExtensions +{ + /// + /// Returns a value indicating whether the current type implements a specified interface. + /// + /// The type whose interface list to check. + /// The interface type. + /// if the current exists on the type; otherwise, . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Implements(this Type value) + { + return value.Implements(typeof(T)); + } + + /// + /// Returns a value indicating whether the current type implements a specified interface. + /// + /// The type whose interface list to check. + /// The interface type. + /// if the current exists on the type; otherwise, . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Implements(this Type value, Type interfaceType) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (interfaceType is null) + { + throw new ArgumentNullException(nameof(interfaceType)); + } + + if (!interfaceType.IsInterface) + { + string? exceptionMessage = ExceptionMessages.TypeIsNotInterface; + string formattedMessage = string.Format(CultureInfo.CurrentCulture, exceptionMessage, interfaceType); + + throw new ArgumentException(formattedMessage, nameof(interfaceType)); + } + + return Array.IndexOf(value.GetInterfaces(), interfaceType) >= 0; + } + + /// + /// Returns a value indicating whether the current type inherits a specified type. + /// + /// The type whose interface list to check. + /// The base type. + /// + /// if the current type inherits , or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Inherits(this Type value) + where T : class + { + return value.Inherits(typeof(T)); + } + + /// + /// Returns a value indicating whether the current type inherits a specified type. + /// + /// The type whose interface list to check. + /// The base type. + /// + /// if the current type inherits , or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Inherits(this Type value, Type type) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!value.IsClass) + { + string? exceptionMessage = ExceptionMessages.TypeIsNotClass; + string formattedMessage = string.Format(CultureInfo.CurrentCulture, exceptionMessage, value); + + throw new ArgumentException(formattedMessage, nameof(value)); + } + + if (!type.IsClass) + { + string? exceptionMessage = ExceptionMessages.TypeIsNotClass; + string formattedMessage = string.Format(CultureInfo.CurrentCulture, exceptionMessage, type); + + throw new ArgumentException(formattedMessage, nameof(type)); + } + + return value.IsSubclassOf(type); + } +} diff --git a/X10D/src/ReflectionExtensions.cs b/X10D/src/ReflectionExtensions.cs deleted file mode 100644 index ff3cb23..0000000 --- a/X10D/src/ReflectionExtensions.cs +++ /dev/null @@ -1,123 +0,0 @@ -namespace X10D -{ - using System; - using System.ComponentModel; - using System.Reflection; - - /// - /// Extension methods for various reflection types. - /// - public static class ReflectionExtensions - { - /// - /// Gets the value set in this member's annotated , or - /// if none exists. - /// - /// The member. - /// - /// Returns an representing the value stored in this member's - /// . - /// - /// is . - public static object GetDefaultValue(this MemberInfo member) - { - if (member is null) - { - throw new ArgumentNullException(nameof(member)); - } - - if (!(member.GetCustomAttribute() is { } attribute)) - { - return default; - } - - return attribute.Value; - } - - /// - /// Gets the value set in this member's annotated , or - /// if none exists. - /// - /// The type to which the value should cast. - /// The member. - /// - /// Returns an instance of representing the value stored in this member's - /// . - /// - /// is . - public static T GetDefaultValue(this MemberInfo member) - { - if (member is null) - { - throw new ArgumentNullException(nameof(member)); - } - - return (T)member.GetDefaultValue(); - } - - /// - /// Gets the value set in this member's annotated , or - /// if none exists. - /// - /// The member. - /// - /// Returns a string representing the value stored in this member's - /// . - /// - /// is . - public static string GetDescription(this MemberInfo member) - { - if (member is null) - { - throw new ArgumentNullException(nameof(member)); - } - - if (!(member.GetCustomAttribute() is { } attribute)) - { - return null; - } - - return attribute.Description; - } - - /// - /// Retrieves a custom attribute of a specified type that is applied to the specified member, and passes it - /// to a selector delegate in order to select one or more the members in the attribute. - /// - /// The attribute type. - /// The return type of the delegate. - /// The member. - /// The selector delegate. - /// - /// Returns an instance of as provided from - /// . - /// - /// - /// is - /// -or- - /// is . - /// - public static TReturn SelectFromCustomAttribute( - this MemberInfo member, - Func selector) - where TAttribute : Attribute - { - if (member is null) - { - throw new ArgumentNullException(nameof(member)); - } - - if (selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } - - if (!(member.GetCustomAttribute() is { } attribute)) - { - return default; - } - - return selector(attribute); - } - } -} diff --git a/X10D/src/SingleExtensions.cs b/X10D/src/SingleExtensions.cs deleted file mode 100644 index 53f6925..0000000 --- a/X10D/src/SingleExtensions.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class SingleExtensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static float Clamp(this float value, float min, float max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts an angle from degrees to radians. - /// - /// The angle in degrees. - /// Returns in radians. - public static float DegreesToRadians(this float angle) - { - return (float)((double)angle).DegreesToRadians(); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this float number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this float number) - { - return ((double)number).IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this float number) - { - return !number.IsEven(); - } - - /// - /// Converts an angle from radians to degrees. - /// - /// The angle in radians. - /// Returns in degrees. - public static float RadiansToDegrees(this float angle) - { - return (float)((double)angle).RadiansToDegrees(); - } - - /// - /// Rounds to the nearest value. - /// - /// The value to round. - /// The nearest value. - /// Returns the rounded value. - public static float Round(this float v, float nearest = 1) - { - return (float)((double)v).Round(nearest); - } - } -} diff --git a/X10D/src/StreamExtensions.cs b/X10D/src/StreamExtensions.cs deleted file mode 100644 index 521796b..0000000 --- a/X10D/src/StreamExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace X10D -{ - using System; - using System.IO; - using System.Security.Cryptography; - - /// - /// Extension methods for . - /// - public static class StreamExtensions - { - /// - /// Returns the hash of a stream using the specified hashing algorithm. - /// - /// A derived type. - /// The stream whose hash is to be computed. - /// Returns a array representing the hash of the stream. - /// is . - public static byte[] GetHash(this Stream stream) - where T : HashAlgorithm - { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - var create = typeof(T).GetMethod("Create", Array.Empty()); - using var crypt = (T)create?.Invoke(null, null); - return crypt?.ComputeHash(stream); - } - } -} diff --git a/X10D/src/StringExtensions.cs b/X10D/src/StringExtensions.cs deleted file mode 100644 index 93920a1..0000000 --- a/X10D/src/StringExtensions.cs +++ /dev/null @@ -1,409 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Security; - using System.Text; - - /// - /// Extension methods for . - /// - public static class StringExtensions - { - /// - /// Returns the current string, or if the current string is null or empty. - /// - /// The value to sanitize. - /// or . - public static string? AsNullIfEmpty(this string value) - { - return string.IsNullOrEmpty(value) ? null : value; - } - - /// - /// Returns the current string, or if the current string is null, empty, or consists of only - /// whitespace. - /// - /// The value to sanitize. - /// or . - public static string? AsNullIfWhiteSpace(this string value) - { - return string.IsNullOrWhiteSpace(value) ? null : value; - } - - /// - /// Decodes a base-64 encoded string. - /// - /// The base-64 string to decode. - /// Returns the string in plain text. - public static string Base64Decode(this string data) - { - return Convert.FromBase64String(data).GetString(); - } - - /// - /// Encodes a base-64 encoded string. - /// - /// The plain text string to decode. - /// Returns the string in plain text. - public static string Base64Encode(this string value) - { - return Convert.ToBase64String(value.GetBytes()); - } - - /// - /// Converts this string from one encoding to another. - /// - /// The input string. - /// The input encoding. - /// The output encoding. - /// - /// Returns a new with its data converted to - /// . - /// - /// - /// is - /// - or - - /// is - /// -or - /// is . - /// - public static string ChangeEncoding(this string str, Encoding from, Encoding to) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - if (from is null) - { - throw new ArgumentNullException(nameof(from)); - } - - if (to is null) - { - throw new ArgumentNullException(nameof(to)); - } - - return str.GetBytes(from).GetString(to); - } - - /// - /// Parses a into an . - /// - /// The type of the . - /// The value to parse. - /// The value corresponding to the . - /// - /// Credit for this method goes to Scott Dorman: - /// (http://geekswithblogs.net/sdorman/Default.aspx). - /// - public static T EnumParse(this string value) - { - return value.EnumParse(false); - } - - /// - /// Parses a into an . - /// - /// The type of the . - /// The value to parse. - /// Whether or not to ignore casing. - /// The value corresponding to the . - /// - /// Credit for this method goes to Scott Dorman: - /// (http://geekswithblogs.net/sdorman/Default.aspx). - /// - public static T EnumParse(this string value, bool ignoreCase) - { - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } - - value = value.Trim(); - - if (string.IsNullOrWhiteSpace(value)) - { - throw new ArgumentException(Resource.EnumParseEmptyStringException, nameof(value)); - } - - var t = typeof(T); - - if (!t.IsEnum) - { - throw new ArgumentException(Resource.EnumParseNotEnumException); - } - - return (T)Enum.Parse(t, value, ignoreCase); - } - - /// - /// Gets a [] representing the value the with - /// encoding. - /// - /// The string to convert. - /// Returns a []. - public static byte[] GetBytes(this string str) - { - return str.GetBytes(Encoding.UTF8); - } - - /// - /// Gets a [] representing the value the with the provided encoding. - /// - /// The string to convert. - /// The encoding to use. - /// Returns a []. - /// - /// or or both are - /// . - /// - public static byte[] GetBytes(this string str, Encoding encoding) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - if (encoding is null) - { - throw new ArgumentNullException(nameof(encoding)); - } - - return encoding.GetBytes(str); - } - - /// - /// Determines if all alpha characters in this string are considered lowercase. - /// - /// The input string. - /// - /// Returns if all alpha characters are lowercase, - /// otherwise. - /// - public static bool IsLower(this string str) - { - return str.Where(char.IsLetter).All(char.IsLower); - } - - /// - /// Determines if all alpha characters in this string are considered uppercase. - /// - /// The input string. - /// - /// Returns if all alpha characters are uppercase, - /// otherwise. - /// - public static bool IsUpper(this string str) - { - return str.Where(char.IsLetter).All(char.IsUpper); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// Returns a containing characters. - public static string Random(this string str, int length) - { - return str.Random(length, RandomExtensions.Random); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// The instance. - /// Returns a containing characters. - /// is . - public static string Random(this string str, int length, Random random) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return str.ToCharArray().Random(length, random); - } - - /// - /// Repeats a string a specified number of times. - /// - /// The string to repeat. - /// The repeat count. - /// - /// Returns a whose value is repeated - /// times. - /// - /// is . - public static string Repeat(this string str, int count) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - var builder = new StringBuilder(str.Length * count); - - for (var i = 0; i < count; i++) - { - builder.Append(str); - } - - return builder.ToString(); - } - - /// - /// Reverses the current string. - /// - /// The string to reverse. - /// A whose characters are that of in reverse order. - public static string Reverse(this string value) - { - return string.Join(string.Empty, Enumerable.Reverse(value)); - } - - /// - /// Shuffles the characters in the string. - /// - /// The string to shuffle. - /// Returns a containing the characters in , rearranged. - public static string Shuffle(this string str) - { - return str.Shuffle(RandomExtensions.Random); - } - - /// - /// Shuffles the characters in the string. - /// - /// The string to shuffle. - /// The instance. - /// Returns a containing the characters in , rearranged. - /// is . - public static string Shuffle(this string str, Random random) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return new string(str.ToCharArray().Shuffle(random).ToArray()); - } - - /// - /// Splits the into chunks that are no greater than in length. - /// - /// The string to split. - /// The maximum length of each string in the returned result. - /// - /// Returns an containing instances which are no - /// greater than in length. - /// - /// is . - public static IEnumerable Split(this string str, int chunkSize) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return SplitInternal(); - - IEnumerable SplitInternal() - { - for (var i = 0; i < str.Length; i += chunkSize) - { - yield return str.Substring(i, Math.Min(chunkSize, str.Length - i)); - } - } - } - - /// - /// Converts a to a . - /// - /// The string to convert. - /// Returns a . - /// is . - public static SecureString ToSecureString(this string str) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - if (string.IsNullOrWhiteSpace(str)) - { - return null; - } - - var result = new SecureString(); - foreach (var c in str) - { - result.AppendChar(c); - } - - return result; - } - - /// - /// Converts a to a . - /// - /// The to convert. - /// Whether or not to use this extension method. - /// Returns a . - /// is . - public static string ToString(this SecureString str, bool extension) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return extension ? new NetworkCredential(string.Empty, str).Password : str.ToString(); - } - - /// - /// Parses a shorthand time span string (e.g. 3w 2d 1.5h) and converts it to an instance of - /// . - /// - /// The input string. - /// Returns an instance of . - /// is . - public static TimeSpan ToTimeSpan(this string str) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return TimeSpanParser.Parse(str); - } - - /// - /// Returns the current string, or an alternative value if the current string is null or empty, or optionally if the - /// current string consists of only whitespace. - /// - /// The value to check. - /// The alternative value if does not meet the criteria. - /// - /// Optional. If set to , will be returned also if - /// only consists of whitespace. - /// - /// Returns or . - /// is . - public static string WithAlternative(this string? value, string alternative, bool includeWhiteSpace = false) - { - if (alternative is null) - { - throw new ArgumentNullException(nameof(alternative)); - } - - return (includeWhiteSpace ? value?.AsNullIfWhiteSpace() : value?.AsNullIfEmpty()) ?? alternative; - } - } -} diff --git a/X10D/src/StructExtensions.cs b/X10D/src/StructExtensions.cs deleted file mode 100644 index cb9aac7..0000000 --- a/X10D/src/StructExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for types. - /// - public static class StructExtensions - { - /// - /// Returns the next value in an using the specified value as a starting point. - /// - /// An . - /// An value. - /// - /// Optional. Whether or not to wrap to the to the start of the enum. Defaults to - /// true. - /// - /// Returns a value. - public static T Next(this T src, bool wrap = true) - where T : struct - { - if (!typeof(T).IsEnum) - { - throw new ArgumentException($"Argument {typeof(T).FullName} is not an Enum"); - } - - var arr = (T[])Enum.GetValues(src.GetType()); - var j = Array.IndexOf(arr, src) + 1; - return arr.Length == j ? arr[wrap ? 0 : j - 1] : arr[j]; - } - - /// - /// Returns the previous value in an using the specified value as a starting point. - /// - /// An . - /// An value. - /// - /// Optional. Whether or not to wrap to the to the end of the enum. Defaults to - /// true. - /// - /// Returns a value. - public static T Previous(this T src, bool wrap = true) - where T : struct - { - if (!typeof(T).IsEnum) - { - throw new ArgumentException($"Argument {typeof(T).FullName} is not an Enum"); - } - - var arr = (T[])Enum.GetValues(src.GetType()); - var j = Array.IndexOf(arr, src) - 1; - return j < 0 ? arr[wrap ? arr.Length - 1 : 0] : arr[j]; - } - } -} diff --git a/X10D/src/Text/CharExtensions.cs b/X10D/src/Text/CharExtensions.cs new file mode 100644 index 0000000..9d59738 --- /dev/null +++ b/X10D/src/Text/CharExtensions.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Text; + +/// +/// Text-related extension methods for . +/// +public static class CharExtensions +{ + /// + /// Returns a string composed of the current character repeated a specified number of times. + /// + /// The character to repeat. + /// The number of times to repeat. + /// + /// A composed of repeated times. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Repeat(this char value, int count) + { + return count switch + { + < 0 => throw new ArgumentOutOfRangeException(nameof(count), ExceptionMessages.CountMustBeGreaterThanOrEqualTo0), + 0 => string.Empty, + 1 => value.ToString(), + _ => new string(value, count) + }; + } +} diff --git a/X10D/src/Text/Extensions.cs b/X10D/src/Text/Extensions.cs new file mode 100644 index 0000000..574083c --- /dev/null +++ b/X10D/src/Text/Extensions.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Text.Json; + +namespace X10D.Text; + +/// +/// Text-related extension methods for all types. +/// +public static class Extensions +{ + /// + /// Returns a JSON string representation of the specified value. + /// + /// The value to convert. + /// The JSON serialization options. + /// The type of the value to convert. + /// A JSON string representing the object. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string ToJson(this T value, JsonSerializerOptions? options = null) + { + return JsonSerializer.Serialize(value, options); + } +} diff --git a/X10D/src/Text/RuneExtensions.cs b/X10D/src/Text/RuneExtensions.cs new file mode 100644 index 0000000..5c0b146 --- /dev/null +++ b/X10D/src/Text/RuneExtensions.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Text; + +namespace X10D.Text; + +/// +/// Text-related extension methods for . +/// +public static class RuneExtensions +{ + /// + /// Returns a string composed of the current rune repeated a specified number of times. + /// + /// The rune to repeat. + /// The number of times to repeat. + /// + /// A composed of repeated times. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Repeat(this Rune value, int count) + { + switch (count) + { + case < 0: + throw new ArgumentOutOfRangeException(nameof(count), ExceptionMessages.CountMustBeGreaterThanOrEqualTo0); + case 0: + return string.Empty; + case 1: + return value.ToString(); + } + + int utf8SequenceLength = value.Utf8SequenceLength; + Span utf8 = stackalloc byte[utf8SequenceLength]; + value.EncodeToUtf8(utf8); + + Span buffer = stackalloc byte[utf8.Length * count]; + for (var index = 0; index < count; index++) + { + utf8.CopyTo(buffer.Slice(index * utf8.Length, utf8.Length)); + } + + return Encoding.UTF8.GetString(buffer); + } +} diff --git a/X10D/src/Text/StringBuilderReader.cs b/X10D/src/Text/StringBuilderReader.cs new file mode 100644 index 0000000..fe6e969 --- /dev/null +++ b/X10D/src/Text/StringBuilderReader.cs @@ -0,0 +1,221 @@ +using System.Text; + +namespace X10D.Text; + +// NOTE: the overriden async overloads simply wrap the result of their sync counterparts because StringBuilder isn't inherently +// async. calling Task.FromResult (or creating a new ValueTask) is sufficient enough in this case, because there is simply no +// other way to have native async reading of a StringBuilder + +/// +/// Represents a reads from a . +/// +public class StringBuilderReader : TextReader +{ + private readonly StringBuilder _stringBuilder; + private int _index; + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + /// is . + public StringBuilderReader(StringBuilder stringBuilder) + { + _stringBuilder = stringBuilder ?? throw new ArgumentNullException(nameof(stringBuilder)); + } + + /// + public override void Close() + { + _index = _stringBuilder.Length; + } + + /// + public override int Peek() + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + return _stringBuilder[_index]; + } + + /// + public override int Read() + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + return _stringBuilder[_index++]; + } + + /// + public override int Read(char[] buffer, int index, int count) + { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (buffer.Length - index < count) + { + throw new ArgumentException(ExceptionMessages.BufferTooSmall, nameof(buffer)); + } + + if (_index >= _stringBuilder.Length) + { + return -1; + } + + int length = System.Math.Min(_stringBuilder.Length - _index, count); + _stringBuilder.CopyTo(_index, buffer, index, length); + _index += length; + return length; + } + + /// + public override int Read(Span buffer) + { + int count = System.Math.Min(buffer.Length, _stringBuilder.Length - _index); + for (var index = 0; index < count; index++) + { + buffer[index] = _stringBuilder[index + _index]; + } + + _index += count; + return count; + } + + /// + public override Task ReadAsync(char[] buffer, int index, int count) + { + return Task.FromResult(Read(buffer, index, count)); + } + + // except not really 🔽 + /// + /// Asynchronously reads the characters from the current stream into a memory block. + /// + /// + /// When this method returns, contains the specified memory block of characters replaced by the characters read from the + /// current source. + /// + /// Ignored. + /// + /// A value task that represents the asynchronous read operation. The value of the type parameter contains the number of + /// characters that have been read, or 0 if at the end of the stream and no data was read. The number will be less than or + /// equal to the buffer length, depending on whether the data is available within the stream. + /// + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return new ValueTask(Read(buffer.Span)); + } + + /// + public override int ReadBlock(Span buffer) + { + return Read(buffer); + } + + /// + public override int ReadBlock(char[] buffer, int index, int count) + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + int length = System.Math.Min(count, _stringBuilder.Length - _index); + _stringBuilder.CopyTo(_index, buffer, index, length); + _index += length; + return length; + } + + /// + public override Task ReadBlockAsync(char[] buffer, int index, int count) + { + return Task.FromResult(ReadBlock(buffer, index, count)); + } + + // except not really 🔽 + /// + /// Asynchronously reads the characters from the current stream and writes the data to a buffer. + /// + /// + /// When this method returns, contains the specified memory block of characters replaced by the characters read from the + /// current source. + /// + /// Ignored. + /// + /// A value task that represents the asynchronous read operation. The value of the type parameter contains the total + /// number of characters read into the buffer. The result value can be less than the number of characters requested if the + /// number of characters currently available is less than the requested number, or it can be 0 (zero) if the end of the + /// stream has been reached. + /// + public override ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return new ValueTask(ReadBlock(buffer.Span)); + } + + /// + public override string? ReadLine() + { + if (_index >= _stringBuilder.Length) + { + return null; + } + + int start = _index; + while (_index < _stringBuilder.Length && _stringBuilder[_index] != '\n') + { + _index++; + } + + if (_index < _stringBuilder.Length) + { + _index++; + } + + var result = _stringBuilder.ToString(start, _index - start); + + if (result.Length > 0 && (result[^1] == '\n' || result[^1] == '\r')) + { + result = result[..^1]; + } + + return result; + } + + /// + public override Task ReadLineAsync() + { + return Task.FromResult(ReadLine()); + } + + /// + public override string ReadToEnd() + { + var value = _stringBuilder.ToString(_index, _stringBuilder.Length - _index); + _index = _stringBuilder.Length; + return value; + } + + /// + public override Task ReadToEndAsync() + { + return Task.FromResult(ReadToEnd()); + } +} diff --git a/X10D/src/Text/StringExtensions.cs b/X10D/src/Text/StringExtensions.cs new file mode 100644 index 0000000..2f83e3c --- /dev/null +++ b/X10D/src/Text/StringExtensions.cs @@ -0,0 +1,551 @@ +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using X10D.Collections; +using X10D.Core; +using X10D.IO; + +namespace X10D.Text; + +/// +/// Text-related extension methods for . +/// +public static class StringExtensions +{ + /// + /// Normalizes a string which may be either or empty to . + /// + /// The value to normalize. + /// + /// if is or empty; otherwise, + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [return: NotNullIfNotNull("value")] + public static string? AsNullIfEmpty(this string? value) + { + return value.WithEmptyAlternative(null); + } + + /// + /// Normalizes a string which may be either , empty, or consisting of only whitespace, to + /// . + /// + /// The value to normalize. + /// + /// if is , empty, or consists of only + /// whitespace; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [return: NotNullIfNotNull("value")] + public static string? AsNullIfWhiteSpace(this string? value) + { + return value.WithWhiteSpaceAlternative(null); + } + + /// + /// Converts the specified string, which encodes binary data as base-64 digits, to an equivalent plain text string. + /// + /// The base-64 string to convert. + /// The plain text string representation of . + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Base64Decode(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return Convert.FromBase64String(value).ToString(Encoding.ASCII); + } + + /// + /// Converts the current string to its equivalent string representation that is encoded with base-64 digits. + /// + /// The plain text string to convert. + /// The string representation, in base 64, of . + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Base64Encode(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return Convert.ToBase64String(value.GetBytes(Encoding.ASCII)); + } + + /// + /// Converts this string from one encoding to another. + /// + /// The input string. + /// The input encoding. + /// The output encoding. + /// + /// Returns a new with its data converted to + /// . + /// + /// + /// is + /// - or - + /// is + /// -or + /// is . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string ChangeEncoding(this string value, Encoding sourceEncoding, Encoding destinationEncoding) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (sourceEncoding is null) + { + throw new ArgumentNullException(nameof(sourceEncoding)); + } + + if (destinationEncoding is null) + { + throw new ArgumentNullException(nameof(destinationEncoding)); + } + + return value.GetBytes(sourceEncoding).ToString(destinationEncoding); + } + + /// + /// Parses a into an . + /// + /// The type of the . + /// The value to parse. + /// The value corresponding to the . + /// + /// Credit for this method goes to Scott Dorman: + /// (http://geekswithblogs.net/sdorman/Default.aspx). + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T EnumParse(this string value) + where T : struct, Enum + { + return value.EnumParse(false); + } + + /// + /// Parses a into an . + /// + /// The type of the . + /// The value to parse. + /// Whether or not to ignore casing. + /// The value corresponding to the . + /// + /// Credit for this method goes to Scott Dorman: + /// (http://geekswithblogs.net/sdorman/Default.aspx). + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T EnumParse(this string value, bool ignoreCase) + where T : struct, Enum + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException(Resource.EnumParseEmptyStringException, nameof(value)); + } + + return Enum.Parse(value, ignoreCase); + } + + /// + /// Returns an object from the specified JSON string. + /// + /// The JSON to convert. + /// The JSON serialization options. + /// The type of the value to deserialize. + /// + /// An object constructed from the JSON string, or if deserialization could not be performed. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T? FromJson(this string value, JsonSerializerOptions? options = null) + { + return JsonSerializer.Deserialize(value, options); + } + + /// + /// Gets a [] representing the value the with + /// encoding. + /// + /// The string to convert. + /// Returns a []. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte[] GetBytes(this string value) + { + return value.GetBytes(Encoding.UTF8); + } + + /// + /// Gets a [] representing the value the with the provided encoding. + /// + /// The string to convert. + /// The encoding to use. + /// Returns a []. + /// + /// or or both are + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte[] GetBytes(this string value, Encoding encoding) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (encoding is null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return encoding.GetBytes(value); + } + + /// + /// Determines if all alpha characters in this string are considered lowercase. + /// + /// The input string. + /// + /// if all alpha characters in this string are lowercase; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLower(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + for (var index = 0; index < value.Length; index++) + { + var rune = new Rune(value[index]); + + if (!Rune.IsLetter(rune)) + { + continue; + } + + if (!Rune.IsLower(rune)) + { + return false; + } + } + + return true; + } + + /// + /// Determines whether the current string is considered palindromic; that is, the letters within the string are the + /// same when reversed. + /// + /// The value to check. + /// + /// if is considered a palindromic string; otherwise, + /// . + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPalindrome(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (string.IsNullOrWhiteSpace(value)) + { + // empty string is not palindromic + return false; + } + + for (int index = 0, endIndex = value.Length - 1; index < value.Length; index++, endIndex--) + { + Rune startRune = new Rune(value[index]); + Rune endRune = new Rune(value[endIndex]); + + if (!Rune.IsLetter(startRune) && !Rune.IsNumber(startRune)) + { + endIndex++; + continue; + } + + if (!Rune.IsLetter(endRune) && !Rune.IsNumber(endRune)) + { + index--; + continue; + } + + if (Rune.ToUpperInvariant(startRune) != Rune.ToUpperInvariant(endRune)) + { + return false; + } + } + + return true; + } + + /// + /// Determines if all alpha characters in this string are considered uppercase. + /// + /// The input string. + /// + /// if all alpha characters in this string are uppercase; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsUpper(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + for (var index = 0; index < value.Length; index++) + { + var rune = new Rune(value[index]); + + if (!Rune.IsLetter(rune)) + { + continue; + } + + if (!Rune.IsUpper(rune)) + { + return false; + } + } + + return true; + } + + /// + /// Repeats a string a specified number of times. + /// + /// The string to repeat. + /// The repeat count. + /// A string containing repeated times. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Repeat(this string value, int count) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + switch (count) + { + case < 0: + throw new ArgumentOutOfRangeException(nameof(count), ExceptionMessages.CountMustBeGreaterThanOrEqualTo0); + case 0: + return string.Empty; + case 1: + return value; + } + + var builder = new StringBuilder(value.Length * count); + + for (var i = 0; i < count; i++) + { + builder.Append(value); + } + + return builder.ToString(); + } + + /// + /// Returns a new string of a specified length by randomly selecting characters from the current string. + /// + /// The pool of characters to use. + /// The length of the new string returned. + /// The supplier. + /// + /// A new string whose length is equal to which contains randomly selected characters from + /// . + /// + /// is . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Randomize(this string source, int length, Random? random = null) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), ExceptionMessages.LengthGreaterThanOrEqualTo0); + } + + if (length == 0) + { + return string.Empty; + } + + random ??= Random.Shared; + + char[] array = source.ToCharArray(); + var builder = new StringBuilder(length); + + while (builder.Length < length) + { + char next = random.NextFrom(array); + builder.Append(next); + } + + return builder.ToString(); + } + + /// + /// Reverses the current string. + /// + /// The string to reverse. + /// A whose characters are that of in reverse order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Reverse(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length < 2) + { + return value; + } + + Span span = stackalloc char[value.Length]; + + for (var index = 0; index < value.Length; index++) + { + span[index] = value[value.Length - index - 1]; + } + + return new string(span); + } + + /// + /// Shuffles the characters in the string. + /// + /// The string to shuffle. + /// + /// The instance to use for the shuffling. If is specified, + /// is used. + /// + /// A new containing the characters in , rearranged. + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Shuffled(this string value, Random? random = null) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + random ??= Random.Shared; + + char[] array = value.ToCharArray(); + array.Shuffle(random); + return new string(array); + } + + /// + /// Splits the into chunks that are no greater than in length. + /// + /// The string to split. + /// The maximum length of each string in the returned result. + /// + /// Returns an containing instances which are no + /// greater than in length. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static IEnumerable Split(this string value, int chunkSize) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (chunkSize == 0) + { + yield return string.Empty; + yield break; + } + + for (var i = 0; i < value.Length; i += chunkSize) + { + yield return value[i..System.Math.Min(i + chunkSize, value.Length)]; + } + } + + /// + /// Normalizes a string which may be either or empty to a specified alternative. + /// + /// The value to normalize. + /// The alternative string. + /// + /// if is or empty; otherwise, + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [return: NotNullIfNotNull("alternative")] + public static string? WithEmptyAlternative(this string? value, string? alternative) + { + return string.IsNullOrEmpty(value) ? alternative : value; + } + + /// + /// Normalizes a string which may be either , empty, or consisting of only whitespace, to a + /// specified alternative. + /// + /// The value to normalize. + /// The alternative string. + /// + /// if is , empty, or consists of only + /// whitespace; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [return: NotNullIfNotNull("alternative")] + public static string? WithWhiteSpaceAlternative(this string? value, string? alternative) + { + return string.IsNullOrWhiteSpace(value) ? alternative : value; + } +} diff --git a/X10D/src/Time/ByteExtensions.cs b/X10D/src/Time/ByteExtensions.cs new file mode 100644 index 0000000..b15a91d --- /dev/null +++ b/X10D/src/Time/ByteExtensions.cs @@ -0,0 +1,166 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class ByteExtensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this byte value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + return value % 4 == 0 && value % 100 != 0; // mod 400 not required, byte.MaxValue is 255 anyway + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this byte value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this byte value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this byte value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this byte value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this byte value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this byte value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this byte value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this byte value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this byte value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/DateTimeExtensions.cs b/X10D/src/Time/DateTimeExtensions.cs new file mode 100644 index 0000000..b546722 --- /dev/null +++ b/X10D/src/Time/DateTimeExtensions.cs @@ -0,0 +1,110 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Extension methods for . +/// +public static class DateTimeExtensions +{ + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Age(this DateTime value) + { + return value.Age(DateTime.Today); + } + + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Age(this DateTime value, DateTime asOf) + { + return ((DateTimeOffset)value).Age(asOf); + } + + /// + /// A representing the first occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime First(this DateTime value, DayOfWeek dayOfWeek) + { + return ((DateTimeOffset)value).First(dayOfWeek).DateTime; + } + + /// + /// A representing the first day of the current month. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime FirstDayOfMonth(this DateTime value) + { + return ((DateTimeOffset)value).FirstDayOfMonth().DateTime; + } + + /// + /// Returns a value indicating whether the year represented by the current is a leap year. + /// + /// The date whose year to check. + /// + /// if the year represented by is a leap year; otherwise, + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this DateTime value) + { + return DateTime.IsLeapYear(value.Year); + } + + /// + /// A representing the final occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime Last(this DateTime value, DayOfWeek dayOfWeek) + { + return ((DateTimeOffset)value).Last(dayOfWeek).DateTime; + } + + /// + /// A representing the last day of the current month. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime LastDayOfMonth(this DateTime value) + { + return ((DateTimeOffset)value).LastDayOfMonth().DateTime; + } + + /// + /// A representing the next occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime Next(this DateTime value, DayOfWeek dayOfWeek) + { + return ((DateTimeOffset)value).Next(dayOfWeek).DateTime; + } + + /// + /// Returns the number of milliseconds that have elapsed since 1970-01-01T00:00:00.000Z. + /// + /// The current date. + /// The number of milliseconds that have elapsed since 1970-01-01T00:00:00.000Z. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long ToUnixTimeMilliseconds(this DateTime value) + { + return ((DateTimeOffset)value).ToUnixTimeMilliseconds(); + } + + /// + /// Returns the number of seconds that have elapsed since 1970-01-01T00:00:00.000Z. + /// + /// The current date. + /// The number of seconds that have elapsed since 1970-01-01T00:00:00.000Z. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long ToUnixTimeSeconds(this DateTime value) + { + return ((DateTimeOffset)value).ToUnixTimeSeconds(); + } +} diff --git a/X10D/src/Time/DateTimeOffsetExtensions.cs b/X10D/src/Time/DateTimeOffsetExtensions.cs new file mode 100644 index 0000000..ca06ec0 --- /dev/null +++ b/X10D/src/Time/DateTimeOffsetExtensions.cs @@ -0,0 +1,137 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Extension methods for . +/// +public static class DateTimeOffsetExtensions +{ + /// + /// Returns the rounded-down integer number of years since a given date as of today. + /// + /// The date from which to calculate. + /// The rounded-down integer number of years since as of today. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Age(this DateTimeOffset value) + { + return value.Age(DateTime.Today); + } + + /// + /// Returns the rounded-down integer number of years since a given date as of another specified date. + /// + /// The date from which to calculate. + /// The date at which to stop calculating. + /// + /// The rounded-down integer number of years since as of the date specified by + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Age(this DateTimeOffset value, DateTimeOffset asOf) + { + return (int)(((asOf.Date - TimeSpan.FromDays(1) - value.Date).TotalDays + 1) / 365.2425); + } + + /// + /// Gets a date representing the first occurence of a specified day of the week in the current month. + /// + /// The current date. + /// The day of the week. + /// A representing the first occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset First(this DateTimeOffset value, DayOfWeek dayOfWeek) + { + var first = value.FirstDayOfMonth(); + + if (first.DayOfWeek != dayOfWeek) + { + first = first.Next(dayOfWeek); + } + + return first; + } + + /// + /// Gets a date representing the first day of the current month. + /// + /// The current date. + /// A representing the first day of the current month. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FirstDayOfMonth(this DateTimeOffset value) + { + return value.AddDays(1 - value.Day); + } + + /// + /// Returns a value indicating whether the year represented by the current is a leap year. + /// + /// The date whose year to check. + /// + /// if the year represented by is a leap year; otherwise, + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this DateTimeOffset value) + { + return DateTime.IsLeapYear(value.Year); + } + + /// + /// Gets a date representing the final occurence of a specified day of the week in the current month. + /// + /// The current date. + /// The day of the week. + /// A representing the final occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset Last(this DateTimeOffset value, DayOfWeek dayOfWeek) + { + var last = value.LastDayOfMonth(); + var lastDayOfWeek = last.DayOfWeek; + + int diff = dayOfWeek - lastDayOfWeek; + int offset = diff > 0 ? diff - 7 : diff; + + return last.AddDays(offset); + } + + /// + /// Gets a date representing the last day of the current month. + /// + /// The current date. + /// A representing the last day of the current month. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset LastDayOfMonth(this DateTimeOffset value) + { + int daysInMonth = DateTime.DaysInMonth(value.Year, value.Month); + return new DateTime(value.Year, value.Month, daysInMonth); + } + + /// + /// Gets a date representing the next occurence of a specified day of the week in the current month. + /// + /// The current date. + /// The day of the week. + /// A representing the next occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset Next(this DateTimeOffset value, DayOfWeek dayOfWeek) + { + int offsetDays = dayOfWeek - value.DayOfWeek; + + if (offsetDays <= 0) + { + offsetDays += 7; + } + + return value.AddDays(offsetDays); + } +} diff --git a/X10D/src/Time/DecimalExtensions.cs b/X10D/src/Time/DecimalExtensions.cs new file mode 100644 index 0000000..880e1a9 --- /dev/null +++ b/X10D/src/Time/DecimalExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class DecimalExtensions +{ + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this decimal value) + { + return TimeSpan.FromMilliseconds((double)value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this decimal value) + { + return TimeSpan.FromSeconds((double)value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this decimal value) + { + return TimeSpan.FromMinutes((double)value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this decimal value) + { + return TimeSpan.FromHours((double)value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this decimal value) + { + return TimeSpan.FromDays((double)value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this decimal value) + { + return TimeSpan.FromDays((double)value * 7); + } +} diff --git a/X10D/src/Time/DoubleExtensions.cs b/X10D/src/Time/DoubleExtensions.cs new file mode 100644 index 0000000..81d6b38 --- /dev/null +++ b/X10D/src/Time/DoubleExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class DoubleExtensions +{ + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this double value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this double value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this double value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this double value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this double value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this double value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/HalfExtensions.cs b/X10D/src/Time/HalfExtensions.cs new file mode 100644 index 0000000..396fc68 --- /dev/null +++ b/X10D/src/Time/HalfExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class HalfExtensions +{ + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this Half value) + { + return TimeSpan.FromMilliseconds((float)value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this Half value) + { + return TimeSpan.FromSeconds((float)value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this Half value) + { + return TimeSpan.FromMinutes((float)value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this Half value) + { + return TimeSpan.FromHours((float)value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this Half value) + { + return TimeSpan.FromDays((float)value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this Half value) + { + return TimeSpan.FromDays((float)value * 7); + } +} diff --git a/X10D/src/Time/Int16Extensions.cs b/X10D/src/Time/Int16Extensions.cs new file mode 100644 index 0000000..bfc1605 --- /dev/null +++ b/X10D/src/Time/Int16Extensions.cs @@ -0,0 +1,172 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this short value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + if (value < 0) + { + value++; + } + + return value.Mod(4) == 0 && (value.Mod(100) != 0 || value.Mod(400) == 0); + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this short value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this short value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this short value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this short value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this short value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this short value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this short value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this short value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this short value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/Int32Extensions.cs b/X10D/src/Time/Int32Extensions.cs new file mode 100644 index 0000000..576f84f --- /dev/null +++ b/X10D/src/Time/Int32Extensions.cs @@ -0,0 +1,172 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this int value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + if (value < 0) + { + value++; + } + + return value.Mod(4) == 0 && (value.Mod(100) != 0 || value.Mod(400) == 0); + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this int value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this int value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this int value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this int value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this int value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this int value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this int value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this int value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this int value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/Int64Extensions.cs b/X10D/src/Time/Int64Extensions.cs new file mode 100644 index 0000000..f07969c --- /dev/null +++ b/X10D/src/Time/Int64Extensions.cs @@ -0,0 +1,172 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this long value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + if (value < 0) + { + value++; + } + + return value.Mod(4) == 0 && (value.Mod(100) != 0 || value.Mod(400) == 0); + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this long value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this long value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this long value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this long value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this long value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this long value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this long value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this long value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this long value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/SByteExtensions.cs b/X10D/src/Time/SByteExtensions.cs new file mode 100644 index 0000000..606df12 --- /dev/null +++ b/X10D/src/Time/SByteExtensions.cs @@ -0,0 +1,173 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +[CLSCompliant(false)] +public static class SByteExtensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this sbyte value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + if (value < 0) + { + value++; + } + + return value.Mod(4) == 0 && value.Mod(100) != 0; // mod 400 not required, sbyte.MaxValue is 127 anyway + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this sbyte value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this sbyte value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this sbyte value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this sbyte value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this sbyte value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this sbyte value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this sbyte value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this sbyte value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this sbyte value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/SingleExtensions.cs b/X10D/src/Time/SingleExtensions.cs new file mode 100644 index 0000000..f1d0546 --- /dev/null +++ b/X10D/src/Time/SingleExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class SingleExtensions +{ + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this float value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this float value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this float value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this float value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this float value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this float value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/StringExtensions.cs b/X10D/src/Time/StringExtensions.cs new file mode 100644 index 0000000..cf22f71 --- /dev/null +++ b/X10D/src/Time/StringExtensions.cs @@ -0,0 +1,72 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Extension methods for . +/// +public static class StringExtensions +{ + /// + /// Parses a shorthand time span string (e.g. 3w 2d 1h) and converts it to an instance of . + /// + /// + /// The input string. Floating point is not supported, but range the following units are supported: + /// + /// + /// + /// Suffix + /// Meaning + /// + /// + /// + /// ms + /// Milliseconds + /// + /// + /// s + /// Seconds + /// + /// + /// m + /// Minutes + /// + /// + /// h + /// Hours + /// + /// + /// d + /// Days + /// + /// + /// w + /// Weeks + /// + /// + /// mo + /// Months + /// + /// + /// y + /// Years + /// + /// + /// + /// A new instance of . + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan ToTimeSpan(this string input) + { + if (input is null) + { + throw new ArgumentNullException(nameof(input)); + } + + return TimeSpanParser.TryParse(input, out TimeSpan result) + ? result + : default; + } +} diff --git a/X10D/src/Time/TimeSpanExtensions.cs b/X10D/src/Time/TimeSpanExtensions.cs new file mode 100644 index 0000000..d9424f1 --- /dev/null +++ b/X10D/src/Time/TimeSpanExtensions.cs @@ -0,0 +1,38 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Extension methods for . +/// +public static class TimeSpanExtensions +{ + /// + /// Returns a that is a specified duration in the past relative to the current time. + /// + /// The whose duration to subtract. + /// + /// A that is a duration of in the past relative to the current time. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime Ago(this TimeSpan value) + { + return DateTime.Now.Subtract(value); + } + + /// + /// Returns a that is a specified duration in the future relative to the current time. + /// + /// The whose duration to add. + /// + /// A that is a duration of in the future relative to the current time. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime FromNow(this TimeSpan value) + { + return DateTime.Now.Add(value); + } +} diff --git a/X10D/src/Time/TimeSpanParser.cs b/X10D/src/Time/TimeSpanParser.cs new file mode 100644 index 0000000..2839f22 --- /dev/null +++ b/X10D/src/Time/TimeSpanParser.cs @@ -0,0 +1,133 @@ +namespace X10D.Time; + +/// +/// Represents a class which contains a parser which converts into . +/// +public static class TimeSpanParser +{ + /// + /// Attempts to parses a shorthand time span string (e.g. 3w 2d 1h), converting it to an instance of + /// which represents that duration of time. + /// + /// + /// The input string. Floating point is not supported, but range the following units are supported: + /// + /// + /// + /// Suffix + /// Meaning + /// + /// + /// + /// ms + /// Milliseconds + /// + /// + /// s + /// Seconds + /// + /// + /// m + /// Minutes + /// + /// + /// h + /// Hours + /// + /// + /// d + /// Days + /// + /// + /// w + /// Weeks + /// + /// + /// mo + /// Months + /// + /// + /// y + /// Years + /// + /// + /// + /// When this method returns, contains the parsed result. + /// if the parse was successful, otherwise. + /// is . + public static bool TryParse(string value, out TimeSpan result) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + result = TimeSpan.Zero; + var unitValue = 0; + + for (var index = 0; index < value.Length; index++) + { + char current = value[index]; + switch (current) + { + case var digitChar when char.IsDigit(digitChar): + var digit = (int)char.GetNumericValue(digitChar); + unitValue = unitValue * 10 + digit; + break; + + case 'y': + result += TimeSpan.FromDays(unitValue * 365); + unitValue = 0; + break; + + case 'm': + if (index < value.Length - 1 && value[index + 1] == 'o') + { + index++; + result += TimeSpan.FromDays(unitValue * 30); + } + else if (index < value.Length - 1 && value[index + 1] == 's') + { + index++; + result += TimeSpan.FromMilliseconds(unitValue); + } + else + { + result += TimeSpan.FromMinutes(unitValue); + } + + unitValue = 0; + break; + + case 'w': + result += TimeSpan.FromDays(unitValue * 7); + unitValue = 0; + break; + + case 'd': + result += TimeSpan.FromDays(unitValue); + unitValue = 0; + break; + + case 'h': + result += TimeSpan.FromHours(unitValue); + unitValue = 0; + break; + + case 's': + result += TimeSpan.FromSeconds(unitValue); + unitValue = 0; + break; + + case var space when char.IsWhiteSpace(space): + break; + + default: + result = TimeSpan.Zero; + return false; + } + } + + return true; + } +} diff --git a/X10D/src/Time/UInt16Extensions.cs b/X10D/src/Time/UInt16Extensions.cs new file mode 100644 index 0000000..b0ee2c9 --- /dev/null +++ b/X10D/src/Time/UInt16Extensions.cs @@ -0,0 +1,167 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt16Extensions +{ + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this ushort value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this ushort value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this ushort value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + return value % 4 == 0 && (value % 100 != 0 || value % 400 == 0); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this ushort value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this ushort value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this ushort value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this ushort value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this ushort value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this ushort value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this ushort value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/UInt32Extensions.cs b/X10D/src/Time/UInt32Extensions.cs new file mode 100644 index 0000000..9a4b09f --- /dev/null +++ b/X10D/src/Time/UInt32Extensions.cs @@ -0,0 +1,167 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt32Extensions +{ + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this uint value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this uint value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this uint value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + return value % 4 == 0 && (value % 100 != 0 || value % 400 == 0); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this uint value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this uint value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this uint value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this uint value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this uint value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this uint value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this uint value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/UInt64Extensions.cs b/X10D/src/Time/UInt64Extensions.cs new file mode 100644 index 0000000..2dfb049 --- /dev/null +++ b/X10D/src/Time/UInt64Extensions.cs @@ -0,0 +1,167 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt64Extensions +{ + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this ulong value) + { + return DateTimeOffset.FromUnixTimeMilliseconds((long)value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this ulong value) + { + return DateTimeOffset.FromUnixTimeSeconds((long)value); + } + + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this ulong value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + return value % 4 == 0 && (value % 100 != 0 || value % 400 == 0); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this ulong value) + { + return TimeSpan.FromTicks((long)value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this ulong value) + { + return TimeSpan.FromMilliseconds((long)value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this ulong value) + { + return TimeSpan.FromSeconds((long)value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this ulong value) + { + return TimeSpan.FromMinutes((long)value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this ulong value) + { + return TimeSpan.FromHours((long)value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this ulong value) + { + return TimeSpan.FromDays((long)value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this ulong value) + { + return TimeSpan.FromDays((long)value * 7); + } +} diff --git a/X10D/src/TimeSpanParser.cs b/X10D/src/TimeSpanParser.cs deleted file mode 100644 index 5f5a7aa..0000000 --- a/X10D/src/TimeSpanParser.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace X10D -{ - using System; - using System.Diagnostics; - using System.Text.RegularExpressions; - - /// - /// Represents a class which contains a parser which converts into . - /// - public static class TimeSpanParser - { - /// - /// Parses a shorthand time span string (e.g. 3w 2d 1.5h) and converts it to an instance of - /// . - /// - /// The input string. - /// The format provider. - /// Returns an instance of . - public static TimeSpan Parse(string input, IFormatProvider provider = null) - { - const string realNumberPattern = @"([0-9]*\.[0-9]+|[0-9]+)"; - var pattern = $"^(?:{realNumberPattern} *w)? *" + - $"(?:{realNumberPattern} *d)? *" + - $"(?:{realNumberPattern} *h)? *" + - $"(?:{realNumberPattern} *m)? *" + - $"(?:{realNumberPattern} *s)? *" + - $"(?:{realNumberPattern} *ms)?$"; - - var match = Regex.Match(input, pattern); - double weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0; - - if (match.Groups[1].Success) - { - weeks = double.Parse(match.Groups[1].Value, provider); - } - - if (match.Groups[2].Success) - { - days = double.Parse(match.Groups[2].Value, provider); - } - - if (match.Groups[3].Success) - { - hours = double.Parse(match.Groups[3].Value, provider); - } - - if (match.Groups[4].Success) - { - minutes = double.Parse(match.Groups[4].Value, provider); - } - - if (match.Groups[5].Success) - { - seconds = double.Parse(match.Groups[5].Value, provider); - } - - if (match.Groups[6].Success) - { - milliseconds = double.Parse(match.Groups[6].Value, provider); - } - - Trace.WriteLine($"Input: {input}"); - Trace.WriteLine($"Parsed: {weeks}w {days}d {hours}h {minutes}m {seconds}s {milliseconds}ms"); - - var span = TimeSpan.Zero; - - span += TimeSpan.FromDays(weeks * 7); - span += TimeSpan.FromDays(days); - span += TimeSpan.FromHours(hours); - span += TimeSpan.FromMinutes(minutes); - span += TimeSpan.FromSeconds(seconds); - span += TimeSpan.FromMilliseconds(milliseconds); - - return span; - } - } -} diff --git a/X10D/src/WaitHandleExtensions.cs b/X10D/src/WaitHandleExtensions.cs deleted file mode 100644 index 9febee6..0000000 --- a/X10D/src/WaitHandleExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace X10D -{ - using System.Threading; - using System.Threading.Tasks; - - /// - /// Extension methods for . - /// - public static class WaitHandleExtensions - { - /// - /// Returns a which can be awaited until the current receives a signal. - /// - /// The instance. - /// Returns a task which wraps . - public static Task WaitOneAsync(this WaitHandle handle) - { - return new Task(() => handle.WaitOne()); - } - } -} diff --git a/banner.png b/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..d9aa957d75e2a9657f189dee8a7dc5f9e89f6dae GIT binary patch literal 18090 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 A?EnA( literal 32695 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