Как я могу перебросить внутреннее исключение, сохраняя при этом трассировку стека?

Дубликат: В С#, как я могу перекрыть InnerException без потери трассировки стека?

У меня есть некоторые операции, которые я вызываю асинхронно в фоновом потоке. Иногда все идет плохо. Когда это происходит, я, как правило, получаю TargetInvocationException, которое, при необходимости, совершенно бесполезно. Я действительно нуждаюсь в TargetInvocationException InnerException, например:

    try
    {
        ReturnValue = myFunctionCall.Invoke(Target, Parameters);
    }
    catch (TargetInvocationException err)
    {
        throw err.InnerException;
    }

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

УТОЧНЕНИЕ: Причина, по которой я хочу только внутреннее исключение, заключается в том, что этот класс пытается "абстрагировать" весь факт, что эти функции (делегаты, предоставленные вызывающим) выполняются на других потоках и еще много чего. Если есть исключение, то вероятность того, что это не имеет никакого отношения к запуску в фоновом потоке, и вызывающему пользователю действительно понравится трассировка стека, которая входит в их делегат, и находит реальную проблему, а не мой вызов для вызова.

Ответ 1

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

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

Ответ 2

Нет, это невозможно. Ваша единственная реальная возможность - следовать рекомендуемому шаблону и бросить свое собственное исключение с соответствующим InnerException.

Edit

Если ваша проблема связана с присутствием TargetInvocationException, и вы хотите ее игнорировать (не то, что я рекомендую это, так как это может очень хорошо иметь отношение к тому, что оно выполняется в другом потоке), тогда ничего чтобы вы не выбрасывали свое собственное исключение и прикрепляли InnerException к TargetInvocationException как свой собственный InnerException. Это немного вонючий, но он может выполнить то, что вы хотите.

Ответ 3

Существует способ "сбросить" трассировку стека в исключении, используя внутренний механизм, который используется для сохранения трассировки стека на стороне сервера при использовании удаленных операций, но это ужасно:

try
{
    // some code that throws an exception...
}
catch (Exception exception)
{
    FieldInfo remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
    remoteStackTraceString.SetValue(exception, exception.StackTrace);
    throw exception;
}

Это ставит исходную трассировку стека в поле _remoteStackTraceString исключения, которое объединяется в новую трассировку стека reset при повторном выпуске исключения.

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

Ответ 4

Хотя вы можете почувствовать, что TargetInvocationException "бесполезно", это реальность. Не пытайтесь притвориться, что .NET не взял оригинальное исключение и обернул его с помощью исключения TargetInvocationException и выбросил его. Это действительно так. В какой-то день вам может понадобиться какая-то часть информации, которая поступает из этой упаковки - например, может быть, местоположение кода, в котором было выбрано TargetInvocationException.

Ответ 5

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

Ответ 6

Использование ключевого слова "throw" с исключением всегда будет reset трассировкой стека.

Лучше всего поймать фактическое исключение, которое вы хотите, и использовать "throw"; вместо "throw ex". Или бросить свое собственное исключение, с InnerException, которое вы хотите передать.

Я не верю, что вы хотите сделать.

Ответ 7

Как говорили другие, используйте ключевое слово "throw", не добавляя к нему, чтобы сохранить цепочку исключений неповрежденной. Если вам нужно это оригинальное исключение (предполагая, что это то, что вы имеете в виду), вы можете вызвать Exception.GetBaseException() в конце своей цепочки, чтобы получить Исключение, которое запустило все это.

Ответ 8

Возможно с .net 4.5:

catch(Exception e)
{
   ExceptionDispatchInfo.Capture(e.InnerException).Throw();
}