Většina vývojářů je asi obeznámena s tím, jaký rozdíl je mezi throw a thow ex tj. pokud místo: (*)
try
{
//do something that can throws
}
catch (Exception ex)
{
//Log exception or something
throw; //<--OK, original stack trace is preserved
}
provedeme toto:
try
{
//do something that can throws
}
catch (Exception ex)
{
//Log exception or something
throw ex; //<--BUG, original stack trace is lost
}
V druhém případě je původně zachycená výjimka znovu vyhozena, čímž přijdeme o původní stack trace informace tj. stack trace pak bude začínat až od nového místa opakovaného vyhození. Zatímco volání pouze throw zajistí správné šíření původní výjimky (ale lze samozřejmě použít pouze uvnitř daného catch bloku).
Pro úplnost ještě další, trochu jiný případ: Jak je to v případě, kdy potřebujeme vyhodit výjimku jinou (jiný datový typ, doplnění informací o aktuálním kontextu apod.)?
try
{
//do something that can throws
}
catch (Exception ex)
{
throw new ApplicationException("Operation failed, more details in InnerException.", ex); //<--Stack trace is preserved in InnerException
}
To také není problém, když to uděláme chytře tj. když do nově zkonstruované výjimky předáme výjimku původní jako InnerException. Tím zajistíme, že ve výpisech (např. přes ToString() na samotné výjimce) apod. bude nový a původní stack trace “složen” dohromady a my o žádnou informaci nepřijdeme.
Tak jo, zatím možná pouze “opáčko”, ale teď pozor! Co když se najde scénář, kdy z nějakého důvodu opravdu potřebujeme opakovaně vyhodit původně zachycenou výjimku a throw přitom použít nelze (tj. opakované vyhození nelze provést v původním catch bloku). I když se sice může na první pohled zdát takový případ obtížně najitelný, již jsem se s několika v praxi setkal. Může se např. jednat o obsluhu chyby asynchronního volání nebo třeba o multi-threadingový scénář, kde je potřeba výjimku předat např. z pracovního vlákna do vlákna hlavního.
Pro takové případy bude velice užitečná extension metoda ExceptionExtensions.PrepareForRethrow(), jejíž implementace (†) vypadá takto:
using System;
using System.Reflection;
namespace System.Diagnostics
{
internal static class ExceptionExtensions
{
#region member varible and default property initialization
private static readonly MethodInfo prepForRemoting = typeof(Exception).GetMethod("PrepForRemoting", BindingFlags.NonPublic | BindingFlags.Instance);
#endregion
#region action methods
public static Exception PrepareForRethrow(this Exception exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
prepForRemoting.Invoke(exception, new object[0]);
return exception;
}
#endregion
}
}
Implementace metody využívá toho, že objekt Exception má sám o sobě již od prvních verzích .NET Frameworku tuto funkci interně zabudovanou a využívanou technologií .NET Remoting (pokud jste se s touto technologií již nesetkali, jedná se o předchůdce WCF). Tato metoda tedy pouze pomoci reflekce volá příslušnou interní funkci (‡).
Metodu PrepareForRethrow() pak stačí na výjimce volat před jejím opakovaném vyhození. Při použití této metody bude ve výpisech původní a nový stack trace oddělen textem "Exception rethrowed at...".
Celé použití pak může vypadat následovně (opět pouze ilustrativní příklad):
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
...
var synchronizationContext = SynchronizationContext.Current; //UI thread
Task.Factory.StartNew(() =>
{
try
{
//...
}
catch (System.Exception ex)
{
synchronizationContext.Post(state => { throw ex.PrepareForRethrow(); }, null); //<--Rethrow back to original synchronization context
}
});
Zde při chybě z pracovního vlákna dojde k “rethrownutí” výjimky do původního synchronizačního kontextu, kterým je zpravidla UI (WPF Dispatcher, Windows Forms apod.), kde pak může být zachycena např. nějakým Application_UnhandledException handlerem apod.
(*) Nutno upozornit, že uvedený příklad je pouze ilustrativní a takového “chytání” obecné výjimky
System.Exception není vůbec správné, a je dokonce diskutabilní i právě pro případ pouhého “zalogování” a opakovaného vyhození. (Protože takovýmto
catch blokem může být výjimka obecně zachycena i v takovém případě, kdy je z důvodu nějaké kritické chyby nekonzistentní nebo nedefinovaný stav celého aktuálního procesu).
(†) Uvedená implementace je součástí také například knihovny Reactive Extensions for .NET (Rx) (System.CoreEx.dll).
(‡) Z důvodu nutnosti volání interní metody pomoci reflekce není bohužel toto řešení použitelné v technologii Silverlight.