Jak zmienić InnerException bez utraty stack trace w C#?
[1]}nazywam, przez refleksję, metodą, która może spowodować wyjątek. Jak mogę przekazać wyjątek mojemu rozmówcy bez odbicia owijki wokół niego?
Zmieniam koncepcję InnerException, ale to niszczy ślad stosu.
Przykładowy kod:
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;
}
}
9 answers
W . NET 4.5 jest teraz ExceptionDispatchInfo
klasy.
Pozwala to przechwycić wyjątek i rzucić go ponownie bez zmiany śladu stosu:
try
{
task.Wait();
}
catch(AggregateException ex)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
To działa na każdym wyjątku, nie tylko AggregateException
.
Został wprowadzony z powodu funkcji języka C# await
, która rozpakowuje wewnętrzne wyjątki z instancji AggregateException
, aby funkcje języka asynchronicznego były bardziej podobne do funkcji języka synchronicznego.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-15 12:35:10
To jest możliwe, aby zachować ślad stosu przed ponownym przeglądaniem bez refleksji:
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
}
To marnuje wiele cykli w porównaniu z wywołaniem InternalPreserveStackTrace
przez buforowanego delegata, ale ma tę zaletę, że polega tylko na publicznej funkcjonalności. Oto kilka typowych wzorców użycia dla funkcji zachowujących stos:
// 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 ;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-19 21:43:34
Myślę, że najlepiej byłoby umieścić to w bloku haczyk:
throw;
A następnie wyodrębnić innerexception później.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-08 18:45:56
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 );
}
}
Wywołanie metody rozszerzenia na Twoim wyjątku przed rzuceniem, zachowa oryginalny ślad stosu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-09 05:31:49
Jeszcze więcej refleksji...
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;
}
Należy pamiętać, że może to ulec awarii w dowolnym momencie, ponieważ pola prywatne nie są częścią API. Zobacz dalszą dyskusję na temat mono bugzilla .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-18 12:18:27
Po pierwsze: nie trać TargetInvocationException - to cenne informacje, gdy będziesz chciał debugować rzeczy.
Po drugie: zawiń krawat jako InnerException we własnym typie wyjątku i umieść właściwość OriginalException, która łączy się z tym, czego potrzebujesz (i zachowuje cały callstack nienaruszony).
Po trzecie: niech bańka krawat z metody.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-15 19:32:57
Nikt nie wyjaśnił różnicy między ExceptionDispatchInfo.Capture( ex ).Throw()
a zwykłym throw
, więc oto ona.
Kompletnym sposobem na zmianę przechwyconego wyjątku jest użycie ExceptionDispatchInfo.Capture( ex ).Throw()
(dostępne tylko z. Net 4.5).
Poniżej znajdują się przypadki niezbędne do przetestowania tego:
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 );
}
}
Przypadek 1 i Przypadek 2 dadzą ślad stosu, w którym numer linii kodu źródłowego metody CallingMethod
jest numerem linii throw new Exception( "TEST" )
linia.
Jednak przypadek 3 daje ślad stosu, w którym numer linii kodu źródłowego metody CallingMethod
jest numerem linii wywołania throw
. Oznacza to, że jeśli linia throw new Exception( "TEST" )
jest otoczona innymi operacjami, nie masz pojęcia, na który numer linii został wyrzucony wyjątek.
Przypadek 4 jest podobny do przypadku 2, ponieważ numer linii oryginalnego wyjątku jest zachowany, ale nie jest prawdziwym rethrow, ponieważ zmienia typ oryginalnego wyjątku.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-14 13:39:00
Chłopaki, jesteście spoko.. Wkrótce zostanę nekromantą.
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))();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-02 17:48:10
Inny przykładowy kod, który używa WYJĄTKÓW serializacja/deserializacja. Nie wymaga, aby rzeczywisty typ wyjątku był serializowalny. Ponadto używa tylko publicznych / chronionych metod.
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 });
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-03 08:19:24