Как сбросить InnerException без потери трассировки стека в С#?
Я размышляю о методе, который может вызвать исключение. Как я могу передать исключение своему вызывающему абоненту без использования обертки?
Я перебрасываю 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;
}
}
Ответы
Ответ 1
В .NET 4.5 теперь существует класс ExceptionDispatchInfo
.
Это позволяет вам фиксировать исключение и повторно бросать его без изменения трассировки стека:
try
{
task.Wait();
}
catch(AggregateException ex)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
Это работает для любого исключения, а не только AggregateException
.
Он был введен благодаря функции языка await
С#, которая разворачивает внутренние исключения из экземпляров AggregateException
, чтобы сделать асинхронные языковые функции более похожими на синхронные языковые функции.
Ответ 2
можно сохранить трассировку стека, прежде чем реконструировать без отражения:
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 ;
}
Ответ 3
Думаю, ваш лучший выбор - просто поместить это в свой блок catch:
throw;
И затем извлеките innerexception позже.
Ответ 4
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 );
}
}
Вызовите метод расширения в своем исключении перед его броском, он сохранит исходную трассировку стека.
Ответ 5
Еще большее отражение...
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.
Ответ 6
Во-первых: не теряйте TargetInvocationException - это ценная информация, когда вы хотите отлаживать вещи.
Во-вторых: оберните TIE как InnerException в свой собственный тип исключения и поместите свойство OriginalException, которое ссылается на то, что вам нужно (и сохраните весь столбец без изменений).
В-третьих: пусть пузырь TIE из вашего метода.
Ответ 7
Никто не объяснил разницу между 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, потому что номер строки исходного исключения сохраняется, но не является реальным ретроном, поскольку он изменяет тип исходного исключения.
Ответ 8
Ребята, вы классные.. Я скоро стану некромантом.
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))();
}
Ответ 9
Код примера, который использует сериализацию/десериализацию исключений.
Это не требует, чтобы фактический тип исключения был сериализуемым.
Также он использует только общедоступные/защищенные методы.
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 });
}
Ответ 10
На основании ответа Пола Тернерса я сделал метод расширения
public static Exception Capture(this Exception ex)
{
ExceptionDispatchInfo.Capture(ex).Throw();
return ex;
}
return ex
ist никогда не достигается, но преимущество в том, что я могу использовать throw ex.Capture()
в качестве однострочника, так что компилятор не будет вызывать, и not all code paths return a value
ошибку 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();
}
}