Skip to content

Unify whether throw helpers are [StackTraceHidden] or not #90539

Open

Description

Edit Please vote by leaving 👍 if want them hidden 👎 if want to include them in stack traces

Currently, most throw helpers (like ArgumentNullException.ThrowIfNull(p)) don't use the [StackTraceHidden], which makes them appear in stack traces. Some throw helpers are internally chained, which causes up to four additional frames. However, most are a single frame see all examples.

We have two options:

  1. Hide all throw helpers. Vote 👍
    • Pro: Simpler stack traces
    • Pro: First line in stack trace points to user code
    • Con: Stack trace doesn't show which argument validation failed, you need to read message and/or look at the code
  2. Hide none of the throw helpers. Vote 👎
    • Pro: Stack trace more accurately reflects reality
    • Pro: Stack trace alone shows which argument validation failed
    • Con: Stack trace is more noisy

There doesn't seem to be an argument that clearly shows one of the two options as superior. It's unlikely that we'll make the debugger configurable in this regard, so we'll have to pick one option, which is why user voting seems like a good way to decide.

Example code:

B(null!);

void B(string arg)
{
   ArgumentException.ThrowIfNullOrEmpty(arg);
}

Current experience:

System.ArgumentNullException: Value cannot be null. (Parameter 'arg')
   at System.ArgumentNullException.Throw(String paramName)
   at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
   at System.ArgumentException.ThrowNullOrEmptyException(String argument, String paramName)
   at System.ArgumentException.ThrowIfNullOrEmpty(String argument, String paramName)
   at Program.<<Main>$>g__B|0_2(String arg) in C:\Users\thund\source\repos\ConsoleApp3\ConsoleApp3\Program.cs:line 26
   at Program.<Main>$(String[] args) in C:\Users\thund\source\repos\ConsoleApp3\ConsoleApp3\Program.cs:line 10

Proposed experience:

System.ArgumentNullException: Value cannot be null. (Parameter 'arg')
   at Program.<<Main>$>g__B|0_2(String arg) in C:\Users\thund\source\repos\ConsoleApp3\ConsoleApp3\Program.cs:line 26
   at Program.<Main>$(String[] args) in C:\Users\thund\source\repos\ConsoleApp3\ConsoleApp3\Program.cs:line 10
API proposal (hidden because we first want users to vote on the experience)
namespace System;

public partial class ArgumentException : SystemException
{
    [StackTraceHidden]
    public static void ThrowIfNullOrEmpty([NotNull] string? argument, [CallerArgumentExpression("argument")] string? paramName = null);

    [StackTraceHidden]
    public static void ThrowIfNullOrWhiteSpace([NotNull] string? argument, [CallerArgumentExpression("argument")] string? paramName = null);
}

public partial class ArgumentOutOfRangeException : ArgumentException
{
    [StackTraceHidden]
    public static void ThrowIfEqual<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null)
        where T, IEquatable<T>?;

    [StackTraceHidden]
    public static void ThrowIfGreaterThan<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null)
        where T, IComparable<T>!;

    [StackTraceHidden]
    public static void ThrowIfGreaterThanOrEqual<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null)
        where T, IComparable<T>!;

    [StackTraceHidden]
    public static void ThrowIfLessThan<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null)
        where T, IComparable<T>!;

    [StackTraceHidden]
    public static void ThrowIfLessThanOrEqual<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null)
        where T, IComparable<T>!;

    [StackTraceHidden]
    public static void ThrowIfNegative<T>(T value, [CallerArgumentExpression("value")] string? paramName = null)
        where T, INumberBase<T>!;

    [StackTraceHidden]
    public static void ThrowIfNegativeOrZero<T>(T value, [CallerArgumentExpression("value")] string? paramName = null)
        where T, INumberBase<T>!;

    [StackTraceHidden]
    public static void ThrowIfNotEqual<T>(T value, T other, [CallerArgumentExpression("value")] string? paramName = null)
        where T, IEquatable<T>?;

    [StackTraceHidden]
    public static void ThrowIfZero<T>(T value, [CallerArgumentExpression("value")] string? paramName = null)
        where T, INumberBase<T>!;
}

// Already marked
public partial class ObjectDisposedException : InvalidOperationException
{
    [StackTraceHidden]
    public static void ThrowIf([DoesNotReturnIf(true)] bool condition, object! instance);

    [StackTraceHidden]
    public static void ThrowIf([DoesNotReturnIf(true)] bool condition, Type! type);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Runtimehelp wanted[up-for-grabs] Good issue for external contributors

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions