dotnet/runtime

Unify whether throw helpers are [StackTraceHidden] or not

Open

#90.539 aperta il 14 ago 2023

Vedi su GitHub
 (49 commenti) (63 reazioni) (0 assegnatari)C# (5445 fork)batch import
api-needs-workarea-System.Runtimehelp wanted

Metriche repository

Star
 (17.886 star)
Metriche merge PR
 (Merge medio 12g 11h) (661 PR mergiate in 30 g)

Descrizione

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
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);
}

Guida contributor