Throw vs Throw ex
This tidbit should be commonly known but in my experience is often overlooked and therefore worth a blog post. In short, there is a big difference between using Throw and Throw ex to re-throw an exception in .NET. Essentially, when using the Throw ex syntax parameters including the stack trace itself are reset resulting in unreliable stack traces when exceptions are bubbled up through an application.
To provide a brief example take a look at the following code. I have created a console application with three classes:
We have Program – the entry point to the console application. The main procedure creates an instance of a DogWalkerClass and runs the WalkDogWithThrowEx or WalkDogWithThrow procedure depending on the value of the throwWithEx field. Both of these procedures in turn create an instance of the BadDog class and calls the WalkDog procedure. The WalkDog procedure has one-and-only-one purpose – to throw an exception.
Program.cs
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5:6: namespace ThrowDemo7: {8: class Program9: {10: static void Main(string[] args)11: {12: bool throwWithEx = true;13: DogWalkerClass dw = new DogWalkerClass();14:15: try16: {17: if (throwWithEx)18: {19: dw.WalkDogWithThrowEx();20: }21: else22: {23: dw.WalkDogWithThrow();24: }25: }26: catch (Exception ex)27: {28: Console.WriteLine(ex.StackTrace);29: Console.ReadLine();30: }31: }32: }33: }
DogWalkerClass.cs
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5:6: namespace ThrowDemo7: {8: class DogWalkerClass9: {10: public void WalkDogWithThrow()11: {12: try13: {14: BadDog gd = new BadDog();15: gd.WalkDog();16: }17: catch (Exception ex)18: {19: // TODO: Perform some additional logging here20: throw;21: }22: }23:24: public void WalkDogWithThrowEx()25: {26: try27: {28: BadDog gd = new BadDog();29: gd.WalkDog();30:31: }32: catch (Exception ex)33: {34: // TODO: Perform some additional logging here35: throw ex;36: }37: }38: }39: }40:
BadDog.cs
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5:6: namespace ThrowDemo7: {8: class BadDog9: {10: public void WalkDog()11: {12: throw new Exception("An error occurred in the baddog class");13: }14: }15: }16:
If the throwWithEx field is set to true, the WalkDogWithThrowEx procedure is called. Inevitably the WalkDog procedure is called, an exception is thrown and is bubbled back up to the Program class. Because we are using the Throw ex syntax the stacktrace is reset locally upon re-throwing the exception and we are left with a shallow stacktrace that indicates the error originated in the DogWalkerClass class at line 35. We know this to be false – since we threw the exception ourselves in the BadDog class.
If, on the other hand, the throwWithEx field is set to false, the WalkDogWithThrow procedure is called. Again, the WalkDog procedure is called, an exception is thrown and is bubbled back up to the Program class. This time we are using the Throw ex syntax. The stacktrace is persisted (i.e. not reset) and we are left with the original, deeper, stacktrace that indicates the error originated in the BadDog class at line 12.
This is certainly not rocket science – but you’d be surprised how often this is misunderstood or not known at all. Try to envision the additional complexity involved if we had 1,000,000 LOC rather than a paltry 100 LOC. It’s the small things that get you in the end…
Comments