Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: ArgumentOutOfRangeException constructor with actualValue and system message #89783

Closed
OwnageIsMagic opened this issue Aug 1, 2023 · 4 comments · Fixed by #90505

Comments

@OwnageIsMagic
Copy link
Contributor

Background and motivation

ArgumentOutOfRangeException have read-only ActualValue property which is settable only from constructor that also requires user provided message.

API Proposal

Unfortunately .ctor(string paramName, object actualValue) is ambiguous if actualValue is string (because of existing .ctor(string paramName, string message)), so I instead suggest new throw helper

namespace System;

public class ArgumentOutOfRangeException
{
    [DoesNotReturn]
    public static void Throw(object/*?*/ actualValue, [CallerArgumentExpression(nameof(actualValue))] string? paramName = null);
}

API Usage

void CheckEven(int i)
{
    if (i % 2 != 0)
        ArgumentOutOfRangeException.Throw(i);
}

Alternative Designs

Add factory method instead, this will allow creation of object without throwing (useful for testing).

Current workaround:

namespace System;

/// <inheritdoc />
public sealed class ArgumentOutOfRangeExceptionEx : ArgumentOutOfRangeException
{
    private static readonly string Arg_ArgumentOutOfRangeException = (string)(typeof(ArgumentOutOfRangeException) // TODO: test on net40
                                                                              .Assembly.GetType("System.SR")?
                                                                              .GetProperty("Arg_ArgumentOutOfRangeException", // net core
                                                                                  BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
                                                                              ?.GetValue(null, null)
                                                                              ?? typeof(ArgumentOutOfRangeException)
                                                                                 .GetProperty("RangeMessage", // net fx
                                                                                     BindingFlags.Static | BindingFlags.NonPublic)
                                                                                 ?.GetValue(null, null)
                                                                              ?? "Arg_ArgumentOutOfRangeException");

    /// <summary>
    /// Create <see cref="ArgumentOutOfRangeException"/> with default message
    /// AND specifying <see cref="ArgumentOutOfRangeException.ActualValue"/>.
    /// </summary>
    /// <inheritdoc path='/param/[@name="paramName"]'/>
    /// <inheritdoc path='/param/[@name="actualValue"]'/>
    public ArgumentOutOfRangeExceptionEx(string paramName, object? actualValue)
        : base(paramName, actualValue, Arg_ArgumentOutOfRangeException) { }
}

Risks

No response

@OwnageIsMagic OwnageIsMagic added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Aug 1, 2023
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 1, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Aug 1, 2023
@stephentoub
Copy link
Member

You can always just pass null as the message. The downside of that is the current implementation then doesn't use the ArgumentOutOfRangeException's default message and instead uses Exception's default message. I suggest this doesn't warrant a new constructor or Throw method, and we instead just change this:

        public ArgumentOutOfRangeException(string? paramName, object? actualValue, string? message)
            : base(message, paramName)

to be this:

        public ArgumentOutOfRangeException(string? paramName, object? actualValue, string? message)
            : base(message ?? SR.Arg_ArgumentOutOfRangeException, paramName)

and the same for the other ctor(s) that take a string? message.

@OwnageIsMagic
Copy link
Contributor Author

@stephentoub

        // Creates a new ArgumentOutOfRangeException with its message
        // string set to a default message explaining an argument was out of range.
        public ArgumentOutOfRangeException()
            : this(null, null, null) {}

        public ArgumentOutOfRangeException(string? paramName)
            : this(paramName, null, null) {}
 
        public ArgumentOutOfRangeException(string? paramName, string? message)
            : this(paramName, null, message) {}
 
        public ArgumentOutOfRangeException(string? message, Exception? innerException)
            : base(message, innerException)
        {
            HResult = HResults.COR_E_ARGUMENTOUTOFRANGE;
        }
 
        public ArgumentOutOfRangeException(string? paramName, object? actualValue, string? message)
            : base(message ?? SR.Arg_ArgumentOutOfRangeException, paramName)
        {
            _actualValue = actualValue;
            HResult = HResults.COR_E_ARGUMENTOUTOFRANGE;
        }

Create PR?

@stephentoub
Copy link
Member

Create PR?

Sure, thanks.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 2, 2023
@ghost
Copy link

ghost commented Aug 2, 2023

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

ArgumentOutOfRangeException have read-only ActualValue property which is settable only from constructor that also requires user provided message.

API Proposal

Unfortunately .ctor(string paramName, object actualValue) is ambiguous if actualValue is string (because of existing .ctor(string paramName, string message)), so I instead suggest new throw helper

namespace System;

public class ArgumentOutOfRangeException
{
    [DoesNotReturn]
    public static void Throw(object/*?*/ actualValue, [CallerArgumentExpression(nameof(actualValue))] string? paramName = null);
}

API Usage

void CheckEven(int i)
{
    if (i % 2 != 0)
        ArgumentOutOfRangeException.Throw(i);
}

Alternative Designs

Add factory method instead, this will allow creation of object without throwing (useful for testing).

Current workaround:

namespace System;

/// <inheritdoc />
public sealed class ArgumentOutOfRangeExceptionEx : ArgumentOutOfRangeException
{
    private static readonly string Arg_ArgumentOutOfRangeException = (string)(typeof(ArgumentOutOfRangeException) // TODO: test on net40
                                                                              .Assembly.GetType("System.SR")?
                                                                              .GetProperty("Arg_ArgumentOutOfRangeException", // net core
                                                                                  BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
                                                                              ?.GetValue(null, null)
                                                                              ?? typeof(ArgumentOutOfRangeException)
                                                                                 .GetProperty("RangeMessage", // net fx
                                                                                     BindingFlags.Static | BindingFlags.NonPublic)
                                                                                 ?.GetValue(null, null)
                                                                              ?? "Arg_ArgumentOutOfRangeException");

    /// <summary>
    /// Create <see cref="ArgumentOutOfRangeException"/> with default message
    /// AND specifying <see cref="ArgumentOutOfRangeException.ActualValue"/>.
    /// </summary>
    /// <inheritdoc path='/param/[@name="paramName"]'/>
    /// <inheritdoc path='/param/[@name="actualValue"]'/>
    public ArgumentOutOfRangeExceptionEx(string paramName, object? actualValue)
        : base(paramName, actualValue, Arg_ArgumentOutOfRangeException) { }
}

Risks

No response

Author: OwnageIsMagic
Assignees: -
Labels:

api-suggestion, area-System.Runtime, untriaged, in-pr, needs-area-label

Milestone: -

@vcsjones vcsjones removed the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 3, 2023
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Aug 14, 2023
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 14, 2023
@stephentoub stephentoub removed the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Oct 3, 2023
@ghost ghost added in-pr There is an active PR which will close this issue when it is merged and removed in-pr There is an active PR which will close this issue when it is merged labels Dec 27, 2023
@ghost ghost added in-pr There is an active PR which will close this issue when it is merged and removed in-pr There is an active PR which will close this issue when it is merged labels Jan 6, 2024
@ghost ghost removed in-pr There is an active PR which will close this issue when it is merged untriaged New issue has not been triaged by the area owner labels Jan 18, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Feb 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
4 participants