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

c# .net exception

60504 просмотра

10 ответа

20899 Репутация автора

Я размышляю о методе, который может вызвать исключение. Как я могу передать исключение своему вызывающему абоненту без использования обертки?
Я перебрасываю 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;
    }
}
Автор: skolima Источник Размещён: 11.09.2008 07:17

Ответы (10)


31 плюса

18863 Репутация автора

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

throw;

А потом извлекать неустражение позже.

Автор: GEOCHET Размещён: 11.09.2008 07:20

11 плюса

20899 Репутация автора

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

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. Смотрите дальнейшее обсуждение Mono Bugzilla .

Автор: skolima Размещён: 11.09.2008 07:20

10 плюса

20779 Репутация автора

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

Автор: kokos Размещён: 11.09.2008 07:22

12 плюса

121 Репутация автора

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

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

Автор: Eric Размещён: 02.11.2009 08:39

5 плюса

14637 Репутация автора

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

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

    }
Автор: Boris Treukhov Размещён: 02.01.2010 05:48

85 плюса

17160 Репутация автора

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

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 ;
}
Автор: Anton Tykhyy Размещён: 18.01.2010 10:42

3 плюса

31 Репутация автора

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

    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 });
    }
Автор: chickenbyproduct Размещён: 03.04.2012 08:19

427 плюса

26579 Репутация автора

Решение

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

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

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

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

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

Автор: Paul Turner Размещён: 13.06.2013 03:42

10 плюса

201 Репутация автора

Никто не объяснил разницу между 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, потому что номер строки исходного исключения сохраняется, но не является реальным перебросом, потому что он меняет тип исходного исключения.

Автор: jeuoekdcwzfwccu Размещён: 14.11.2016 10:28

0 плюса

19977 Репутация автора

На основании ответа Пола Тернерса я сделал метод расширения

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

return exIST никогда не достиг , но преимущество заключается в том, что я могу использовать в 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();
        }
    }
Автор: Jürgen Steinblock Размещён: 16.07.2019 08:09
32x32