diff --git a/.editorconfig b/.editorconfig index 175c36f..fa80a79 100644 --- a/.editorconfig +++ b/.editorconfig @@ -44,6 +44,7 @@ generated_code = true # XML project files [*.{slnx,csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,nativeproj,locproj}] indent_size = 2 +max_line_length = 160 # Xml build files [*.builds] diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index cead3c1..62d43f8 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -25,5 +25,6 @@ jobs: with: dotnetLogging: ${{ inputs.dotnet-logging }} dotnetVersion: ${{ vars.NE_DOTNET_TARGETFRAMEWORKS }} + runsOnBuild: windows-latest solution: ./Arguments.slnx secrets: inherit diff --git a/Directory.Build.props b/Directory.Build.props index 2fb19b4..f38854d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,15 @@ - - net8.0 + <_NetTargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0 + <_ClassicTargetFrameworks>net472;net48;net481 + <_ProjectTargetFrameworks>$(_NetTargetFrameworks);netstandard2.0 + <_ProjectTargetFrameworks Condition=" '$(OS)' == 'Windows_NT' " + >$(_ProjectTargetFrameworks);$(_ClassicTargetFrameworks) + <_TestTargetFrameworks>$(_NetTargetFrameworks) + <_TestTargetFrameworks Condition=" '$(OS)' == 'Windows_NT' " + >$(_TestTargetFrameworks);$(_ClassicTargetFrameworks) + false diff --git a/README.md b/README.md index ee08d0f..2d515c8 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,21 @@ A comprehensive library providing backward-compatible argument validation helper Modern .NET versions (starting with .NET 6) introduced streamlined argument validation methods such as `ArgumentNullException.ThrowIfNull` and `ArgumentOutOfRangeException.ThrowIfEqual`. However, projects targeting multiple frameworks or older .NET versions cannot utilize these convenient methods without conditional compilation or duplicated validation logic. -**NetEvolve.Arguments** bridges this gap by providing polyfilled implementations of these modern validation methods, allowing developers to write consistent, maintainable argument validation code regardless of the target framework. +**NetEvolve.Arguments** bridges this gap by providing full polyfill implementations via extension methods on `ArgumentNullException`, `ArgumentException`, and `ArgumentOutOfRangeException`. These polyfills enable the use of modern .NET API patterns across all supported frameworks, allowing developers to write consistent, maintainable argument validation code regardless of the target framework. + +### Polyfill Architecture + +The library provides polyfills through three main extension classes: + +- **`ArgumentNullExceptionPolyfills`**: Extends `ArgumentNullException` with `ThrowIfNull` methods +- **`ArgumentExceptionPolyfills`**: Extends `ArgumentException` with `ThrowIfNullOrEmpty` and `ThrowIfNullOrWhiteSpace` methods +- **`ArgumentOutOfRangeExceptionPolyfills`**: Extends `ArgumentOutOfRangeException` with range validation methods (`ThrowIfZero`, `ThrowIfNegative`, `ThrowIfEqual`, comparison methods, etc.) + +These polyfills are conditionally compiled and only active when targeting frameworks that don't provide the native implementations, ensuring zero overhead on modern .NET versions. ## Key Features -- **Multi-Framework Support**: Compatible with .NET Standard 2.0, .NET 8.0, .NET 9.0, and .NET 10.0 +- **Multi-Framework Support**: Compatible with .NET Standard 2.0, .NET 6.0-10.0, and .NET Framework 4.7.2-4.8.1 (on Windows) - **Zero Runtime Overhead**: Uses conditional compilation to delegate to native implementations where available - **Drop-in Replacement**: Identical API signatures to native .NET implementations - **Type-Safe**: Fully generic implementations with proper type constraints @@ -35,154 +45,233 @@ Install-Package NetEvolve.Arguments ## Usage -Import the namespace in your code: - -```csharp -using NetEvolve.Arguments; -``` - -Then use the validation methods just as you would with native .NET implementations: +Simply use the validation methods directly on the exception types, just as you would with native .NET 8+ implementations: ```csharp public void ProcessData(string data, int count) { - Argument.ThrowIfNullOrWhiteSpace(data); - Argument.ThrowIfLessThan(count, 1); + ArgumentException.ThrowIfNullOrWhiteSpace(data); + ArgumentOutOfRangeException.ThrowIfLessThan(count, 1); // Your implementation } ``` +The polyfills are automatically available through extension methods when targeting older frameworks. No additional using directives are needed since the polyfills reside in the `System` namespace. + ## Available Methods ### Null Validation -#### `Argument.ThrowIfNull(object?, string?)` +#### `ArgumentNullException.ThrowIfNull(object?, string?)` Throws an `ArgumentNullException` if the argument is `null`. -**Replacement for**: [`ArgumentNullException.ThrowIfNull(object, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentnullexception.throwifnull) (introduced in .NET 6) +**Native API**: [`ArgumentNullException.ThrowIfNull`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentnullexception.throwifnull) (introduced in .NET 6) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1 **Example**: ```csharp public void Process(object data) { - Argument.ThrowIfNull(data); + ArgumentNullException.ThrowIfNull(data); } ``` -#### `Argument.ThrowIfNull(void*, string?)` +#### `ArgumentNullException.ThrowIfNull(void*, string?)` Throws an `ArgumentNullException` if the pointer argument is `null`. -**Replacement for**: [`ArgumentNullException.ThrowIfNull(void*, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentnullexception.throwifnull?view=net-8.0#system-argumentnullexception-throwifnull(system-void*-system-string)) (introduced in .NET 7) +**Native API**: [`ArgumentNullException.ThrowIfNull(void*)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentnullexception.throwifnull?view=net-10.0#system-argumentnullexception-throwifnull(system-void*-system-string)) (introduced in .NET 7) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0 + +**Example**: +```csharp +public unsafe void Process(void* pointer) +{ + ArgumentNullException.ThrowIfNull(pointer); +} +``` -#### `Argument.ThrowIfNullOrEmpty(string?, string?)` +#### `ArgumentException.ThrowIfNullOrEmpty(string?, string?)` Throws an `ArgumentNullException` if the argument is `null`, or an `ArgumentException` if the argument is an empty string. -**Replacement for**: [`ArgumentException.ThrowIfNullOrEmpty(string, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentexception.throwifnullorempty) (introduced in .NET 7) +**Native API**: [`ArgumentException.ThrowIfNullOrEmpty`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentexception.throwifnullorempty) (introduced in .NET 7) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0 **Example**: ```csharp public void Process(string name) { - Argument.ThrowIfNullOrEmpty(name); + ArgumentException.ThrowIfNullOrEmpty(name); } ``` -#### `Argument.ThrowIfNullOrEmpty(IEnumerable?, string?)` +#### `ArgumentException.ThrowIfNullOrEmpty(IEnumerable?, string?)` Throws an `ArgumentNullException` if the argument is `null`, or an `ArgumentException` if the collection is empty. -**Note**: This is a custom extension method not present in the base .NET framework, providing convenient collection validation. +**Note**: This is a custom extension method not present in the native .NET framework, providing convenient collection validation. + +**Availability**: All supported frameworks **Example**: ```csharp public void Process(IEnumerable items) { - Argument.ThrowIfNullOrEmpty(items); + ArgumentException.ThrowIfNullOrEmpty(items); } ``` -#### `Argument.ThrowIfNullOrWhiteSpace(string?, string?)` +#### `ArgumentException.ThrowIfNullOrWhiteSpace(string?, string?)` Throws an `ArgumentNullException` if the argument is `null`, or an `ArgumentException` if the argument is empty or contains only white-space characters. -**Replacement for**: [`ArgumentException.ThrowIfNullOrWhiteSpace(string, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentexception.throwifnullorwhitespace) (introduced in .NET 8) +**Native API**: [`ArgumentException.ThrowIfNullOrWhiteSpace`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentexception.throwifnullorwhitespace) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 **Example**: ```csharp public void Process(string description) { - Argument.ThrowIfNullOrWhiteSpace(description); + ArgumentException.ThrowIfNullOrWhiteSpace(description); } ``` ### Range Validation -#### `Argument.ThrowIfEqual(T, T, string?)` +#### `ArgumentOutOfRangeException.ThrowIfZero(T, string?)` +Throws an `ArgumentOutOfRangeException` if the argument is zero. + +**Native API**: [`ArgumentOutOfRangeException.ThrowIfZero`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifzero) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 + +**Example**: +```csharp +public void SetDivisor(int divisor) +{ + ArgumentOutOfRangeException.ThrowIfZero(divisor); +} +``` + +#### `ArgumentOutOfRangeException.ThrowIfNegative(T, string?)` +Throws an `ArgumentOutOfRangeException` if the argument is negative. + +**Native API**: [`ArgumentOutOfRangeException.ThrowIfNegative`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifnegative) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 + +**Example**: +```csharp +public void SetCount(int count) +{ + ArgumentOutOfRangeException.ThrowIfNegative(count); +} +``` + +#### `ArgumentOutOfRangeException.ThrowIfNegativeOrZero(T, string?)` +Throws an `ArgumentOutOfRangeException` if the argument is negative or zero. + +**Native API**: [`ArgumentOutOfRangeException.ThrowIfNegativeOrZero`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifnegativeorzero) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 + +**Example**: +```csharp +public void SetQuantity(int quantity) +{ + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(quantity); +} +``` + +#### `ArgumentOutOfRangeException.ThrowIfEqual(T, T, string?)` Throws an `ArgumentOutOfRangeException` if the first argument is equal to the second argument. -**Replacement for**: [`ArgumentOutOfRangeException.ThrowIfEqual(T, T, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifequal) (introduced in .NET 8) +**Native API**: [`ArgumentOutOfRangeException.ThrowIfEqual`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifequal) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 **Example**: ```csharp public void SetValue(int value) { - Argument.ThrowIfEqual(value, 0); // Value must not be zero + ArgumentOutOfRangeException.ThrowIfEqual(value, 0); // Value must not be zero } ``` -#### `Argument.ThrowIfNotEqual(T, T, string?)` +#### `ArgumentOutOfRangeException.ThrowIfNotEqual(T, T, string?)` Throws an `ArgumentOutOfRangeException` if the first argument is not equal to the second argument. -**Replacement for**: [`ArgumentOutOfRangeException.ThrowIfNotEqual(T, T, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifnotequal) (introduced in .NET 8) +**Native API**: [`ArgumentOutOfRangeException.ThrowIfNotEqual`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifnotequal) (introduced in .NET 8) -#### `Argument.ThrowIfGreaterThan(T, T, string?)` +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 + +**Example**: +```csharp +public void ValidateConstant(int value) +{ + ArgumentOutOfRangeException.ThrowIfNotEqual(value, 42); // Value must be exactly 42 +} +``` + +#### `ArgumentOutOfRangeException.ThrowIfGreaterThan(T, T, string?)` Throws an `ArgumentOutOfRangeException` if the first argument is greater than the second argument. -**Replacement for**: [`ArgumentOutOfRangeException.ThrowIfGreaterThan(T, T, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifgreaterthan) (introduced in .NET 8) +**Native API**: [`ArgumentOutOfRangeException.ThrowIfGreaterThan`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifgreaterthan) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 **Example**: ```csharp public void SetAge(int age) { - Argument.ThrowIfGreaterThan(age, 150); // Age must be 150 or less + ArgumentOutOfRangeException.ThrowIfGreaterThan(age, 150); // Age must be 150 or less } ``` -#### `Argument.ThrowIfGreaterThanOrEqual(T, T, string?)` +#### `ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(T, T, string?)` Throws an `ArgumentOutOfRangeException` if the first argument is greater than or equal to the second argument. -**Replacement for**: [`ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(T, T, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifgreaterthanorequal) (introduced in .NET 8) +**Native API**: [`ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifgreaterthanorequal) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 **Example**: ```csharp public void SetCount(int count, int maximum) { - Argument.ThrowIfGreaterThanOrEqual(count, maximum); // Count must be less than maximum + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(count, maximum); // Count must be less than maximum } ``` -#### `Argument.ThrowIfLessThan(T, T, string?)` +#### `ArgumentOutOfRangeException.ThrowIfLessThan(T, T, string?)` Throws an `ArgumentOutOfRangeException` if the first argument is less than the second argument. -**Replacement for**: [`ArgumentOutOfRangeException.ThrowIfLessThan(T, T, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwiflessthan) (introduced in .NET 8) +**Native API**: [`ArgumentOutOfRangeException.ThrowIfLessThan`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwiflessthan) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 **Example**: ```csharp public void SetCount(int count) { - Argument.ThrowIfLessThan(count, 1); // Count must be at least 1 + ArgumentOutOfRangeException.ThrowIfLessThan(count, 1); // Count must be at least 1 } ``` -#### `Argument.ThrowIfLessThanOrEqual(T, T, string?)` +#### `ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(T, T, string?)` Throws an `ArgumentOutOfRangeException` if the first argument is less than or equal to the second argument. -**Replacement for**: [`ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(T, T, string)`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwiflessthanorequal) (introduced in .NET 8) +**Native API**: [`ArgumentOutOfRangeException.ThrowIfLessThanOrEqual`](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwiflessthanorequal) (introduced in .NET 8) + +**Polyfill availability**: .NET Standard 2.0, .NET Framework 4.7.2-4.8.1, .NET 6.0, .NET 7.0 **Example**: ```csharp public void SetMinimum(int value, int threshold) { - Argument.ThrowIfLessThanOrEqual(value, threshold); // Value must be greater than threshold + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, threshold); // Value must be greater than threshold } ``` @@ -191,6 +280,11 @@ public void SetMinimum(int value, int threshold) | Target Framework | Status | Notes | |-----------------|--------|-------| | .NET Standard 2.0 | ✅ Supported | Full polyfill implementations | +| .NET Framework 4.7.2 | ✅ Supported | Windows only, full polyfill implementations | +| .NET Framework 4.8 | ✅ Supported | Windows only, full polyfill implementations | +| .NET Framework 4.8.1 | ✅ Supported | Windows only, full polyfill implementations | +| .NET 6.0 | ✅ Supported | Delegates to native implementations where available | +| .NET 7.0 | ✅ Supported | Delegates to native implementations where available | | .NET 8.0 | ✅ Supported | Delegates to native implementations where available | | .NET 9.0 | ✅ Supported | Full native delegation | | .NET 10.0 | ✅ Supported | Full native delegation | diff --git a/src/NetEvolve.Arguments/Argument.cs b/src/NetEvolve.Arguments/Argument.cs index 3ef639d..c9916d9 100644 --- a/src/NetEvolve.Arguments/Argument.cs +++ b/src/NetEvolve.Arguments/Argument.cs @@ -5,5 +5,5 @@ /// /// Provides a set of backward compatible `throw` helper methods, which have been added in previous .NET versions. /// -[SuppressMessage("Style", "IDE0022:Use expression body for method", Justification = "As designed.")] -public static partial class Argument { } +[SuppressMessage("Info Code Smell", "S1133:Deprecated code should be removed", Justification = "As designed.")] +public static partial class Argument; diff --git a/src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs b/src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs new file mode 100644 index 0000000..5b9a19c --- /dev/null +++ b/src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs @@ -0,0 +1,86 @@ +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +/// Provides polyfill extension methods for to support newer .NET APIs in older framework versions. +[SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "As designed.")] +public static class ArgumentExceptionPolyfills +{ + extension(ArgumentException) + { +#if !NET8_0_OR_GREATER + /// Throws an exception if is or empty. + /// The string argument to validate as non- and non-empty. + /// The name of the parameter with which corresponds. + /// is . + /// is empty. + /// + public static void ThrowIfNullOrEmpty( + [NotNull] string? argument, + [CallerArgumentExpression(nameof(argument))] string? paramName = null + ) + { + if (argument is null) + { + throw new ArgumentNullException(paramName); + } + + if (argument.Length == 0) + { + throw new ArgumentException("The value cannot be an empty string.", paramName); + } + } + + /// Throws an exception if is , empty, or consists only of white-space characters. + /// The string argument to validate. + /// The name of the parameter with which corresponds. + /// is . + /// is empty or consists only of white-space characters. + /// + public static void ThrowIfNullOrWhiteSpace( + [NotNull] string? argument, + [CallerArgumentExpression(nameof(argument))] string? paramName = null + ) + { + if (argument is null) + { + throw new ArgumentNullException(paramName); + } + + if (string.IsNullOrWhiteSpace(argument)) + { + throw new ArgumentException( + "The value cannot be an empty string or composed entirely of whitespace.", + paramName + ); + } + } +#endif + + /// Throws an exception if is or empty. + /// The type of objects to enumerate. + /// The enumerable argument to validate as non- and non-empty. + /// The name of the parameter with which corresponds. + /// is . + /// is empty. + public static void ThrowIfNullOrEmpty( + [NotNull] IEnumerable? argument, + [CallerArgumentExpression(nameof(argument))] string? paramName = null + ) + { + if (argument is null) + { + throw new ArgumentNullException(paramName); + } + + if (argument.TryGetNonEnumeratedCount(out var count) && count == 0) + { + throw new ArgumentException("The collection cannot be empty.", paramName); + } + } + } +} diff --git a/src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs b/src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs new file mode 100644 index 0000000..439c678 --- /dev/null +++ b/src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs @@ -0,0 +1,53 @@ +#if !NET9_0_OR_GREATER + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +/// Provides polyfill extension methods for to support newer .NET APIs in older framework versions. +[SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "As designed.")] +public static class ArgumentNullExceptionPolyfills +{ + extension(ArgumentNullException) + { +#if !NET6_0_OR_GREATER + /// Throws an if is . + /// The reference type argument to validate as non-null. + /// The name of the parameter with which corresponds. If you omit this parameter, the name of is used. + /// is . + /// The parameter is included to support the attribute. It's recommended that you don't pass a value for this parameter and let the name of be used instead. + /// + public static void ThrowIfNull( + [NotNull] object? argument, + [CallerArgumentExpression(nameof(argument))] string? paramName = null + ) + { + if (argument is null) + { + throw new ArgumentNullException(paramName); + } + } +#endif + + /// Throws an if is . + /// The pointer argument to validate as non-null. + /// The name of the parameter with which corresponds. + /// is . + /// + public static unsafe void ThrowIfNull( + void* argument, + [CallerArgumentExpression(nameof(argument))] string? paramName = null + ) + { + if (argument is null) + { + throw new ArgumentNullException(paramName); + } + } + } +} +#endif diff --git a/src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs b/src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs new file mode 100644 index 0000000..fca9d8a --- /dev/null +++ b/src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs @@ -0,0 +1,335 @@ +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +using System.Diagnostics.CodeAnalysis; + +/// Provides polyfill extension methods for to support newer .NET APIs in older framework versions. +[SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "As designed.")] +public static class ArgumentOutOfRangeExceptionPolyfills +{ + extension(ArgumentOutOfRangeException) + { +#if !NET8_0_OR_GREATER + /// Throws an if is zero. + /// The type of the object to validate. + /// The argument to validate as non-zero. + /// The name of the parameter with which corresponds. + /// is zero. + /// + public static void ThrowIfZero( + T value, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) +#if NET7_0_OR_GREATER + where T : Numerics.INumberBase + { + if (T.IsZero(value)) + { + ThrowZero(paramName); + } + } +#else + where T : struct, IEquatable + { + if (value.Equals(default)) + { + ThrowZero(paramName); + } + } +#endif + + /// Throws an with a message indicating that a value must not be zero. + /// The name of the parameter with which the value corresponds. + /// Always thrown. + /// + /// + /// This is a helper method used internally by + /// to throw an exception when a zero value is detected. The method is marked with + /// to inform the compiler that it unconditionally throws. + /// + /// + [DoesNotReturn] + private static void ThrowZero(string? paramName) => + throw new ArgumentOutOfRangeException(paramName, "Value must not be zero."); +#endif + +#if !NET8_0_OR_GREATER + /// Throws an if is negative. + /// The type of the object to validate. + /// The argument to validate as non-negative. + /// The name of the parameter with which corresponds. + /// is negative. + /// + public static void ThrowIfNegative( + T value, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) +#if NET7_0_OR_GREATER + where T : Numerics.INumberBase + { + if (T.IsNegative(value)) + { + ThrowNegative(value, paramName); + } + } +#else + where T : struct, IComparable + { + if (value.CompareTo(default) < 0) + { + ThrowNegative(value, paramName); + } + } +#endif + +#if !NET7_0_OR_GREATER + /// Throws an if is negative. + /// The argument to validate as non-negative. + /// The name of the parameter with which corresponds. + /// is negative. + /// + public static void ThrowIfNegative( + nint value, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) + { + if (value < (nint)0) + { + ThrowNegative(value, paramName); + } + } +#endif + + /// Throws an with a message indicating that a value must be non-negative. + /// The type of the value being validated. + /// The value that failed validation. + /// The name of the parameter with which corresponds. + /// Always thrown. + /// + /// + /// This is a helper method used internally by + /// overloads to throw an exception when a negative value is detected. The method is marked with + /// to inform the compiler that it unconditionally throws. + /// + /// + [DoesNotReturn] + private static void ThrowNegative(T value, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, "Value must be non-negative."); + +#endif + +#if !NET8_0_OR_GREATER + /// Throws an if is negative or zero. + /// The type of the object to validate. + /// The argument to validate as non-zero and non-negative. + /// The name of the parameter with which corresponds. + /// is negative or zero. + /// + public static void ThrowIfNegativeOrZero( + T value, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) +#if NET7_0_OR_GREATER + where T : Numerics.INumberBase + { + if (T.IsNegative(value) || T.IsZero(value)) + { + ThrowNegativeOrZero(value, paramName); + } + } +#else + where T : struct, IComparable + { + if (value.CompareTo(default) <= 0) + { + ThrowNegativeOrZero(value, paramName); + } + } +#endif +#endif + +#if !NET7_0_OR_GREATER + /// Throws an if is negative or zero. + /// The argument to validate as non-zero and non-negative. + /// The name of the parameter with which corresponds. + /// is negative or zero. + /// + public static void ThrowIfNegativeOrZero( + nint value, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) + { + if (value <= (nint)0) + { + ThrowNegativeOrZero(value, paramName); + } + } +#endif + + /// Throws an with a message indicating that a value must be non-negative and non-zero. + /// The type of the value being validated. + /// The value that failed validation. + /// The name of the parameter with which corresponds. + /// Always thrown. + /// + /// + /// This is a helper method used internally by the + /// overloads to throw an exception when a negative or zero value is detected. The method is marked with + /// to inform the compiler that it unconditionally throws. + /// + /// + /// The exception message includes the parameter name and the actual value that failed validation + /// to provide detailed diagnostic information. + /// + /// + [DoesNotReturn] + private static void ThrowNegativeOrZero(T value, string? name) => + throw new ArgumentOutOfRangeException( + name, + value, + $"{name} ('{value}') must be a non-negative and non-zero value." + ); + +#if !NET8_0_OR_GREATER + /// Throws an if is equal to . + /// The type of the objects to validate. + /// The argument to validate as not equal to . + /// The value to compare with . + /// The name of the parameter with which corresponds. + /// is equal to . + /// + public static void ThrowIfEqual( + T value, + T other, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) + where T : IEquatable? + { + if (EqualityComparer.Default.Equals(value, other)) + { + throw new ArgumentOutOfRangeException(paramName, value, $"Value must not be equal to {other}."); + } + } +#endif + +#if !NET8_0_OR_GREATER + /// Throws an if is not equal to . + /// The type of the objects to validate. + /// The argument to validate as equal to . + /// The value to compare with . + /// The name of the parameter with which corresponds. + /// is not equal to . + /// + public static void ThrowIfNotEqual( + T value, + T other, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) + where T : IEquatable? + { + if (!EqualityComparer.Default.Equals(value, other)) + { + throw new ArgumentOutOfRangeException(paramName, value, $"Value must be equal to {other}."); + } + } +#endif + +#if !NET8_0_OR_GREATER + /// Throws an if is greater than . + /// The type of the objects to compare. + /// The argument to validate as less than or equal to . + /// The value to compare with . + /// The name of the parameter with which corresponds. + /// is greater than . + /// + public static void ThrowIfGreaterThan( + T value, + T other, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) + where T : IComparable + { + if (value.CompareTo(other) > 0) + { + throw new ArgumentOutOfRangeException( + paramName, + value, + $"Value must be less than or equal to {other}." + ); + } + } +#endif + +#if !NET8_0_OR_GREATER + /// Throws an if is greater than or equal to . + /// The type of the objects to compare. + /// The argument to validate as less than . + /// The value to compare with . + /// The name of the parameter with which corresponds. + /// is greater than or equal to . + /// + public static void ThrowIfGreaterThanOrEqual( + T value, + T other, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) + where T : IComparable + { + if (value.CompareTo(other) >= 0) + { + throw new ArgumentOutOfRangeException(paramName, value, $"Value must be less than {other}."); + } + } +#endif + +#if !NET8_0_OR_GREATER + /// Throws an if is less than . + /// The type of the objects to compare. + /// The argument to validate as greater than or equal to . + /// The value to compare with . + /// The name of the parameter with which corresponds. + /// is less than . + /// + public static void ThrowIfLessThan( + T value, + T other, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) + where T : IComparable + { + if (value.CompareTo(other) < 0) + { + throw new ArgumentOutOfRangeException( + paramName, + value, + $"Value must be greater than or equal to {other}." + ); + } + } +#endif + +#if !NET8_0_OR_GREATER + /// Throws an if is less than or equal to . + /// The type of the objects to compare. + /// The argument to validate as greater than . + /// The value to compare with . + /// The name of the parameter with which corresponds. + /// is less than or equal to . + /// + public static void ThrowIfLessThanOrEqual( + T value, + T other, + [Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null + ) + where T : IComparable + { + if (value.CompareTo(other) <= 0) + { + throw new ArgumentOutOfRangeException(paramName, value, $"Value must be greater than {other}."); + } + } +#endif + } +} diff --git a/src/NetEvolve.Arguments/Argument_EMPTY.cs b/src/NetEvolve.Arguments/Argument_EMPTY.cs deleted file mode 100644 index 3f5175b..0000000 --- a/src/NetEvolve.Arguments/Argument_EMPTY.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace NetEvolve.Arguments; - -public static partial class Argument { } diff --git a/src/NetEvolve.Arguments/Argument_ThrowArgumentException.cs b/src/NetEvolve.Arguments/Argument_ThrowArgumentException.cs deleted file mode 100644 index d146d78..0000000 --- a/src/NetEvolve.Arguments/Argument_ThrowArgumentException.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NetEvolve.Arguments; - -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -public static partial class Argument -{ - /// Throws an . - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - [StackTraceHidden] - private static void ThrowArgumentException(string? paramName, string? message = null) - { - throw new ArgumentException(message, paramName); - } -} diff --git a/src/NetEvolve.Arguments/Argument_ThrowArgumentNullException.cs b/src/NetEvolve.Arguments/Argument_ThrowArgumentNullException.cs deleted file mode 100644 index 51e959d..0000000 --- a/src/NetEvolve.Arguments/Argument_ThrowArgumentNullException.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NetEvolve.Arguments; - -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -public static partial class Argument -{ - /// Throws an . - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - [StackTraceHidden] - private static void ThrowArgumentNullException(string? paramName, string? message = null) - { - throw new ArgumentNullException(paramName, message); - } -} diff --git a/src/NetEvolve.Arguments/Argument_ThrowArgumentOutOfRangeException.cs b/src/NetEvolve.Arguments/Argument_ThrowArgumentOutOfRangeException.cs deleted file mode 100644 index 40cab8d..0000000 --- a/src/NetEvolve.Arguments/Argument_ThrowArgumentOutOfRangeException.cs +++ /dev/null @@ -1,20 +0,0 @@ -#if !NET8_0_OR_GREATER -namespace NetEvolve.Arguments; - -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -public static partial class Argument -{ - /// Throws an . - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - [StackTraceHidden] - private static void ThrowArgumentOutOfRangeException(string? paramName, T value, string? message = null) - { - throw new ArgumentOutOfRangeException(paramName, value, message); - } -} -#endif diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs b/src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs index dc41779..18324ed 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs @@ -1,7 +1,6 @@ namespace NetEvolve.Arguments; using System; -using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -11,31 +10,14 @@ public static partial class Argument /// The argument to validate as not equal to . /// The value to compare with . /// The name of the parameter with which corresponds. + [Obsolete("Use ArgumentOutOfRangeException.ThrowIfEqual instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET8_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfEqual( T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null ) - where T : IEquatable - { -#if NET8_0_OR_GREATER - ArgumentOutOfRangeException.ThrowIfEqual(value, other, paramName); -#else - if (EqualityComparer.Default.Equals(value, other)) - { - ThrowArgumentOutOfRangeException( - paramName, - value, - $"{paramName} ('{value}') must not be equal to '{other}'." - ); - } -#endif - } + where T : IEquatable => ArgumentOutOfRangeException.ThrowIfEqual(value, other, paramName); } diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs b/src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs index d939073..9e4fb0e 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs @@ -10,31 +10,14 @@ public static partial class Argument /// The argument to validate as less or equal than . /// The value to compare with . /// The name of the parameter with which corresponds. + [Obsolete("Use ArgumentOutOfRangeException.ThrowIfGreaterThan instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET8_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfGreaterThan( T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null ) - where T : IComparable - { -#if NET8_0_OR_GREATER - ArgumentOutOfRangeException.ThrowIfGreaterThan(value, other, paramName); -#else - if (value.CompareTo(other) > 0) - { - ThrowArgumentOutOfRangeException( - paramName, - value, - $"{paramName} ('{value}') must be less than or equal to '{other}'." - ); - } -#endif - } + where T : IComparable => ArgumentOutOfRangeException.ThrowIfGreaterThan(value, other, paramName); } diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs b/src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs index eac31a6..4357800 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs @@ -10,27 +10,14 @@ public static partial class Argument /// The argument to validate as less than . /// The value to compare with . /// The name of the parameter with which corresponds. + [Obsolete("Use ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET8_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfGreaterThanOrEqual( T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null ) - where T : IComparable - { -#if NET8_0_OR_GREATER - ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(value, other, paramName); -#else - if (value.CompareTo(other) >= 0) - { - ThrowArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be less than '{other}'."); - } -#endif - } + where T : IComparable => ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(value, other, paramName); } diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs b/src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs index a263962..cd0bf57 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs @@ -10,31 +10,14 @@ public static partial class Argument /// The argument to validate as greatar than or equal than . /// The value to compare with . /// The name of the parameter with which corresponds. + [Obsolete("Use ArgumentOutOfRangeException.ThrowIfLessThan instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET8_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfLessThan( T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null ) - where T : IComparable - { -#if NET8_0_OR_GREATER - ArgumentOutOfRangeException.ThrowIfLessThan(value, other, paramName); -#else - if (value.CompareTo(other) < 0) - { - ThrowArgumentOutOfRangeException( - paramName, - value, - $"{paramName} ('{value}') must be greater than '{other}'." - ); - } -#endif - } + where T : IComparable => ArgumentOutOfRangeException.ThrowIfLessThan(value, other, paramName); } diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs b/src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs index 9db4f09..adc0c08 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs @@ -7,34 +7,17 @@ public static partial class Argument { /// Throws an if is less than or equal . - /// The argument to validate as greatar than than . + /// The argument to validate as greatar than . /// The value to compare with . /// The name of the parameter with which corresponds. + [Obsolete("Use ArgumentOutOfRangeException.ThrowIfLessThanOrEqual instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET8_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfLessThanOrEqual( T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null ) - where T : IComparable - { -#if NET8_0_OR_GREATER - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, other, paramName); -#else - if (value.CompareTo(other) <= 0) - { - ThrowArgumentOutOfRangeException( - paramName, - value, - $"{paramName} ('{value}') must be greater than or equal to '{other}'." - ); - } -#endif - } + where T : IComparable => ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, other, paramName); } diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs b/src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs index dd36012..d9e995a 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs @@ -11,27 +11,14 @@ public static partial class Argument /// The argument to validate as equal to . /// The value to compare with . /// The name of the parameter with which corresponds. + [Obsolete("Use ArgumentOutOfRangeException.ThrowIfNotEqual instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET8_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfNotEqual( T value, T other, [CallerArgumentExpression(nameof(value))] string? paramName = null ) - where T : IEquatable - { -#if NET8_0_OR_GREATER - ArgumentOutOfRangeException.ThrowIfNotEqual(value, other, paramName); -#else - if (!EqualityComparer.Default.Equals(value, other)) - { - ThrowArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be equal to '{other}'."); - } -#endif - } + where T : IEquatable => ArgumentOutOfRangeException.ThrowIfNotEqual(value, other, paramName); } diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfNull.cs b/src/NetEvolve.Arguments/Argument_ThrowIfNull.cs index 10e7050..f1a5e63 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfNull.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfNull.cs @@ -10,52 +10,26 @@ public static partial class Argument /// Throws an if is null. /// The reference type argument to validate as non-null. /// The name of the parameter with which corresponds. + [Obsolete("Use ArgumentNullException.ThrowIfNull instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET6_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfNull( [NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null - ) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(argument, paramName); -#else - if (argument is null) - { - ThrowArgumentNullException(paramName); - } -#endif - } + ) => ArgumentNullException.ThrowIfNull(argument, paramName); /// Throws an if is null. /// The reference type argument to validate as non-null. /// The name of the parameter with which corresponds. + [Obsolete("Use ArgumentNullException.ThrowIfNull instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET7_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif #pragma warning disable S6640 // Make sure that using "unsafe" is safe here. public static unsafe void ThrowIfNull( #pragma warning restore S6640 // Make sure that using "unsafe" is safe here. [NotNull] void* argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null - ) - { -#if NET7_0_OR_GREATER - ArgumentNullException.ThrowIfNull(argument, paramName); -#else - if (argument == null) - { - ThrowArgumentNullException(paramName); - } -#endif - } + ) => ArgumentNullException.ThrowIfNull(argument, paramName); } diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs b/src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs index 3462339..d1a58d5 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs @@ -14,54 +14,26 @@ public static partial class Argument /// The name of the parameter with which corresponds. /// is null. /// is empty. + [Obsolete("Use ArgumentException.ThrowIfNullOrEmpty instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET7_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfNullOrEmpty( [NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null - ) - { -#if NET7_0_OR_GREATER - ArgumentException.ThrowIfNullOrEmpty(argument, paramName); -#else - if (argument is null) - { - ThrowArgumentNullException(paramName); - } - - if (string.IsNullOrEmpty(argument)) - { - ThrowArgumentException(paramName); - } -#endif - } + ) => ArgumentException.ThrowIfNullOrEmpty(argument, paramName); /// Throws an exception if is null or empty. /// The string argument to validate as non-null and non-empty. /// The name of the parameter with which corresponds. /// is null. /// is empty. + [Obsolete("Use ArgumentException.ThrowIfNullOrEmpty instead.")] [DebuggerStepThrough] [StackTraceHidden] [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowIfNullOrEmpty( [NotNull] IEnumerable argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null - ) - { - if (argument is null) - { - ThrowArgumentNullException(paramName); - } - - if (argument.TryGetNonEnumeratedCount(out var count) && count == 0) - { - ThrowArgumentException(paramName); - } - } + ) => ArgumentException.ThrowIfNullOrEmpty(argument, paramName); } diff --git a/src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs b/src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs index 7a44487..783ea1a 100644 --- a/src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs +++ b/src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs @@ -12,30 +12,12 @@ public static partial class Argument /// The name of the parameter with which corresponds. /// is null. /// is empty or consists only of white-space characters. + [Obsolete("Use ArgumentException.ThrowIfNullOrWhiteSpace instead.")] [DebuggerStepThrough] [StackTraceHidden] -#if NET8_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] -#else - [MethodImpl(MethodImplOptions.NoInlining)] -#endif public static void ThrowIfNullOrWhiteSpace( [NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null - ) - { -#if NET8_0_OR_GREATER - ArgumentException.ThrowIfNullOrWhiteSpace(argument, paramName); -#else - if (argument is null) - { - ThrowArgumentNullException(paramName); - } - - if (string.IsNullOrWhiteSpace(argument)) - { - ThrowArgumentException(paramName); - } -#endif - } + ) => ArgumentException.ThrowIfNullOrWhiteSpace(argument, paramName); } diff --git a/src/NetEvolve.Arguments/NetEvolve.Arguments.csproj b/src/NetEvolve.Arguments/NetEvolve.Arguments.csproj index 817ca5a..cb04da8 100644 --- a/src/NetEvolve.Arguments/NetEvolve.Arguments.csproj +++ b/src/NetEvolve.Arguments/NetEvolve.Arguments.csproj @@ -1,21 +1,12 @@ - netstandard2.0;net8.0;net9.0;net10.0 - A library that provides compatible `ThrowIf` methods for .NET / C# for older runtimes. + $(_ProjectTargetFrameworks) + A universal polyfill library that provides modern ArgumentNullException.ThrowIf* and ArgumentException.ThrowIf* helper methods across all .NET runtimes (.NET Standard 2.0+, .NET Framework 4.6.2+, .NET 6.0+), enabling consistent argument validation patterns regardless of target framework version. true $(MSBuildProjectName) https://github.com/dailydevops/arguments https://github.com/dailydevops/arguments.git - guard;clausel;exceptions + guard;clause;exceptions;argument-validation;polyfill;null-check;parameter-validation;defensive-programming;argument-exception;argumentnullexception $(PackageProjectUrl)/releases/ - 2023 - - true - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs b/src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs new file mode 100644 index 0000000..57c1e6e --- /dev/null +++ b/src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs @@ -0,0 +1,34 @@ +#if !NETCOREAPP3_0_OR_GREATER + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Runtime.CompilerServices; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +using Diagnostics; +using Diagnostics.CodeAnalysis; + +/// +/// Indicates that a parameter captures the expression passed for another parameter as a string. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class CallerArgumentExpressionAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + public CallerArgumentExpressionAttribute(string parameterName) => ParameterName = parameterName; + + /// + /// Gets the name of the parameter whose expression should be captured as a string. + /// + public string ParameterName { get; } +} + +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(CallerArgumentExpressionAttribute))] +#endif diff --git a/src/NetEvolve.Arguments/Polyfills/DoesNotReturnAttribute.cs b/src/NetEvolve.Arguments/Polyfills/DoesNotReturnAttribute.cs new file mode 100644 index 0000000..6faf68a --- /dev/null +++ b/src/NetEvolve.Arguments/Polyfills/DoesNotReturnAttribute.cs @@ -0,0 +1,19 @@ +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Diagnostics.CodeAnalysis; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +/// +/// Specifies that a method that will never return under any circumstance. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(validOn: AttributeTargets.Method, Inherited = false)] +internal sealed class DoesNotReturnAttribute : Attribute; +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute))] +#endif diff --git a/src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs b/src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs new file mode 100644 index 0000000..453cdae --- /dev/null +++ b/src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs @@ -0,0 +1,34 @@ +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Diagnostics.CodeAnalysis; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +/// +/// Specifies that the method will not return if the associated +/// parameter is passed the specified value. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class DoesNotReturnIfAttribute : Attribute +{ + /// + /// Gets the condition parameter value. + /// Code after the method is considered unreachable by diagnostics if the argument + /// to the associated parameter matches this value. + /// + public bool ParameterValue { get; } + + /// + /// Initializes a new instance of the + /// class with the specified parameter value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; +} +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute))] +#endif diff --git a/src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs b/src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs new file mode 100644 index 0000000..847f5e6 --- /dev/null +++ b/src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs @@ -0,0 +1,56 @@ +#if !NET6_0_OR_GREATER + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Linq; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal static class IEnumerableExtensions +{ + /// Attempts to determine the number of elements in a sequence without forcing an enumeration. + /// The type of elements in the source sequence. + /// The source sequence to get the count from. + /// The count of elements in the sequence, if available; otherwise, 0. + /// + /// if the number of elements could be determined without enumeration; + /// otherwise, . + /// + /// + /// + /// This method attempts to determine the number of elements in a sequence without forcing + /// an enumeration by checking for common collection implementations such as + /// and . + /// + /// + /// If the sequence does not implement one of these interfaces or the count is not immediately + /// available, the method returns and sets + /// to 0. + /// + /// + /// + public static bool TryGetNonEnumeratedCount(this IEnumerable target, out int count) + { + if (target is ICollection genericCollection) + { + count = genericCollection.Count; + return true; + } + + if (target is ICollection collection) + { + count = collection.Count; + return true; + } + + count = 0; + return false; + } +} +#endif diff --git a/src/NetEvolve.Arguments/Polyfills/ModuleInitializerAttribute.cs b/src/NetEvolve.Arguments/Polyfills/ModuleInitializerAttribute.cs new file mode 100644 index 0000000..e15e4ef --- /dev/null +++ b/src/NetEvolve.Arguments/Polyfills/ModuleInitializerAttribute.cs @@ -0,0 +1,25 @@ +#if !NET5_0_OR_GREATER + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Runtime.CompilerServices; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using static AttributeTargets; + +/// +/// Used to indicate to the compiler that a method should be called +/// in its containing module's initializer. +/// +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(validOn: Method, Inherited = false)] +internal sealed class ModuleInitializerAttribute : Attribute; +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(ModuleInitializerAttribute))] +#endif diff --git a/src/NetEvolve.Arguments/Polyfills/NotNullAttribute.cs b/src/NetEvolve.Arguments/Polyfills/NotNullAttribute.cs new file mode 100644 index 0000000..43ea81f --- /dev/null +++ b/src/NetEvolve.Arguments/Polyfills/NotNullAttribute.cs @@ -0,0 +1,22 @@ +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Diagnostics.CodeAnalysis; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +using static AttributeTargets; + +/// +/// Specifies that an output is not even if the +/// corresponding type allows it. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(validOn: Field | Parameter | Property | ReturnValue)] +internal sealed class NotNullAttribute : Attribute; +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.NotNullAttribute))] +#endif diff --git a/src/NetEvolve.Arguments/Polyfills/StackTraceHiddenAttribute.cs b/src/NetEvolve.Arguments/Polyfills/StackTraceHiddenAttribute.cs new file mode 100644 index 0000000..9ffdcbc --- /dev/null +++ b/src/NetEvolve.Arguments/Polyfills/StackTraceHiddenAttribute.cs @@ -0,0 +1,22 @@ +#if !NET6_0_OR_GREATER +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Diagnostics; + +#pragma warning restore IDE0130 // Namespace does not match folder structure + +using System.Diagnostics.CodeAnalysis; +using static AttributeTargets; + +/// +/// Types and Methods attributed with StackTraceHidden will be omitted from the stack trace text shown in StackTrace.ToString() +/// and Exception.StackTrace +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(validOn: Class | Method | Constructor | Struct, Inherited = false)] +internal sealed class StackTraceHiddenAttribute : Attribute; +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.StackTraceHiddenAttribute))] +#endif diff --git a/tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs b/tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs new file mode 100644 index 0000000..e47c858 --- /dev/null +++ b/tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs @@ -0,0 +1,155 @@ +namespace NetEvolve.Arguments.Tests.Unit; + +using System; +using System.Collections.Generic; + +public sealed class ArgumentExceptionPolyfillsTests +{ + [Test] + public void ThrowIfNullOrEmpty_String_WhenArgumentIsNull_ThrowsArgumentNullException() + { + // Arrange + string? argument = null; + + // Act + void Act() => ArgumentException.ThrowIfNullOrEmpty(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public void ThrowIfNullOrEmpty_String_WhenArgumentIsEmpty_ThrowsArgumentException() + { + // Arrange + var argument = string.Empty; + + // Act + void Act() => ArgumentException.ThrowIfNullOrEmpty(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public async Task ThrowIfNullOrEmpty_String_WhenArgumentIsValid_DoesNotThrow() + { + // Arrange + var argument = "valid"; + + // Act & Assert + ArgumentException.ThrowIfNullOrEmpty(argument); + _ = await Assert.That(argument).IsNotNullOrWhiteSpace(); + } + + [Test] + public void ThrowIfNullOrWhiteSpace_WhenArgumentIsNull_ThrowsArgumentNullException() + { + // Arrange + string? argument = null; + + // Act + void Act() => ArgumentException.ThrowIfNullOrWhiteSpace(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public void ThrowIfNullOrWhiteSpace_WhenArgumentIsEmpty_ThrowsArgumentException() + { + // Arrange + var argument = string.Empty; + + // Act + void Act() => ArgumentException.ThrowIfNullOrWhiteSpace(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public void ThrowIfNullOrWhiteSpace_WhenArgumentIsWhiteSpace_ThrowsArgumentException() + { + // Arrange + var argument = " "; + + // Act + void Act() => ArgumentException.ThrowIfNullOrWhiteSpace(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + [Arguments("valid")] + [Arguments("test")] + [Arguments("a")] + public async Task ThrowIfNullOrWhiteSpace_WhenArgumentIsValid_DoesNotThrow(string argument) + { + // Act & Assert + ArgumentException.ThrowIfNullOrWhiteSpace(argument); + _ = await Assert.That(argument).IsNotNullOrWhiteSpace(); + } + + [Test] + public void ThrowIfNullOrEmpty_Enumerable_WhenArgumentIsNull_ThrowsArgumentNullException() + { + // Arrange + IEnumerable? argument = null; + + // Act + void Act() => ArgumentException.ThrowIfNullOrEmpty(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public void ThrowIfNullOrEmpty_Enumerable_WhenArgumentIsEmptyArray_ThrowsArgumentException() + { + // Arrange + var argument = Array.Empty(); + + // Act + void Act() => ArgumentException.ThrowIfNullOrEmpty(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public void ThrowIfNullOrEmpty_Enumerable_WhenArgumentIsEmptyList_ThrowsArgumentException() + { + // Arrange + var argument = new List(); + + // Act + void Act() => ArgumentException.ThrowIfNullOrEmpty(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public async Task ThrowIfNullOrEmpty_Enumerable_WhenArgumentHasElements_DoesNotThrow() + { + // Arrange + var argument = new[] { 1, 2, 3 }; + + // Act & Assert + ArgumentException.ThrowIfNullOrEmpty(argument); + _ = await Assert.That(argument).HasCount().EqualTo(3); + } + + [Test] + public async Task ThrowIfNullOrEmpty_Enumerable_WhenArgumentIsSingleElement_DoesNotThrow() + { + // Arrange + var argument = new List { "item" }; + + // Act & Assert + ArgumentException.ThrowIfNullOrEmpty(argument); + _ = await Assert.That(argument).HasCount().EqualTo(1); + } +} diff --git a/tests/NetEvolve.Arguments.Tests.Unit/ArgumentNullExceptionPolyfillsTests.cs b/tests/NetEvolve.Arguments.Tests.Unit/ArgumentNullExceptionPolyfillsTests.cs new file mode 100644 index 0000000..6ec56ba --- /dev/null +++ b/tests/NetEvolve.Arguments.Tests.Unit/ArgumentNullExceptionPolyfillsTests.cs @@ -0,0 +1,97 @@ +namespace NetEvolve.Arguments.Tests.Unit; + +using System; + +public sealed class ArgumentNullExceptionPolyfillsTests +{ + [Test] + public void ThrowIfNull_Object_WhenArgumentIsNull_ThrowsArgumentNullException() + { + // Arrange + object? argument = null; + + // Act + void Act() => ArgumentNullException.ThrowIfNull(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public async Task ThrowIfNull_Object_WhenArgumentIsNotNull_DoesNotThrow() + { + // Arrange + var argument = new object(); + + // Act & Assert + ArgumentNullException.ThrowIfNull(argument); + } + + [Test] + public void ThrowIfNull_String_WhenArgumentIsNull_ThrowsArgumentNullException() + { + // Arrange + string? argument = null; + + // Act + void Act() => ArgumentNullException.ThrowIfNull(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public async Task ThrowIfNull_String_WhenArgumentIsNotNull_DoesNotThrow() + { + // Arrange + var argument = "test"; + + // Act & Assert + ArgumentNullException.ThrowIfNull(argument); + _ = await Assert.That(argument).IsNotNull(); + } + + [Test] + public unsafe void ThrowIfNull_Pointer_WhenArgumentIsNull_ThrowsArgumentNullException() + { + // Arrange + int* argument = null; + + // Act + void Act() => ArgumentNullException.ThrowIfNull(argument); + + // Assert + _ = Assert.Throws("argument", Act); + } + + [Test] + public unsafe void ThrowIfNull_Pointer_WhenArgumentIsNotNull_DoesNotThrow() + { + // Arrange + var value = 42; + var argument = &value; + + // Act & Assert + ArgumentNullException.ThrowIfNull(argument); + } + + [Test] + public async Task ThrowIfNull_ReferenceType_WhenArgumentIsNotNull_DoesNotThrow() + { + // Arrange + var argument = new object(); + + // Act & Assert + ArgumentNullException.ThrowIfNull(argument); + } + + [Test] + public async Task ThrowIfNull_ValueType_WhenArgumentHasValue_DoesNotThrow() + { + // Arrange + var argument = (object)123; + + // Act & Assert + ArgumentNullException.ThrowIfNull(argument); + } +} diff --git a/tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeExceptionPolyfillsTests.cs b/tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeExceptionPolyfillsTests.cs new file mode 100644 index 0000000..5e2664d --- /dev/null +++ b/tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeExceptionPolyfillsTests.cs @@ -0,0 +1,417 @@ +namespace NetEvolve.Arguments.Tests.Unit; + +using System; + +public sealed class ArgumentOutOfRangeExceptionPolyfillsTests +{ + [Test] + public void ThrowIfZero_Int_WhenValueIsZero_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = 0; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfZero(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + [Arguments(1)] + [Arguments(-1)] + [Arguments(100)] + [Arguments(-100)] + public async Task ThrowIfZero_Int_WhenValueIsNotZero_DoesNotThrow(int value) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfZero(value); + _ = await Assert.That(value).IsNotEqualTo(0); + } + + [Test] + public void ThrowIfZero_Double_WhenValueIsZero_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = 0.0; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfZero(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + [Arguments(1.5)] + [Arguments(-1.5)] + public async Task ThrowIfZero_Double_WhenValueIsNotZero_DoesNotThrow(double value) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfZero(value); + _ = await Assert.That(value).IsNotEqualTo(0.0); + } + + [Test] + public void ThrowIfNegative_Int_WhenValueIsNegative_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = -1; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNegative(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + [Arguments(0)] + [Arguments(1)] + [Arguments(100)] + public async Task ThrowIfNegative_Int_WhenValueIsNonNegative_DoesNotThrow(int value) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfNegative(value); + _ = await Assert.That(value).IsGreaterThanOrEqualTo(0); + } + + [Test] + public void ThrowIfNegative_Double_WhenValueIsNegative_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = -1.5; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNegative(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + [Arguments(0.0)] + [Arguments(1.5)] + public async Task ThrowIfNegative_Double_WhenValueIsNonNegative_DoesNotThrow(double value) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfNegative(value); + _ = await Assert.That(value).IsGreaterThanOrEqualTo(0.0); + } + + [Test] + public void ThrowIfNegative_NInt_WhenValueIsNegative_ThrowsArgumentOutOfRangeException() + { + // Arrange + nint value = -1; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNegative(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + public async Task ThrowIfNegative_NInt_WhenValueIsNonNegative_DoesNotThrow() + { + // Arrange + nint value = 0; + + // Act & Assert + ArgumentOutOfRangeException.ThrowIfNegative(value); + } + + [Test] + [Arguments(0)] + [Arguments(-1)] + [Arguments(-100)] + public void ThrowIfNegativeOrZero_Int_WhenValueIsNegativeOrZero_ThrowsArgumentOutOfRangeException(int value) + { + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); + + // Assert + _ = Assert.Throws(nameof(value), Act); + } + + [Test] + [Arguments(1)] + [Arguments(100)] + public async Task ThrowIfNegativeOrZero_Int_WhenValueIsPositive_DoesNotThrow(int value) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); + _ = await Assert.That(value).IsGreaterThan(0); + } + + [Test] + public void ThrowIfNegativeOrZero_Double_WhenValueIsZero_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = 0.0; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + public void ThrowIfNegativeOrZero_Double_WhenValueIsNegative_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = -1.5; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + [Arguments(0.1)] + [Arguments(1.5)] + public async Task ThrowIfNegativeOrZero_Double_WhenValueIsPositive_DoesNotThrow(double value) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); + _ = await Assert.That(value).IsGreaterThan(0.0); + } + + [Test] + public void ThrowIfNegativeOrZero_NInt_WhenValueIsZero_ThrowsArgumentOutOfRangeException() + { + // Arrange + nint value = 0; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + public void ThrowIfNegativeOrZero_NInt_WhenValueIsNegative_ThrowsArgumentOutOfRangeException() + { + // Arrange + nint value = -1; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + public async Task ThrowIfNegativeOrZero_NInt_WhenValueIsPositive_DoesNotThrow() + { + // Arrange + nint value = 1; + + // Act & Assert + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); + } + + [Test] + public void ThrowIfEqual_Int_WhenValuesAreEqual_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = 5; + var other = 5; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfEqual(value, other); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + [Arguments(1, 2)] + [Arguments(10, 5)] + [Arguments(-1, 1)] + public async Task ThrowIfEqual_Int_WhenValuesAreNotEqual_DoesNotThrow(int value, int other) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfEqual(value, other); + _ = await Assert.That(value).IsNotEqualTo(other); + } + + [Test] + public void ThrowIfEqual_String_WhenValuesAreEqual_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = "test"; + var other = "test"; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfEqual(value, other); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + public async Task ThrowIfEqual_String_WhenValuesAreNotEqual_DoesNotThrow() + { + // Arrange + var value = "test1"; + var other = "test2"; + + // Act & Assert + ArgumentOutOfRangeException.ThrowIfEqual(value, other); + _ = await Assert.That(value).IsNotEqualTo(other); + } + + [Test] + [Arguments(1, 2)] + [Arguments(10, 5)] + [Arguments(-1, 1)] + public void ThrowIfNotEqual_Int_WhenValuesAreNotEqual_ThrowsArgumentOutOfRangeException(int value, int other) + { + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNotEqual(value, other); + + // Assert + _ = Assert.Throws(nameof(value), Act); + } + + [Test] + public async Task ThrowIfNotEqual_Int_WhenValuesAreEqual_DoesNotThrow() + { + // Arrange + var value = 5; + var other = 5; + + // Act & Assert + ArgumentOutOfRangeException.ThrowIfNotEqual(value, other); + _ = await Assert.That(value).IsEqualTo(other); + } + + [Test] + public void ThrowIfNotEqual_String_WhenValuesAreNotEqual_ThrowsArgumentOutOfRangeException() + { + // Arrange + var value = "test1"; + var other = "test2"; + + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfNotEqual(value, other); + + // Assert + _ = Assert.Throws("value", Act); + } + + [Test] + public async Task ThrowIfNotEqual_String_WhenValuesAreEqual_DoesNotThrow() + { + // Arrange + var value = "test"; + var other = "test"; + + // Act & Assert + ArgumentOutOfRangeException.ThrowIfNotEqual(value, other); + _ = await Assert.That(value).IsEqualTo(other); + } + + [Test] + [Arguments(6, 5)] + [Arguments(100, 50)] + public void ThrowIfGreaterThan_Int_WhenValueIsGreater_ThrowsArgumentOutOfRangeException(int value, int other) + { + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfGreaterThan(value, other); + + // Assert + _ = Assert.Throws(nameof(value), Act); + } + + [Test] + [Arguments(5, 5)] + [Arguments(4, 5)] + [Arguments(1, 100)] + public async Task ThrowIfGreaterThan_Int_WhenValueIsNotGreater_DoesNotThrow(int value, int other) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, other); + _ = await Assert.That(value).IsLessThanOrEqualTo(other); + } + + [Test] + [Arguments(6, 5)] + [Arguments(5, 5)] + public void ThrowIfGreaterThanOrEqual_Int_WhenValueIsGreaterOrEqual_ThrowsArgumentOutOfRangeException( + int value, + int other + ) + { + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(value, other); + + // Assert + _ = Assert.Throws(nameof(value), Act); + } + + [Test] + [Arguments(4, 5)] + [Arguments(1, 100)] + public async Task ThrowIfGreaterThanOrEqual_Int_WhenValueIsLess_DoesNotThrow(int value, int other) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(value, other); + _ = await Assert.That(value).IsLessThan(other); + } + + [Test] + [Arguments(4, 5)] + [Arguments(1, 100)] + public void ThrowIfLessThan_Int_WhenValueIsLess_ThrowsArgumentOutOfRangeException(int value, int other) + { + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfLessThan(value, other); + + // Assert + _ = Assert.Throws(nameof(value), Act); + } + + [Test] + [Arguments(5, 5)] + [Arguments(6, 5)] + [Arguments(100, 1)] + public async Task ThrowIfLessThan_Int_WhenValueIsNotLess_DoesNotThrow(int value, int other) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfLessThan(value, other); + _ = await Assert.That(value).IsGreaterThanOrEqualTo(other); + } + + [Test] + [Arguments(4, 5)] + [Arguments(5, 5)] + public void ThrowIfLessThanOrEqual_Int_WhenValueIsLessOrEqual_ThrowsArgumentOutOfRangeException( + int value, + int other + ) + { + // Act + void Act() => ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, other); + + // Assert + _ = Assert.Throws(nameof(value), Act); + } + + [Test] + [Arguments(6, 5)] + [Arguments(100, 1)] + public async Task ThrowIfLessThanOrEqual_Int_WhenValueIsGreater_DoesNotThrow(int value, int other) + { + // Act & Assert + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, other); + _ = await Assert.That(value).IsGreaterThan(other); + } +} diff --git a/tests/NetEvolve.Arguments.Tests.Unit/ArgumentTests.cs b/tests/NetEvolve.Arguments.Tests.Unit/ArgumentTests.cs deleted file mode 100644 index 74ec165..0000000 --- a/tests/NetEvolve.Arguments.Tests.Unit/ArgumentTests.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace NetEvolve.Arguments.Tests.Unit; - -using System.Diagnostics.CodeAnalysis; -using NetEvolve.Extensions.TUnit; - -[ExcludeFromCodeCoverage] -[UnitTest] -public sealed partial class ArgumentTests { } diff --git a/tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj b/tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj index 3167f14..4d3f840 100644 --- a/tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj +++ b/tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj @@ -1,9 +1,10 @@  - net8.0;net9.0;net10.0 + $(_TestTargetFrameworks) true - $(NoWarn);NU1701 + $(NoWarn);NU1701;CS0618 true + Exe @@ -14,4 +15,15 @@ + + + + + x64 + win-x64 + + + x86 + win-x86 +