Сохранение исходного StackTrace/LineNumbers в .NET Исключения

Понимая разницу между throw ex и throw, почему исходный StackTrace сохранен в этом примере:

    static void Main(string[] args)
    {
        try
        {
            LongFaultyMethod();
        }
        catch (System.Exception ex)
        {
            Console.WriteLine(ex.StackTrace);
        }
    }

    static void LongFaultyMethod()
    {
        try
        {
            int x = 20;
            SomethingThatThrowsException(x);
        }
        catch (Exception)
        {
            throw;
        }
    }

    static void SomethingThatThrowsException(int x)
    {
        int y = x / (x - x);
    }

Но не в этом:

    static void Main(string[] args)
    {
        try
        {
            LongFaultyMethod();
        }
        catch (System.Exception ex)
        {
            Console.WriteLine(ex.StackTrace);
        }
    }

    static void LongFaultyMethod()
    {
        try
        {
            int x = 20;
            int y = x / (x - 20);
        }
        catch (Exception)
        {
            throw;
        }
    }

Второй сценарий создает тот же результат, что и throw ex?

В обоих случаях ожидается, что номер строки, где y инициализируется.

Ответы

Ответ 1

Я не уверен, что это ограничение относится к языку С#, CLI или реализации Microsoft, но ваш второй пример - это случай, когда требуется явный вызов Exception.InternalPreserveStackTrace, как описано в следующем сообщении, Поскольку этот метод internal, его обычно нужно вызывать через отражение. Проблемы с производительностью, связанные с этим, могут быть почти полностью устранены путем создания Action<Exception> для вызова, как показано в конце этого ответа.

Ссылка: Повторное удаление исключений и сохранение полной трассировки стека вызовов

Изменить: После пересмотра ECMA-335 Partition я §12.4.2 (Обработка исключений) и раздела III §4.24 (rethrow) теперь я считаю, что поведение, которое вы видите, является семантической ошибкой в CLR (реализация Microsoft CLI). Единственная конкретная ссылка на поведение: "A rethrow не меняет трассировку стека в объекте". В случае, описанном здесь, ретролл фактически изменяет трассировку стека, заставляя PreserveStackTrace взломать обходной путь для известного недостатка CLR.

static void LongFaultyMethod() 
{ 
    try 
    { 
        int x = 20; 
        int y = x / (x - 20); 
    } 
    catch (Exception ex) 
    { 
        PreserveStackTrace(ex); // <-- add this line
        throw; 
    } 
} 

PreserveStackTrace Вот оптимизация одной из этой записи в блоге:

private static readonly Action<Exception> _internalPreserveStackTrace =
    (Action<Exception>)Delegate.CreateDelegate(
        typeof(Action<Exception>),
        typeof(Exception).GetMethod(
            "InternalPreserveStackTrace",
            BindingFlags.Instance | BindingFlags.NonPublic));

public static void PreserveStackTrace(Exception e)
{
    _internalPreserveStackTrace(e);
}

Ответ 2

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

Сделайте так, как показано ниже, лучший способ - всегда исключить исключение из нового исключения, чтобы вы увидели глубину исключения.

"Если ретрол был выпущен в тот же метод (трассировка стека исключений имеет только одну информацию о номере линии за каждый метод вы никогда не видите трассировку стека что в методе A, в строке номер 2 исключение было брошено, а затем в том же Метод A, он был сброшен из строки номер 17, он будет содержать только последние номер строки, откуда исключение вызваны повторно"

try        
{            
   int x = 20;            
   int y = x / (x - 20);        
}        
catch (Exception ex)        
{            
   // do something here.. like log or something
   throw new Exception("Internal Exception", ex);        
}