Как скрыть текущий метод от трассировки стека исключений в .NET? - PullRequest
31 голосов
/ 04 июня 2010

Я хотел бы знать, есть ли способ вызвать исключение внутри метода, но не включать этот метод в трассировку стека исключений. Э.Г.

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

И затем, если я вызываю этот метод из метода, называемого Foo(), я хочу, чтобы трассировка стека исключений начиналась с at Foo(), а не at ThrowSomeException(). Я предполагаю, что если бы это было возможно, это могло бы быть сделано с помощью атрибутов метода.

Меня интересует общий ответ, но в случае, если это невозможно, я действительно пытаюсь создать метод расширения AssertEqual() для IEnumerable, который я буду использовать в тестах NUnit. Поэтому, когда я вызываю myEnumerable.AssertEqual(otherEnumerable) и происходит сбой, NUnit должен сообщать об ошибке внутри метода test, а не внутри метода extension.

Спасибо!

Ответы [ 7 ]

17 голосов
/ 10 августа 2012

Использование кода в конце этого ответа позволяет написать код, такой как:

[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

Вот одна из возможных реализаций:

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

Обратите внимание, что это приводит к тому, что помеченные методы исключаются только из одного конкретного представления трассировки стека, а не из самой трассы стека . Я не знаю, как достичь последнего.

PS: Если все, что вам нужно, это скрыть метод в окне Call Stack во время сеанса отладки, просто примените атрибут [DebuggerHidden] к метод.

11 голосов
/ 04 июня 2010

Возможно, вы можете получить свой собственный тип исключения и переопределить метод получения свойства StackTrace, чтобы исключить ваш метод:

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

}
5 голосов
/ 04 июня 2010

Я предполагаю, что вы хотите сделать это, чтобы объединить код, который используется для создания исключения?
В этом случае, вместо того, чтобы писать функцию ThrowException(), почему бы не написать функцию GetException()? Тогда в Foo, просто сделайте throw GetException();

3 голосов
/ 04 октября 2017

Ответ метода расширения GetStackTraceWithoutHiddenMethods () - это хорошо, за исключением того, что Exception.ToString () не использует свойство StackTrace, вместо этого он вызывает GetStackTrace (), который не может быть переопределен. Поэтому, если кто-то хочет использовать этот метод расширения со своими собственными типами, основанными на исключениях, он должен переопределить ToString (), а не переопределить свойство StackTrace.

1 голос
/ 17 марта 2016

Обратите внимание, что это улучшение существующих ответов. <Ч /> Принятый ответ на этот вопрос действительно неуклюж, поскольку

  1. Он определяет метод, который нам нужно скрыть от трассировки стека, по его имени, используя чистую строку.
  2. Разделение трассировки стека основано на методе string.Split.
  3. Он скрывает только один метод из свойства StackTrace, не более.

Но оно переопределяет StackTrace само свойство (которое является желательным поведением вопроса) <Ч /> Most Upvoted Answer действительно чище, потому что

  1. он использует атрибут вместо указания имени метода в виде строки.
  2. Может использоваться для сокрытия более одного метода от StackTrace.

но это действительно сложно и просто добавление еще двух классов для методов расширения. И самое главное слабое место в этом не перевешивает само свойство StackTrace. <Ч /> Прочитав предыдущие два решения, я думаю, что нашел самый простой и самый чистый способ (который объединяет лучшее из двух лучших ответов на этот вопрос)

вот инфраструктура, которая нужна.

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

и вот пример использования предыдущей инфраструктуры

[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 Согласно комментарию @Stakx к этому ответу, , который удаляется сразу после ввода , он указывает на одну важную идею:
Это решение работает только для пользовательских определенных исключений, а его решение работает со всеми типами исключений, что абсолютно правильно.
В соответствии с этим, вот один метод расширения, без гораздо более сложного, который мог бы решить проблему и работать со всеми типами исключений.

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

, который почти идентичен его коду, за исключением интеграции метода IsDefined.

0 голосов
/ 06 июня 2019

Я создал метод расширения в соответствии с решением StriplingWarrior и отлично работал.

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

и тогда мы сможем его использовать ...

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

При этом метод Throw() не будет отображаться в трассировке стека.

0 голосов
/ 24 апреля 2018

Если вы скажете компилятору агрессивно встроить ваш метод, это должно помешать вашему методу вообще когда-либо попасть в стек вызовов:

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

Этот атрибут доступен с .NET 4.5.

Однако, это считается только сильным указанием для компилятора, и в некоторых случаях он все еще не вызывает вставку. Например, я не думаю, что он может встроить его, если вы вызываете метод из другой сборки или если вы компилируете в режиме отладки.

Одним из способов решения этой проблемы может быть только создание исключения с вашим помощником и выброс из вызывающего кода.

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

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

Но если ваш вспомогательный метод имеет логику для определения , следует ли выдать исключение, это может не сработать для вас:

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

В этих случаях вам, вероятно, придется копаться со следами стека исключений, как показывают другие ответы здесь. Например, вы можете игнорировать методы, отмеченные DebuggerHiddenAttribute.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...