You're Throwing Exceptions Wrong! Don't Throw Away The Trace!
October 2, 2023
• 1,002 views
How are you throwing exceptions in C#? What approach do you use to rethrow exceptions? You might not know it yet, but you could be throwing away the entire stack trace if you're doing it wrong! In this video, we'll cover how to rethrow exceptions in CSharp so that you get the information you need.
Have you subscribed to my weekly newsletter yet? A 5-minute read every weekend, right to your inbox, so you can start your weekend learning off strong:
https://www.devleader.ca/newsletter
Check out m...
View Transcript
exceptions if you've been programming in c-sharp or any other language for at least a little bit you've encountered exceptions in some of your code now nearly all of the time when we have exceptions coming up in our code this is not a good thing and it's up to you as the developer to make sure that your code can handle exceptional cases now in C sharp we have a couple of different ways of doing this and if we're looking at just the basics we're going to be looking at a try catch block however even with a try catch block there's a couple of things that we have to keep in mind so if you're just getting started with C sharp or you haven't really spent a lot of time trying to think about different ways that you can handle exceptions you might be making some mistakes
perhaps some opportunities to do things better in this video I'm going to show you a couple of different ways that try catch blocks work and that way the code that you're writing in your catch blocks will do what you expect with that said let's jump over to visual studio and start throwing some exceptions alright I'm here in visual studio with a bunch of code that is probably the most dense set of try catch blocks I have ever written in any program but we're gonna have some fun with this and I'm gonna walk us through through the example code that we have and how the try catch blocks behave so on my screen at the top level we can see that we have this big try catch block and what we're going to be doing is comparing scenario one and scenario two that I have defined
right here so I'll explain those in just a moment but we're going to walk through the other methods just so you can see what's happening essentially what we're going to be looking at is the stack Trace behavior on an exception when we catch exceptions in a catch block and we have to consider rethrowing them this is a pretty common thing that happens in try catch blocks along with logging or some other type of handling however a lot of people don't understand the implications of the code they're writing so we're going to have a closer look at that so both of these scenarios are going to end up calling method one and then I'm just going to kind of Step through this super quick because you'll see a pattern and that's the method one calls method two method two calls method three so on and so
forth until we get all the way to Method four here and Method four is just going to be the spot that throws this invalid operation exception and just put a little message in here that said expected exception that way when we throw it and we see it in some of the stack Trace output we know what we're looking for so pause for a moment and just kind of think about what you might expect a stack Trace to look like if we were catching an exception at the top level of the program that's calling these methods if you think about it because we have each of these chained together we should probably see something like method four in the stack tray somewhere method three two and one as well so if we go to look at scenario one and scenario two like I said they're calling
method one which changed through all of the code we just looked at and therefore if we end up having to catch an exception thrown from somewhere inside of here we should also see either scenario one or scenario two somewhere in the stack trace the first example that we're going to look at is calling method one and then if we look at the catch block this is where this is going to be a little bit unique compared to the other scenario in this case we're catching an exception and I just have it captured as X here and then we're going to actually throw this exception as well now there's a little bit of a squiggly here and this is I think a little bit newer in Visual Studio because I haven't seen this before but it's a bit of a spoiler if we go compare this
to scenario two we can see it's a very similar setup like I said we're still calling method 1 and chaining through the other methods however this line here on line 30 looks a little bit different than line 18. and the difference is that we're not actually rethrowing specifically this exception so this is going to have different behavior and we're going to take a look at what happens okay before I show you the output of both of these runs I wanted you to pause and think about this for a moment to try and think about what differences you might expect to see in the stack traces I went through it pretty quickly but the only differences between the two scenarios that we're going to be running are just the throw followed by the exception object or just a throw without anything after it both of these
scenarios will call method one which ends up calling all of the other methods as well and then throwing an exception from method four so the execution of the code that we're going to be calling is the same but what about what the stack Trace looks like based on the behavior that we have in the catch blocks so if you have something in mind already great let's go ahead and run these check out the output and then let's see if you're right all right so this is the output for scenario one and I know it's a little bit messy and kind of hard to read but let's have a look at the stack Trace that we see which is going to be where I'm highlighting with my cursor so as we can see program.cs shows up at lion 3 and then from there the only other
line that we have showing up and it's a little bit hard to see here but it's line 18 in program.cs and what's interesting about that is if we look at the method calls that we have right we have the main method which because I'm not using top level statements I actually didn't explicitly write a main method but that's going to be the entry point for every C sharp program so we have that main method show up which is what we would expect right at the top of the stack Trace but the only other method that we have showing up in our stack Trace is actually just scenario one rethrow explicit this is interesting because we were saying earlier that method one all the way through method 4 should show up in our stack trace and that's because we're actually throwing the exception from method four
so to recall scenario one actually had in the catch block a throw followed by the exception object that was captured this behavior that we're seeing here in the stack Trace is probably not what we would want but let's go ahead and run scenario two so that we can compare it all right and here is scenario two which has the catch block with just a throw and no explicit exception object after it you can see at the top of this console output there is a lot larger stack Trace if we examine it at the top of the stack Trace we still have that main method that was implicit just like the other example we just looked at from there instead of going into scenario one we go into scenario two and that's what we would expect in this case but compared to scenario one scenario one
actually just stopped here and if we can see in this case we actually have many more lines so the next method that we have is actually method one which is awesome this is what we said we think should happen and then method two then three and then 4 where the exceptions actually thrown so in this case we actually had the full resolution of where the exception was thrown from however in the first case we essentially had up to this point where we just had stack Trace information so we ended up only seeing two lines so we only had line three and then the other line for scenario one as well all right so just a quick check is that what you were expecting to see why do we have different Behavior like that when the catch blocks are so similar what's the difference between just
doing throw and doing throw followed by the exception that was already being caught well the answer in this case actually comes from the visual studio tip that shows in the intellisense and that's that if you're doing throw with an exception that's already been caught you're actually going to rewrite the stack trace and I would say almost all of the time this is probably not what you want because having more stack Trace information is going to be very helpful in debugging issues in fact off the top of my head I can't think of a time where you would want to do this but perhaps there is a case and that's why they allow it without just preventing it outright so the takeaway so far is that when you're doing some type of handling in a catch block so say you're logging something or you're trying to
do some cleanup or some other checks whatever you happen to be doing if you need to continue to Bubble Up that exception the pattern that you want to be using is not throw followed by the exception you just captured but instead just throw but is that the only thing that we can do in a catch block when it comes to throwing or is there something else that we might want to consider so this becomes an interesting question and I don't think that there's any right answer here but out of the options that we looked at I think we have a clear winner of the two that we would want to use but there is a third option that you might want to consider and that's actually throwing a new exception which is different than just throwing the one that we caught and in that case
yes you are going to be writing the stack Trace information of the new exception that you're throwing so technically it's not rewriting it it's just creating a new Stacker trees but what we can do is actually pass in the reference of the exception we just caught into that new exception so that we do end up getting full resolution but why would we want to do this what are the scenarios where that might make sense to actually catch an exception and then put it inside of another one as you're trying to think up some of your own examples one that comes to mind for me is when we're thinking about the different contacts or domain that our application is working in and what I mean by that is when we're thinking about the different type of Stack trace or error information that we want to see
when debugging perhaps there's more context that we could be adding than just bubbling up an exception from the depths of somewhere else that we're calling and if you're still trying to think up an example one that comes to mind for me specifically is parsing so if you had an application and somewhere in your large amount of code that you have for all of your business logic you have to call something to do some parsing perhaps one of the exceptions that gets thrown away in the depths of your code so something like format exception invalid care character at position seven and what's interesting is that if you use the pattern that we looked at and you don't just re-throw an exception that you're catching but instead if you have to catch and rethrow you just Bubble Up the original you would get the stack Trace that
you want to see and you would end up seeing that you have it some depth in your code some format exception that's indicating what the error is but aside from just that exception in the stack Trace you'd have to go essentially figure out based on the stack Trace what's happening and that might not be so bad maybe your application's straightforward enough where you can get by doing that and debugging is pretty easy however consider a scenario where if you were in some parsing logic we could actually say at a level above it that's trying to catch and handle that exception just to rethrow it that we can add in some information about what we're trying to parse why we're trying to parse it who we're trying to parse it for if we had additional helpful information that would make debugging easier we could actually go
make a new exception that would get caught at some higher point in the application then from there inject some additional helpful information for someone debugging and what we could do is take that exception that was captured and embed it inside of the new exception that we created this would mean that is the exception that we just created bubbles up to the next catch block we will see a stack Trace that goes to the one we just threw however if you've examine the inner exception you will still have the full resolution of the stack Trace from the original exception so let's go back to visual studio I'm going to tweak the example just a little bit it's not going to be super helpful for demonstrating that example that I just walked through however I think it will illustrate the point that I'm trying to make and
that way you can have an idea of how to apply this alright so I'm back in Visual Studio I've just created a scenario three that's called erythro custom and all that I did was copy and paste the code actually from Scenario one so this is not implemented yet but let's walk through what this could look like we were saying that we don't actually want to do exactly this and yes the squiggly is giving it away for us and as we saw if we do this we'll rewrite the stack Trace information which is no good but what we could do is go make a custom exception that we want to throw here and put in extra helpful information now copilot is being super helpful I don't know if it can hear what I'm saying or not which is a little bit creepy but if I just
press tab that's basically going to demonstrate most of the example that I wanted to put together if we clean up this code just a little bit the idea that we have here now is that we're throwing a new exception like we just talked about doing we can put in some extra information so here I don't have a lot of extra stuff that I could add because we're just walking through a really simple example but what I was just talking about is maybe saying some information about what we're trying to parse if we're going back to that parse for example maybe if you're doing it for a certain user or a certain calling service that you could provide information about that and that way you have have this opportunity to pass in some other useful info for debugging purposes that will make the life of someone
debugging this a lot easier you do want to keep in mind that you don't want to go over the top with this because adding more information here could mean that you're putting in stuff that should not show up in logs you want to be careful about that but you do want to think about someone who has to look at this exception and if you can make their life easier and so that we don't fall into the Trap of being in scenario one we actually pass in the exception that was captured here and that way we have the option later on to go see if we can use the stack Trace to be more helpful however if I go run this code it looks just like scenario one it's not super useful we do see scenario three here so we know we're calling the right code
but why are we only seeing these two lines of the stack trees well the answer to that is because we're only printing out the stack Trace information for the exception that we caught which is the one that we just ended up creating and growing if we want to get the exception and stack Trace information from the exception that was caught earlier and embedded into the one that we created we would have to ask for the inner exception property on the exception object let's go back to visual studio change things up a little bit just so we can see that and prove that that information exists so if I go modify the original part of the core application here which is just this try catch block instead just for scenario three I'm going to put inner exception here which you need to be careful because it's
not always going to be set and in fact in the first two scenarios it will not be set but just in this case just to prove that we do have that useful stack Trace information I'm going to put this here and let's see what the stack Trace looks like all right so this is the output when we're looking at the inner exceptions stack trace and if we pay attention this looks like it's almost the exact same as scenario two where we were just doing the wreath row with no exception but it is slave different if you recall every example that I've shown so far the top level of the stack Trace that's going to end up being at the bottom part here is going to say that it's in the main method of the program and if you look closely the first method that we're
actually in is scenario three now this is really interesting because we're catching that exception it actually doesn't show the information about being in the main method at any point however what we have from there on is exactly what we would expect so starting from Scenario three we go into method one two three and then four where the exceptions actually thrown so what this tells us is that if we have inner exceptions on the exceptions we're catching and we want to get full information about the stack traces we can look at the stack traces across multiple exceptions and try to paint a picture of what's going on now this gets even more complicated when we have other exception types like aggregate exceptions that are allowed to have more than one in inner exception as well this is really common for asynchronous code so if you're running
things in different threads or on different tasks and then an aggregate exception ends up getting thrown sometimes you'll notice that you have more than one exception inside of there so this ends up being like a snapshot of different exceptions that were thrown and it's going to be a little bit more confusing because you have these different Paths of code that you'd need to look through but perhaps that's all the more reason to think about actually wrapping exceptions that you're catching with more useful information in a custom exception with that said though I'm not going to necessarily recommend that you do that all of the time I think it's just an option that you have to consider if you do think it will be valuable to add more debugging information to the exception alright so that's going to wrap it up for today's look at exceptions
and C sharp and the different variations that we looked at for try catch blocks were scenarios where we were re-throwing an exception that we just caught but what we were doing with that is putting throw followed by the exception object the reference to the exception that we just caught and then allowing that to Bubble Up the output of that was not really desirable because the stack Trace ended up getting Rewritten from the point that we were doing the new throw that meant that we lost all of the information about where the original exception was being thrown from Scenario two is the preferred pattern where you don't actually rethrow the specific exception reference that was caught but instead you just say throw this will implicitly allow the exception that was caught to Bubble Up and you will get the full stack Trace resolution that you're after
and the third variation that we looked at is a little bit of a bonus and that's a bit of a hybrid between the two scenarios you might find that there are instances in your application where when you're able to catch an exception you're able to provide more context to the caller about what's going on if you think about trying to be helpful for the person debugging this looking through logs or whatever else you might have additional contacts that you can put into a new exception and then pass in the reference of to the exception you caught such that it's an inner exception of the new one this will mean that it makes it a little bit more complicated for people to actually look through the exception object that bubbles up so you might have different inner exceptions and other stack traces to look at but
all of the information that you might be interested in should be there now if you're on the fence between option two and three that we looked at if you don't really think that you're adding additional helpful information please just stick with option two it's definitely the safer route but we could all see today that when we looked at scenario one versus two we don't want to be using scenario one which is going to rewrite all of that stack Trace info so that's it for this video I hope you found it helpful thanks and we'll see you next time foreign
Frequently Asked Questions
What is the main difference between throwing an exception with the exception object and just throwing the exception?
The main difference is that when I throw an exception with the exception object, it rewrites the stack trace, which often isn't what I want. Instead, if I just use 'throw' without the object, it allows the original stack trace to bubble up, giving me the full context of where the exception originated.
When should I consider creating a new exception instead of rethrowing the caught exception?
I might consider creating a new exception when I want to add more context or information that could be helpful for debugging. For example, if I’m handling a parsing error, I could create a new exception that includes details about what I was trying to parse, while still keeping the original exception as an inner exception for full resolution.
What should I do if I want to see the full stack trace of an inner exception?
To see the full stack trace of an inner exception, I need to access the 'InnerException' property of the exception object. This way, I can get the complete stack trace information from the original exception that was caught.
These FAQs were generated by AI from the video transcript.