Как повторно выбросить InnerException без потери трассировки стека в C#?

Я вызываю через отражение метод, который может вызвать исключение. Как я могу передать исключение вызывающему абоненту без того, чтобы его окружало отражение оболочки?
Я повторно генерирую InnerException, но это уничтожает трассировку стека. Пример кода:

public void test1()
{
    // Throw an exception for testing purposes
    throw new ArgumentException("test1");
}

void test2()
{
    try
    {
        MethodInfo mi = typeof(Program).GetMethod("test1");
        mi.Invoke(this, null);
    }
    catch (TargetInvocationException tiex)
    {
        // Throw the new exception
        throw tiex.InnerException;
    }
}

Есть другой способ сделать это, не требующий вуду. Взгляните на ответ здесь: stackoverflow.com/questions/15668334/…

Timothy Shields 30.03.2013 01:41

Исключение, созданное в динамически вызываемом методе, является внутренним исключением исключения «Исключение было создано целью вызова». У него есть собственная трассировка стека. Больше не о чем беспокоиться.

ajeh 12.02.2018 21:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
320
2
80 409
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

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

throw;

А потом извлеките внутреннее исключение.

Или вообще удалите try / catch.

Daniel Earwicker 18.12.2008 01:41

@Earwicker. Удаление try / catch в целом не является хорошим решением, поскольку игнорирует случаи, когда требуется код очистки перед распространением исключения вверх по стеку вызовов.

Jordan 02.11.2009 23:43

@Jordan - код очистки должен быть в блоке finally, а не в блоке catch

Paolo 02.01.2010 21:12

@Paolo - Если предполагается, что это должно выполняться в каждом случае, да. Если предполагается, что он будет выполняться только в случае неудачи, нет.

chiccodoro 01.09.2010 19:03

Имейте в виду, что InternalPreserveStackTrace не является потокобезопасным, поэтому, если у вас есть 2 потока в одном из этих состояний исключения ... да помилует нас всех Бог.

Rob 04.02.2011 04:35

Это работает, если вы "бросаете" сразу в блок catch, но не работает, если вам нужно "выбросить" за пределы блока catch.

Mark Lakata 15.10.2011 00:16

Еще больше размышлений ...

catch (TargetInvocationException tiex)
{
    // Get the _remoteStackTraceString of the Exception class
    FieldInfo remoteStackTraceString = typeof(Exception)
        .GetField("_remoteStackTraceString",
            BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net

    if (remoteStackTraceString == null)
        remoteStackTraceString = typeof(Exception)
        .GetField("remote_stack_trace",
            BindingFlags.Instance | BindingFlags.NonPublic); // Mono

    // Set the InnerException._remoteStackTraceString
    // to the current InnerException.StackTrace
    remoteStackTraceString.SetValue(tiex.InnerException,
        tiex.InnerException.StackTrace + Environment.NewLine);

    // Throw the new exception
    throw tiex.InnerException;
}

Имейте в виду, что это может сломаться в любой момент, поскольку частные поля не являются частью API. См. Дальнейшее обсуждение Моно багзилла.

Это действительно очень плохая идея, поскольку она зависит от внутренних недокументированных деталей о классах фреймворка.

Daniel Earwicker 18.12.2008 01:39

@Earwicker: +1 это действительно плохая идея

Richard Szalay 15.05.2009 11:05

Оказывается, можно сохранить трассировку стека без отражения, см. Ниже.

Anton Tykhyy 20.01.2010 02:23

Было бы лучше вызвать внутренний метод InternalPreserveStackTrace, поскольку он делает то же самое и вряд ли изменится в будущем ...

Thomas Levesque 02.06.2010 12:54

На самом деле, было бы хуже, поскольку InternalPreserveStackTrace не существует на Mono.

skolima 07.06.2010 20:15

@daniel - ну, это действительно, очень, очень плохая идея для броска; чтобы сбросить трассировку стека, когда каждый разработчик .net убежден, что это не так. Это также действительно, очень, очень плохо, если вы не можете узнать источник исключения NullReferenceException и потерять клиента / заказ, потому что вы не можете его найти. для меня это важнее «недокументированных деталей» и определенно моно.

Simon_Weaver 09.11.2010 08:28

@Simon: throw; не сбрасывает трассировку стека. throw e; делает.

Anton Tykhyy 18.11.2010 13:47

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

tster 12.12.2017 04:14

Во-первых: не теряйте TargetInvocationException - это ценная информация, когда вы захотите что-то отладить. Во-вторых: оберните TIE как InnerException в свой собственный тип исключения и поместите свойство OriginalException, которое ссылается на то, что вам нужно (и сохраните весь стек вызовов нетронутым). В-третьих: позвольте TIE пузырю исчезнуть из вашего метода.

public static class ExceptionHelper
{
    private static Action<Exception> _preserveInternalException;

    static ExceptionHelper()
    {
        MethodInfo preserveStackTrace = typeof( Exception ).GetMethod( "InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic );
        _preserveInternalException = (Action<Exception>)Delegate.CreateDelegate( typeof( Action<Exception> ), preserveStackTrace );            
    }

    public static void PreserveStackTrace( this Exception ex )
    {
        _preserveInternalException( ex );
    }
}

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

Имейте в виду, что в .Net 4.0 InternalPreserveStackTrace теперь не работает - посмотрите в Reflector, и вы увидите, что метод полностью пуст!

Samuel Jack 19.04.2010 18:37

Счеркните это: я смотрел на RC: в бета-версии они снова вернули реализацию!

Samuel Jack 20.04.2010 00:04

предложение: измените PreserveStackTrace, чтобы он возвращал ex - затем, чтобы выбросить исключение, вы можете просто сказать: throw ex.PreserveStackTrace ();

Simon_Weaver 09.11.2010 08:33

Зачем использовать Action<Exception>? Здесь использовать статический метод

Kiquenet 25.05.2018 13:14

Ребята, вы классные .. Я скоро стану некромантом.

    public void test1()
    {
        // Throw an exception for testing purposes
        throw new ArgumentException("test1");
    }

    void test2()
    {
            MethodInfo mi = typeof(Program).GetMethod("test1");
            ((Action)Delegate.CreateDelegate(typeof(Action), mi))();

    }

Хорошая идея, но вы не всегда контролируете код, который вызывает .Invoke().

Anton Tykhyy 18.01.2010 13:46

И вы не всегда знаете типы аргументов / результатов во время компиляции.

Roman Starkov 03.03.2010 15:12

является возможно сохранить трассировку стека перед повторным выбросом без отражения:

static void PreserveStackTrace (Exception e)
{
    var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ;
    var mgr = new ObjectManager     (null, ctx) ;
    var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ;

    e.GetObjectData    (si, ctx)  ;
    mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData
    mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData

    // voila, e is unmodified save for _remoteStackTraceString
}

Это тратит много циклов по сравнению с вызовом InternalPreserveStackTrace через кешированный делегат, но имеет то преимущество, что полагается только на общедоступные функции. Вот несколько распространенных шаблонов использования функций сохранения трассировки стека:

// usage (A): cross-thread invoke, messaging, custom task schedulers etc.
catch (Exception e)
{
    PreserveStackTrace (e) ;

    // store exception to be re-thrown later,
    // possibly in a different thread
    operationResult.Exception = e ;
}

// usage (B): after calling MethodInfo.Invoke() and the like
catch (TargetInvocationException tiex)
{
    PreserveStackTrace (tiex.InnerException) ;

    // unwrap TargetInvocationException, so that typed catch clauses 
    // in library/3rd-party code can work correctly;
    // new stack trace is appended to existing one
    throw tiex.InnerException ;
}

Выглядит круто, что должно произойти после запуска этих функций?

vdboor 23.02.2010 19:34

@vdboor: Я не совсем понимаю ваш вопрос. Редактирование прояснило ситуацию?

Anton Tykhyy 24.02.2010 10:52

На самом деле, это ненамного медленнее, чем вызов InternalPreserveStackTrace (примерно на 6% медленнее при 10000 итерациях). Доступ к полям напрямую через отражение примерно на 2,5% быстрее, чем при вызове InternalPreserveStackTrace.

Thomas Levesque 02.06.2010 12:58

@Thomas: при использовании InternalPreserveStackTrace гораздо быстрее создать делегата для него, вместо того, чтобы каждый раз вызывать с отражением. Я сравнил с первым сценарием.

Anton Tykhyy 24.06.2010 10:23

Привет! Спасибо за отличный код и пояснения. Можно ли добавить строку в сообщение об исключении, чтобы я мог фиксировать не только трассировку стека, откуда она была выброшена, но и причину?

Ido Ran 17.11.2010 11:44

Я бы рекомендовал использовать словарь e.Data со строкой или уникальным ключом объекта (static readonly object myExceptionDataKey = new object (), но не делайте этого, если вам нужно где-то сериализовать исключения). Избегайте изменения e.Message, потому что у вас может быть где-то код, который анализирует e.Message. Парсить e.Message - зло, но другого выхода может и не быть, например если вам нужно использовать стороннюю библиотеку с плохой практикой исключения.

Anton Tykhyy 17.11.2010 15:48

DoFixups прерывается для пользовательских исключений, если у них нет ctor сериализации

ruslander 16.02.2012 23:06

Предлагаемое решение не работает, если исключение не имеет конструктора сериализации. Я предлагаю использовать решение, предложенное на stackoverflow.com/a/4557183/209727, которое хорошо работает в любом случае. Для .NET 4.5 рассмотрите возможность использования класса ExceptionDispatchInfo.

Davide Icardi 28.01.2013 03:40

Так что, это. В своем ответе я упоминаю, что преимущество подхода сериализации заключается в том, что он полагается только на общедоступные API. Очевидно, некоторые стандарты проверки кода не одобряют использование отражения для доступа к внутренним компонентам среды CLR. Что касается класса ExceptionDispatchInfo, он хорош, но не сериализуем (вероятно, потому, что он сохраняет информацию Watson в дополнение к трассировке стека), что несколько ограничивает его полезность.

Anton Tykhyy 28.01.2013 05:14

Ответ stackoverflow.com/a/9989557/206730не требовать тип исключения должен быть сериализуемый?

Kiquenet 25.05.2018 13:17

Другой пример кода, который использует сериализацию / десериализацию исключений. Это не требует, чтобы фактический тип исключения был сериализуемым. Также он использует только общедоступные / защищенные методы.

    static void PreserveStackTrace(Exception e)
    {
        var ctx = new StreamingContext(StreamingContextStates.CrossAppDomain);
        var si = new SerializationInfo(typeof(Exception), new FormatterConverter());
        var ctor = typeof(Exception).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null);

        e.GetObjectData(si, ctx);
        ctor.Invoke(e, new object[] { si, ctx });
    }

не требовать, чтобы фактический тип исключения был сериализуемым?

Kiquenet 25.05.2018 13:12
Ответ принят как подходящий

В .NET 4.5 теперь есть класс ExceptionDispatchInfo.

Это позволяет вам захватить исключение и повторно выбросить его без изменения трассировки стека:

try
{
    task.Wait();
}
catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}

Это работает с любым исключением, а не только с AggregateException.

Он был введен благодаря функции языка C# await, которая разворачивает внутренние исключения из экземпляров AggregateException, чтобы сделать функции асинхронного языка более похожими на функции синхронного языка.

Хороший кандидат для метода расширения Exception.Rethrow ()?

nmarler 07.04.2014 19:48

Обратите внимание, что класс ExceptionDispatchInfo находится в пространстве имен System.Runtime.ExceptionServices и недоступен до .NET 4.5.

yoyo 13.05.2014 08:25

Вам может потребоваться поставить обычный throw; после строки .Throw (), потому что компилятор не будет знать, что .Throw () всегда генерирует исключение. throw; никогда не будет вызван в результате, но, по крайней мере, компилятор не будет жаловаться, если ваш метод требует возвращаемого объекта или является асинхронной функцией.

Todd 10.07.2014 03:19

Есть ли реализация для .Net 4.0?

Soppus 04.09.2014 12:57

Почему простого throw; недостаточно? Я только что протестировал его в небольшом консольном приложении и обнаружил, что трассировка стека не изменилась между повторным генерированием и отсутствием обработки исключения.

Taudris 03.12.2014 02:40

@Taudris Этот вопрос конкретно касается повторной генерации внутреннего исключения, которое не может быть специально обработано throw;. Если вы используете throw ex.InnerException;, трассировка стека повторно инициализируется в момент повторного вызова.

Paul Turner 03.12.2014 03:48

Упс, пропустил деталь InnerException. Неважно, имеет смысл! Это могло быть весьма полезно.

Taudris 03.12.2014 04:10

иногда InnerException имеет значение null. Что в таком случае?

amit jha 05.04.2016 10:14

@amitjha в случае, если InnerException имеет значение null, внешнее исключение должно быть просто сгенерировано.

Peter Lillevold 27.04.2016 01:52

@amitjha ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();

Vedran 02.06.2016 17:10

throw;та же, что System.Runtime.ExceptionServices.ExceptionDispatchInfo.Captu‌​re(ex).Throw(); и сохраняет трассировку стека?

Kiquenet 25.05.2018 13:49

public static T Rethrow<T>(this System.Exception ex){ ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();throw new System.Exception(); // noop} Это позволяет вернуться, чтобы сделать компиляцию счастливой. Не уверен, что это отличная идея ...

Red Riding Hood 19.05.2019 14:45

@RedRidingHood, хорошо, но в Generic нет необходимости, верно? Просто недействительно.

crokusek 25.02.2020 02:48

Никто не объяснил разницу между ExceptionDispatchInfo.Capture( ex ).Throw() и простым throw, так что вот она.

Полный способ повторно вызвать перехваченное исключение - использовать ExceptionDispatchInfo.Capture( ex ).Throw() (доступно только в .Net 4.5).

Ниже приведены примеры, необходимые для проверки:

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

Случай 1 и случай 2 предоставят вам трассировку стека, где номер строки исходного кода для метода CallingMethod - это номер строки строки throw new Exception( "TEST" ).

Однако вариант 3 предоставит вам трассировку стека, где номер строки исходного кода для метода CallingMethod - это номер строки вызова throw. Это означает, что если строка throw new Exception( "TEST" ) окружена другими операциями, вы не знаете, на каком номере строки фактически было сгенерировано исключение.

Случай 4 аналогичен случаю 2, потому что номер строки исходного исключения сохраняется, но не является реальным повторным вызовом, поскольку он изменяет тип исходного исключения.

Я всегда думал, что «throw» не сбрасывает трассировку стека (в отличие от «throw e»).

Jesper Matthiesen 19.10.2017 16:31

@JesperMatthiesen Я могу ошибаться, но я слышал, что это зависит от того, было ли выброшено исключение и обнаружено ли оно в том же файле. Если это тот же файл, трассировка стека будет потеряна, если это другой файл, она будет сохранена.

jahu 01.10.2019 13:26

То, что я могу найти на ExceptionDispatchInfo, указывает на его использование, когда вы хотите повторно выбросить вне контекста улова или развернуть AggregateException. Разница, которую я вижу в своих тестах, заключается в том, что throw; не может захватить какие-либо строки из try в трассировке стека. Изменив пример 3 на try { DoThrow(); } catch (Exception) { throw; }, вы увидите, что строка выброса в DoThrowявляется включена в трассировку стека, но все же не вызывающая линия из try. Тогда как ExceptionDispatchInfo.Capture включает эту строку. По-прежнему кажется, что слишком много работы за пределами основных вариантов использования.

Johann 09.09.2020 04:17

Поведение из примера 3 было зарегистрировано как ошибка .NET Core и исправлено в .NET Core 2.1: github.com/dotnet/runtime/issues/9518

Johann 09.09.2020 04:23

Основываясь на ответе Пола Тернерса, я сделал метод расширения

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }

return ex никогда не достигается, но преимущество в том, что я могу использовать throw ex.Capture() как однострочник, поэтому компилятор не вызовет ошибку not all code paths return a value.

    public static object InvokeEx(this MethodInfo method, object obj, object[] parameters)
    {
        {
            return method.Invoke(obj, parameters);
        }
        catch (TargetInvocationException ex) when (ex.InnerException != null)
        {
            throw ex.InnerException.Capture();
        }
    }

Другие вопросы по теме