开发者

How to hide the current method from exception stack trace in .NET?

I'd like to know if there is a way to throw an exception from inside a method, but to not include that method in the exception stack trace. E.g.

void ThrowSomeException()
{
    throw new SomeException();
}

And then, if I call that method from a method called Foo() I want the exception stack trace to start with at Foo(), not at ThrowSomeException(). I assume if this was possible it might be through the use of attributes on the method.

I'm interested in the general answer, but in case this isn't possible, what I'm really trying to do is create an extension method AssertEqual() for IEnumerable that I'll use in NUnit tests. So when I call myEnumerable.AssertEqual(otherEnumerable) and it fails, NUnit should report the error inside the开发者_如何转开发 test method, not inside the extension method.

Thanks!


Using the code at the end of this answer allows you to write code such as:

[HideFromStackTrace] // apply this to all methods you want omitted in stack traces
static void ThrowIfNull(object arg, string paramName)
{
    if (arg == null) throw new ArgumentNullException(paramName);
}

static void Foo(object something)
{
    ThrowIfNull(something, nameof(something));
    …
}

static void Main()
{
    try
    {
        Foo(null);
    }
    catch (Exception e)
    {
        Console.WriteLine(e.GetStackTraceWithoutHiddenMethods());
    }                  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}                      // gets a stack trace string representation
                       // that excludes all marked methods

Here's one possible implementation:

using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

[AttributeUsage(AttributeTargets.Method, Inherited=false)]
public class HideFromStackTraceAttribute : Attribute { }

public static class MethodBaseExtensions
{
    public static bool ShouldHideFromStackTrace(this MethodBase method)
    {
        return method.IsDefined(typeof(HideFromStackTraceAttribute), true);
    }
}

public static class ExceptionExtensions
{
    public static string GetStackTraceWithoutHiddenMethods(this Exception e)
    {
        return string.Concat(
            new StackTrace(e, true)
                .GetFrames()
                .Where(frame => !frame.GetMethod().ShouldHideFromStackTrace())
                .Select(frame => new StackTrace(frame).ToString())
                .ToArray());  // ^^^^^^^^^^^^^^^     ^
    }                         // required because you want the usual stack trace
}                             // formatting; StackFrame.ToString() formats differently

Note that this only causes marked methods to be excluded from one particular representation of the stack trace, not from the stack trace itself. I know of no way to achieve the latter.

P.S.: If all you want is to hide a method in the Call Stack window during a debugging session, simply apply the [DebuggerHidden] attribute to the method.


Maybe you could derive your own exception type and override the StackTrace property getter to exclude your method:

using System;
using System.Collections.Generic;

class MyException : Exception {

    string _excludeFromStackTrace;

    public MyException(string excludeFromStackTrace) {
        _excludeFromStackTrace = excludeFromStackTrace;
    }

    public override string StackTrace {
        get {
            List<string> stackTrace = new List<string>();
            stackTrace.AddRange(base.StackTrace.Split(new string[] {Environment.NewLine},StringSplitOptions.None));
            stackTrace.RemoveAll(x => x.Contains(_excludeFromStackTrace));
            return string.Join(Environment.NewLine, stackTrace.ToArray());
        }
    }
}

class Program {

    static void TestExc() {
        throw new MyException("Program.TestExc");
    }

    static void foo() {
        TestExc();
    }

    static void Main(params string[] args) {
        try{
            foo();
        } catch (Exception exc){
            Console.WriteLine(exc.StackTrace);
        }
    }

}


This is now built-in starting with .NET 6 using the StackTraceHiddenAttribute

https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stacktracehiddenattribute?view=net-6.0

[StackTraceHidden]
void ThrowSomeException()
{
    throw new SomeException();
}


I am guessing that you want to do this in order to consolidate code that is used to create the exception?
In that case, rather than write a ThrowException() function, why not write a GetException() function? Then in Foo, just do throw GetException();


The GetStackTraceWithoutHiddenMethods() extension method answer is fine, except that Exception.ToString() doesn't use the StackTrace property, it calls GetStackTrace() instead, which isn't overrridable. So, if one wishes to use this extension method with their own Exception based types, they have to override ToString() rather than overriding the StackTrace property.


Perhaps in a near futur, you could use [StackTraceHidden]. For now, System.Diagnostics.StackTraceHiddenAttribute is internal but Consider exposing System.Diagnostics.StackTraceHiddenAttribute publicly is on the go.


Please note that this is an improvement to the existing answers.


The Accepted answer of this question is really clumsy because

  1. It determines the method that we need to hide from stack trace by its name using pure string.
  2. Splitting the stack trace is based on the string.Split method.
  3. It hides just one method from StackTrace property, No more.

But it overrides StackTrace property itself (which the desired behavior of the question)


The Most Upvoted Answer is really cleaner because

  1. it is using an attribute instead of specifying the name of the method as a string.
  2. It could be used to hide more than one method from the StackTrace.

but it really complicated and adding two more classes just for extension methods. And the most important weak point in it is not overriding the StackTrace property itself.


After reading the previous two solutions, I think I reached to the simplest AND most clean way (which combine the best of the two top answer to this question)

here is the infrastructure that needed.

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class StackTraceHiddenAttribute : Attribute
{
}

public class SomeException : Exception
{
    public override string StackTrace
    {
        get
        {
            return string.Concat(
                new StackTrace(this, true)
                    .GetFrames()
                    .Where(frame => !frame.GetMethod().IsDefined(typeof(StackTraceHiddenAttribute), true))
                    .Select(frame => new StackTrace(frame).ToString())
                    .ToArray());
        }
    }
}

and here is an example of using the previous infrastructure

[StackTraceHidden] // apply this to all methods you want to be omitted in stack traces
static void Throw()
{
    throw new SomeException();
}

static void Foo()
{
    Throw();
}

static void Main()
{
    try
    {
        Foo();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.StackTrace);
    }                  
}      

EDIT According to a comment by @Stakx on this answer, which is deleted immediately after putting it, he points out to some important idea:
This solution works only for the custom defined exceptions, and his solution working on all exceptions types, which is absolutely correct.
According to that, here is one extension method, without much more complex, which could solve the problem and work on all the exception types.

public static class ExceptionExtensions
{
    public static string GetStackTraceWithoutHiddenMethods(this Exception e)
    {
        return string.Concat(
           new StackTrace(e, true)
               .GetFrames()
               .Where(frame => !frame.GetMethod().IsDefined(typeof(StackTraceHiddenAttribute), true))
               .Select(frame => new StackTrace(frame).ToString())
               .ToArray());
    }                         
}

which is almost identical to his code, except integrating the IsDefined method.


I created an extension method according with the StriplingWarrior solution and worked perfectly.

public static class ExceptionExtensions
{
    [DebuggerHidden]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Throw(this Exception exception) => throw exception;
}

and then we can use it...

using static SomeNamespace.ExceptionExtensions;

public class SomeClass
{
    private void SomeMethod(string value)
    {
        var exception = GetArgumentException(nameof(value), value);
        exception?.Throw(); // only throw if any exception was getted

        ... //method implementation
    }

    private Exception GetArgumentException(string paramName, string value)
    {
        if (value == null)
            return new ArgumentNullException(paramName);
        if (string.IsNullOrEmpty(value))
            return new ArgumentException("value is empty.", paramName);

        return null;
    }
}

With that the Throw() method will not appear in the stack trace.


If you tell the compiler to aggressively inline your method, it should prevent your method from ever making it to the call stack in the first place:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ThrowSomeException()
{
    throw new SomeException();
}

This attribute is available as of .NET 4.5.

However, this is only considered a strong hint to the compiler, and in some cases it still doesn't cause inlining. For example, I don't think it can inline it if you call the method from a different assembly, or if you compile in debug mode.

One workaround for this could be to only create the exception with your helper, and throw it from the calling code.

public static InvalidOperationException InvalidOperation(FormattableString message)
{
  return new InvalidOperationException(FormattableString.Invariant(message));
}

// calling code
throw ExceptionHelpers.InvalidOperation($"{0} is not a valid value", value);

But if your helper method has logic to determine whether to throw the exception, that might not work out for you:

public static class Require
{
    [ContractAnnotation("condition:false => halt")]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    [DebuggerHidden]
    public static void True(bool condition)
    {
        if (!condition)
        {
            throw ExceptionHelpers.InvalidOperation($"Expected condition was not met.");
        }
    }
}

In those cases, you'll probably have to muck with the exception stack traces as the other answers here show. For example, you may want to ignore methods marked with the DebuggerHiddenAttribute.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜