Skip to content

[API Proposal]: ArgumentOutOfRangeException helper methods #69590

@hrrrrustic

Description

@hrrrrustic

UPD: Fully rewrote description after creating PR
UPD2: Initially IComparisonOperators<T, T> was used instead of IComparable<T>
Proposed methods (split them as mush as possible) after looking at /runtime code:
(Not sure about struct constraint, everything looks working without it, but it exists in Linq.Max/Min)

namespace System;

public class ArgumentOutOfRangeException : ArgumentException
{
      public static void ThrowIfZero<T>(T value, [CallerArgumentExpression("value")] string? paramName = null) 
          where T : struct, INumberBase<T>, IComparable<T>;
      public static void ThrowIfNegative<T>(T value, [CallerArgumentExpression("value")] string? paramName = null) 
          where T : struct, INumberBase<T>, ISignedNumber<T>, IComparable<T>;
      public static void ThrowIfNegativeOrZero<T>(T value, [CallerArgumentExpression("value")] string? paramName = null) 
          where T : struct, INumberBase<T>, ISignedNumber<T>, IComparable<T>;
      
      public static void ThrowIfGreaterThan<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) 
          where T : struct, IComparable<T>;
      public static void ThrowIfGreaterThanOrEqual<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) 
          where T : struct, IComparable<T>;
      
      public static void ThrowIfLessThan<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null)
          where T : struct, IComparable<T>;
      public static void ThrowIfLessThanOrEqual<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) 
          where T : struct, IComparable<T>;
      
      public static void ThrowIfNotBetween<T>(T value, T inclusiveLower, T inclusiveUpper, [CallerArgumentExpression("value")] string? paramName = null)
          where T : struct, IComparable<T>;
}

Some statistics

Actual usages count will be a little bit lower since some of them are under libraries with old targets (net 4.x or netstandard)

  • ThrowIfNull
    Usages: <10
    I didn't split this in PR, but saw a few of them with ArgumentOutOfRangeException, InvalidArgumentException and base ArgumentException. It probably makes sense to add this one for better null analysis
  • ThrowIfZero
    I personally don't think that this one is good enough, but there is a [API Proposal]: Add ArgumentOutOfRangeException.ThrowIfZero() #68339
    Usages: 8
  • ThrowIfNegative
    Usages: 755
  • ThrowIfNegativeOrZero
    Usages: 100
  • ThrowIfGreaterThan
    Usages: 204
  • ThrowIfGreaterThanOrEqual
    Usages: 32
  • ThrowIfLessThan
    Usages: 174
  • ThrowIfLessThanOrEqual
    Usages: 4
  • ThrowIfNotBetween
    Usages: 255

Risks & Problems & Questions

  • Using casts while passing argument to helper will cause saving it to ParamName property in ArgumentException. That's break all tests with Assert.Throw(paramName, () => {}) because they're strictly comparing parameter name and ParamName property. I don't think this is a problem for end users, but replacement in /runtime could be tricky
  • ThrowIf should contains additional argument for passing custom message. Should it be just string or InterpolatedStringHandler? Discussed in [API Proposal]: More helper methods for exception types #69637 and ThrowIf completely removed from proposal.
  • ThrowIfLessThan and others similar methods with IComparisonOperators constraint doesn't work for TimeSpan, DateTime, etc. Related to Determine recommended behavior of checked/unchecked operators for DateTime and similar types #67744, but Idk why these types can't implement IComparisonOperators that doesn't cause any overflowing issues. Could be replaced by IComparable<T>? UPD: replaced by IComparable<T>
  • ThrowIfNegative{OrZero} requires too much for custom types due to IBaseNumber<> constraint (we only need T.Zero). I think most of the users will use ThrowIfLess with manual passing "zero" value instead (only IComparable<T> required for this)
  • I saw a lot of similar conditions with throwing base ArgumentException instead ArgumentOutOfRangeException. Should we duplicate these methods for it?
  • [API Proposal]: Throw helpers should return the valid value back #70515
  • Renaming ThrowIfNotBetween -> ThrowIfOutsideRange ArgumentOutOfRangeException throw helpers #69767 (comment)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions