You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Plan 0168 addressed parent issue #162 (point 5) by adding concrete MustBePositive overloads for the integral types previously covered only by the modern generic INumber<T> API. The other four sign-guard families remain asymmetric: their .NET Standard assets and portable source exports omit smaller signed integral types and, where the predicate remains meaningful, unsigned integral types, although the .NET 10 generic overloads already define their behavior.
Extend the concrete integral surface of MustBeNegative, MustNotBeNegative, MustNotBePositive, and MustNotBeZero on every supported target while preserving the existing generic APIs and exception contracts. Omit unsigned overloads whose predicate would be constant rather than adding APIs that either always fail or cannot validate anything.
Acceptance Criteria
MustBeNegative and MustNotBeNegative each have default and custom-exception-factory overloads for sbyte and short, but do not add concrete unsigned overloads whose checks would always fail or always succeed.
MustNotBePositive and MustNotBeZero each have default and custom-exception-factory overloads for sbyte, byte, short, ushort, uint, and ulong.
Every new overload is available on .NET Standard 2.0, .NET Standard 2.1, and .NET 10, returns the original exactly typed value when its predicate succeeds, and matches the corresponding existing .NET 10 generic semantics.
Default failures preserve each guard family's existing ArgumentOutOfRangeException contract, including caller-argument-expression parameter names, optional custom messages, and the standard generated message containing the rejected value.
Each new factory overload passes the original value with its exact integral type, invokes the factory only when validation fails, propagates the factory's exception, and throws ArgumentNullException through the existing Throw.CustomException convention when a failing check receives a null factory.
Automated tests cover signed and applicable unsigned boundary behavior for every new overload, return values, exception parameter names and messages, factory arguments and exceptions, factories not invoked on success, and null-factory failures.
Source-export tests verify the exact per-family concrete type matrix in portable and modern exports, including the deliberate omission of unsigned MustBeNegative and MustNotBeNegative overloads; generic INumber<T> overloads appear only in modern exports; and every custom-exception-factory overload is trimmed when that option is disabled.
The committed .NET Standard 2.0 Light.GuardClauses.SingleFile.cs distribution is regenerated with the expanded APIs, and generated source validates for both supported source-export targets.
The sign-guard documentation describes the per-family concrete integral surface, and the package release notes mention the expanded support across all five sign-guard families.
The complete solution restores and builds without warnings in Release configuration, and all automated tests pass on the pinned SDK.
Technical Details
Add the concrete overload pairs outside the NET8_0_OR_GREATER regions according to this matrix:
Guard
New concrete types
Predicate
MustBeNegative
sbyte, short
Less than zero
MustNotBeNegative
sbyte, short
Greater than or equal to zero
MustNotBePositive
sbyte, byte, short, ushort, uint, ulong
Less than or equal to zero
MustNotBeZero
sbyte, byte, short, ushort, uint, ulong
Not equal to zero
Unsigned overloads are appropriate for MustNotBePositive, where zero succeeds and positive values fail, and MustNotBeZero, where zero fails and positive values succeed. Do not add concrete unsigned overloads to MustBeNegative, which could never succeed, or MustNotBeNegative, which could never fail.
For each listed guard and type, follow the annotations, XML documentation, parameter ordering, and implementation conventions of the corresponding existing int and long pair. The following is an illustrative shape for the concrete APIs, not a new generic API:
Compare values directly to type-appropriate zero without widening to int or long, so return values, throw-helper inputs, and factory arguments retain their exact types. The generic throw helpers already support these types, so no new exception or Throw API is required.
Keep all INumber<T> overloads under NET8_0_OR_GREATER. Normal modern calls for types in the matrix should resolve to the new concrete overloads, while explicitly generic calls, deliberately omitted unsigned combinations, and numeric types such as Half remain supported by the generic surface with identical predicates. Retain focused tests for that distinction. Do not add nint or nuint overloads.
Extend the four guard-specific test classes and NumericCustomFactorySuccessTests with the new concrete matrix, retaining focused modern tests for explicitly generic calls and types without concrete overloads. Strengthen SourceFileMergerWhitelistTests.SignGuardWhitelistsUseTargetSpecificSurface to cover every sign-guard family and both factory settings, update docs/assertion-overview.md and src/Directory.Build.props, and regenerate the root Light.GuardClauses.SingleFile.cs through the committed source-export settings. No microbenchmarks are required because these overloads add only primitive comparisons equivalent to the existing generic behavior.
Rationale
Plan 0168 addressed parent issue #162 (point 5) by adding concrete
MustBePositiveoverloads for the integral types previously covered only by the modern genericINumber<T>API. The other four sign-guard families remain asymmetric: their .NET Standard assets and portable source exports omit smaller signed integral types and, where the predicate remains meaningful, unsigned integral types, although the .NET 10 generic overloads already define their behavior.Extend the concrete integral surface of
MustBeNegative,MustNotBeNegative,MustNotBePositive, andMustNotBeZeroon every supported target while preserving the existing generic APIs and exception contracts. Omit unsigned overloads whose predicate would be constant rather than adding APIs that either always fail or cannot validate anything.Acceptance Criteria
MustBeNegativeandMustNotBeNegativeeach have default and custom-exception-factory overloads forsbyteandshort, but do not add concrete unsigned overloads whose checks would always fail or always succeed.MustNotBePositiveandMustNotBeZeroeach have default and custom-exception-factory overloads forsbyte,byte,short,ushort,uint, andulong.ArgumentOutOfRangeExceptioncontract, including caller-argument-expression parameter names, optional custom messages, and the standard generated message containing the rejected value.ArgumentNullExceptionthrough the existingThrow.CustomExceptionconvention when a failing check receives a null factory.MustBeNegativeandMustNotBeNegativeoverloads; genericINumber<T>overloads appear only in modern exports; and every custom-exception-factory overload is trimmed when that option is disabled.Light.GuardClauses.SingleFile.csdistribution is regenerated with the expanded APIs, and generated source validates for both supported source-export targets.Technical Details
Add the concrete overload pairs outside the
NET8_0_OR_GREATERregions according to this matrix:MustBeNegativesbyte,shortMustNotBeNegativesbyte,shortMustNotBePositivesbyte,byte,short,ushort,uint,ulongMustNotBeZerosbyte,byte,short,ushort,uint,ulongUnsigned overloads are appropriate for
MustNotBePositive, where zero succeeds and positive values fail, andMustNotBeZero, where zero fails and positive values succeed. Do not add concrete unsigned overloads toMustBeNegative, which could never succeed, orMustNotBeNegative, which could never fail.For each listed guard and type, follow the annotations, XML documentation, parameter ordering, and implementation conventions of the corresponding existing
intandlongpair. The following is an illustrative shape for the concrete APIs, not a new generic API:Compare values directly to type-appropriate zero without widening to
intorlong, so return values, throw-helper inputs, and factory arguments retain their exact types. The generic throw helpers already support these types, so no new exception orThrowAPI is required.Keep all
INumber<T>overloads underNET8_0_OR_GREATER. Normal modern calls for types in the matrix should resolve to the new concrete overloads, while explicitly generic calls, deliberately omitted unsigned combinations, and numeric types such asHalfremain supported by the generic surface with identical predicates. Retain focused tests for that distinction. Do not addnintornuintoverloads.Extend the four guard-specific test classes and
NumericCustomFactorySuccessTestswith the new concrete matrix, retaining focused modern tests for explicitly generic calls and types without concrete overloads. StrengthenSourceFileMergerWhitelistTests.SignGuardWhitelistsUseTargetSpecificSurfaceto cover every sign-guard family and both factory settings, updatedocs/assertion-overview.mdandsrc/Directory.Build.props, and regenerate the rootLight.GuardClauses.SingleFile.csthrough the committed source-export settings. No microbenchmarks are required because these overloads add only primitive comparisons equivalent to the existing generic behavior.