Faster Way To Deal With Exceptions That You Aren't Using In #CSharp
October 6, 2023
• 1,208 views
If we hate exceptions in CSharp much, why are we throwing them? In this video, we'll look at some variations to throwing exceptions in C# in addition to some performance insights from benchmarkdotnet! We have alternatives to throwing exceptions all of the time!
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 more Dev Leader content (including ful...
View Transcript
as programmers exceptions are essentially the bane of our existence and if you're a c developer and you've created anything beyond a simple hello world program it's very likely you've encountered some type of exception but even though exceptions always seem to give us a hard time because they the reason we spend all of this time debugging we still seem to write code that throws exceptions on purpose so why is it that we do that if we don't like them so much and they're such a headache when we're debugging things why do we keep writing code that throws exception now in the last video I put out about exceptions which I'll link up here if you haven't seen it already we talked about the different ways that you can catch exceptions and the right way to be able to throw or rethrow an exception from a catch
block and not lose your stack trees now if you're interacting with code that you don't control and it can throw an exception you probably want to create some type of protection for yourself but if you do control that code and you are throwing an exception why are you doing that so in this video we're going to talk about some different options that you can leverage to not actually be throwing exceptions and we're also going to look at the performance impact of doing that because once you see that I think you're probably going to change your mind about all the different areas that you're throwing exceptions in your code now before we go over to visual Studio One disclaimer is that I do try to remain pragmatic so the fact that I'm kind of making fun about throwing exceptions I am exaggerating a little bit there's
a time and a place for probably everything so if I sat here and told you oh you should never throw an exception that's just not reality but I think personally exceptions should be intended for exceptional cases and I think one of the challenges that we end up having in software development is that if we take a smaller lens into the code we're looking at it's very likely that you would consider a case to be exceptional in the lens of that area that you're looking in however in a larger system and when you start combining these different things together an exceptional case here and an exceptional case there in the grand scheme of the entire application may not actually be an exceptional case to worry about so it's not that you should never throw them but I do think you're going to want to reconsider some
options that you have after we go through this so with that said let's jump over to visual studio all right so I'm here in visual studio and I'm actually using the same solution from the last video and what I've done is I've added some benchmarks that we're going to be looking at and instead of creating an entirely separate example I'm just going to use the benchmarks to explain the different variations that we can go looking at so the two different classes that I have that we're going to be benchmarking for sort of the normal cases that we were looking at before have to do with the implicit and explicit throwing and what I mean by that for implicit and explicit is if I go expand this explicit one it'll be a little bit more obvious but if we go look at something like method one
here where we have this Tri catch block when I'm talking about an explicit rethrow I mean that we're taking the exception that we capture from the catch block nesting it inside of the inner exception and rethrowing a new one conversely an implicit throw would look something more like this and this implicit throw as the name suggests is that it's actually implicitly just throwing the exception we already caught and if you recall from the last video if you did go through and watch that what we don't want to be doing is this because this will actually lose the stack trace of the exception we caught that was really the lesson from the last video so the two basee cases that we're going to be looking at are when we actually have an explicit throw like this and if I just kind of scroll through this to
show you what's going on because the other class will look very similar we have an entry point to the class I'm not very creative with my naming so I apologize but this will be the entry point we're going to have a TR catch block around the whole thing and we're going to call method one from the entry point and you'll notice here that it's a Boolean return type in hindsight after I put this example together I probably should have used something like an integer or whatever just so that you don't totally see exceptions and booleans being synonymous with each other so I apologize for that but in this particular case I am kind of meaning that as you see right here so in the exceptional case I will return a false and in the happy path I will return a true this will make a
little bit more sense as I go explain the other scenarios so if we go look at method one it looks very similar right I have a TR catch block I'm just going to be rethrowing an exception that we catch and putting that as the inner exception if we go to Method two it's very similar right there's a lot of copy paste going on uh in fact we might actually catch a bug if we keep scrolling through but keep your eyes peeled so method two will call method three do the same type of thing method three calls method four and I ended up stopping it right at method 5 so when we call method 5 that's where we're going to be throwing an exception and the point of the example code being structured like this is that we can show that when we throw an exception
in some nested method and we want to go catch it and then throw a new exception sort of passing in the Cod exception is the inner one like you see right here we're going to want to measure the performance of that versus just the implicit throw so let's go check out the implicit throw variation of this and it's going to look very very similar so as I scroll up here here and expand this part we'll notice that we also have an entry point exact same type of setup where I just have a return false when there's an exception and we're going to turn true when there's no exception if we look at method one I'm not going to bore you with kind of explaining it all over again except for this part where I'm not rethrowing explicitly by creating a new exception and passing this
as an inner one I'm just implicitly throwing the exception that was caught if I quickly scroll through the other ones you'll notice that they all just have throw until we get to Method five which is where we actually have this exception that we're throwing in the first place so hopefully those two comparisons make a lot of sense they're very similar except the implicit and explicit throw like I was trying to call out in the other video all right so the main point of putting this video together is to show you the differences when you're throwing exceptions on purpose versus avoiding them altogether so the two classes we looked at have some logic that at the very bottom that last method method five because I'm so creative with my naming that method actually throws the exception now when we go to look at the other class
this one's not going to throw exceptions but instead of just showing you that right away I wanted to give you a more practical example to Think Through just so that you're not going well this is super contrived they don't really get the point so a common place that I see this type of thing that you may have control over in your own code if you're writing things like this are parsers so if you're writing code that parses input pars is a file pars is something from a website maybe you're looking at website text and you're looking for a particular part of the HTML whatever it happens to be when you're writing parsers unfortunately we have situations where we don't always control the input if you're writing a parser in the context of your parser you might be saying hey it's an exceptional case if we
have invalid characters or the structure that we're looking to parse is not as expected and that might be totally fair if your program is a dedicated parser or you're making a library that's a dedicated parser perhaps from your perspective having invalid characters bad structure whatever it happens to be you consider an exception the alternative lens that I want you to have as we walk through this is thinking about what that might mean if we instead don't throw exceptions because what what we're going to see is a performance difference between the two spoiler alert but how much of a performance difference I think is what's going to surprise you if you had a parser and you had all of this code that was looking for the proper format so you could extract the right pieces and parse what was necessary instead of throwing an exception when
you come across some format that's not expected I would say if you switch that to instead return a Boolean or some other type of State you could use an enum whatever it happens to be maybe in this case you do want to return the exception so that there's some information associated with why it's invalid you have a bunch of different options but instead of throwing instead of throwing that exception and letting it bubble up instead switching to some type of return value that indicates a success State and the actual value that you want to have when it is successful if it's not successful you would just have some other state on the return type there's a bunch of different ways to do this I have articles and videos that show other ways we're going to look at using a tupple I think people pronounce it
Tuple I can't say that so I am going to say tupple in this video I apologize if that drives you nuts let's go back to visual studio and look at some tupples all right so if you're not already triggered by my usage of the word tupple instead of Tuple thanks for sticking around let's go look at this non thrower class that should demonstrate very similar functionality but instead of throwing we're going going to be returning some values I'm going to come back to this because until we see how this is actually structured it may not make a lot of sense let's start instead from the very bottom in method 5 okay so in method 5 in the other examples we were throwing an exception what I have highlighted here might be a little bit confusing if you're not used to this syntax so bear with
me as I explain it what we have instead of a Boolean on the return type or a void in the other examples is that we have a tupple or a tuple um of exception that's nullable and a Boolean that's nullable now this isn't my preferred way to do this I just wanted to demonstrate it for this video so you had a visual example that we could show both of these things I'll elaborate on this a little bit more as we go through the example so instead of throwing an exception in method 5 what instead we're doing is having a return of a new instance of this of this tupple or Tuple and then we're going to make a new exception right this is very similar to before but instead of throwing we're just making a new one the difference is that when we throw an
exception we actually have to acquire some of the stack information so getting that trace and being able to show that in logs and stuff that information gets jammed into the exception when we throw it in this particular case there is no stack trees that's something to keep in mind as we go through this the other thing that we're doing is because the other examples had a Boolean I am doing the same thing here and this is just the reminder that I said at the beginning where said I probably shouldn't have picked Boolean because I don't want to confuse you between having a Boolean and an exception here if this was an integer it probably would have made the example a little bit more clear so having false or having true here doesn't actually matter this is just signifying the return type that we're interested in
and this is the state that will tell us if it's a pass or fail to give you another example of how this could be structured let's look at this method that I Just Whipped up super quick instead I've actually used a Boolean and a integer that's nullable and you'll notice that I'm not using an exception anymore it's just a Bo and I'm calling it success I've named these which I could do up here as well right I can put stuff there and the names but the example that I want to illustrate here that we're just going to apply to the other part of the code is that we have some piece that it's going to indicate State about pass or fail so here I'm using a Boolean here I'm using an exception or null and then the second part that we're doing here is actually
the value that we're interested in so if this were going to be a false value on the return and the number that we want to return because something failed we probably put null here right so it might look like this on a failure that's the difference that we're looking at here so that's the point of structuring it like this where we have one part that is a pass or fail State and the other part is the value that you're interested in if things work so with that said that's what this means in method 5 the hardcoded example that I'm using is that this is going to create an exception not throw it though and that we're passing back a true this could be a false it doesn't really matter but the point is that this is indicating a fail here because we do have an
exception now you'll notice as I scroll up through the other methods I'm not putting a TR catch around everything I don't need to have a TR catch because the intended point of this signature is that we are supposed to be indicating a pass or fail with this exception and then the value that we're interested in is the other part of the Tuple so if I keep scrolling you'll notice all of them have the same format no TR catches because we don't have to worry about that until we get to the entry point method just like the other examples and still you'll notice that I don't have a TR catch so again the Boolean Choice was kind of silly of me I don't mean for that to be confusing but what this is going to mean because I wanted to keep this part the same as
the other examples is that we will return true off of here if there's no exception and we'll return false if there is an exception and that's why here we can see this syntax where we're checking item one which is the exception I could have named it like I said so we could have done error and then made this part error that would be a lot more readable we have that option I just didn't indicate it here but this means a pass if it's null and a fail if there's an exception and just to kind of make it more clear I was saying yeah we would probably do something with the results so item two in this case if we really cared but we're just going to be benchmarking this hardcoded scenario now before I go run the benchmarks I just wanted to talk about this
return type because I said a little bit earlier I'm not actually a fan of doing this I just wanted to illustrate how this works with a tupal or a because of the visibility of these objects here personally how I like to do this is I have a dedicated return type that I've created in my own libraries and you can actually use one out of the box with a nougat package called one of the one of nougat package allows you to have a class that can have as the name suggests one of two different types on it so it's very similar to how we have this syntax where we have item one and item two that we can check it's really a type that can can have as it's instantiated only one of two different options you either in this case pass in an exception or
you pass in this it cannot be both types at the same time so the one of Nate package allows you to have that that's something I might recommend if you're interested in this kind of syntax here and the one that I created actually uses implicit operators in a similar way to the one of Nate package but I wanted to roll my own mainly because I wanted to learn about it so that's what I like using instead of tles here but that's really up to you if you like this syntax and you want to make it more readable like I said you could do something like exception as the name and then you could have value and that way you don't need to deal with item one because it's a little bit misleading harder to read and you can do this kind of thing so that's
totally an option if you do want to stick with these topples okay you're probably sick of me blabbing and you're like Nick just show us the results because you're talking in up throwing exceptions is being this really bad thing and not throwing them is being super awesome so let's go see the difference between running these which throw exceptions against this non-throw which just passes information back and does not do any try catch okay so by the time I can even introduce what we have on the screen here your eyes have probably already figured out where the results are and the Staggering difference that we have so the implicit rethrow and explicit rethrow benchmarks that I have here are at 38,000 and 37,000 roughly microc seconds which is going to be 38 and 37 uh respectively milliseconds so if we look at that compared to the
non-throw right so this case here that does not throw exceptions but instead just Returns the state it is three orders of magnitude with respect to the speed that it runs at that is absolutely staggering if you think about running code that is going to be in the hot path of your application and it's throwing exceptions right so you have something that's parsing data and using a parser you've written that throws exceptions when there's an invalid character instead of just returning true or false or even returning an exception object with information about why it's an exceptional case for your parser look at the difference that we have here and I just wanted to add that if you're looking at these Benchmark results and going okay well I can see the data but I don't really want to trust it because the example seem pretty made up
kind of silly I wanted to add that in my professional experience I spent years at a digital forensic software company writing software that would scan hard drives mobile phones looking for patterns of text this meant that we would have all sorts of cases across gigabytes of data where we had no control over the format so I've spent a ton of time personally writing parsers of all different kinds and what we noticed especially in the early days when we were in startup mode was that we had tons of code that would try catch on different things and we realized that that's absolutely destroying our performance we needed to move away from try catch around everything and start saying look it's not truly an exceptional case the difference was that when we were calling code that we did not control and that code was throwing exceptions we
didn't have much of a choice so instead we would try to pre validate things before letting that other code grow an exception we were trying to do everything we could to squeeze performance out of this another data point if you don't want to believe me is on the other video we actually had someone awesome from the gaming industry commenting on that video and indicating that they don't even use C for this kind of thing and they've absolutely seen these situations with exceptions destroying performance so if you don't want to believe me go back to that other video check out the comments and you'll see from someone in the gaming industry that's had a heck of a time having to deal with this okay so to summarize this video we looked at benchmarks that were comparing code we saw in the previous video about throwing implicit
exceptions so we'd catch them in the TR catch block and throw that implicitly and then explicit throws where we would actually create a new exception and Nest the other one that we caught as the inner exception we compared those with a new approach that actually has nothing to do with throwing exceptions but just to make it more comparable we still used an exception object and just returned it instead of throwing it now the benchmarks are pretty staggering and if you were paying attention I actually didn't call this out but the benchmarks show that the implicit throw which should be doing not much extra work was somehow actually slower than the one where we were rethrowing a new exception and passing in the inner exception into that I can't really explain that yet that seems a little bit bizarre to me they were comparable but it
doesn't make sense that we would be creating a whole new exception object and passing in a parameter and the other one that just rethrows implicitly was somehow a little bit slower so if you had a Keen Eye good job you caught it but the one that I was trying to call out more specifically for this video is that when we don't throw exceptions but instead avoid them all together we can actually cut down the time a dramatic amount so the question that I want to put back to you is essentially when you're looking through your code truly consider if you're dealing with an exceptional case and you need to throw an exception to Halt execution or if it's just a case that you're not really happy with and instead you can return some state to say this is no good I don't know your code
you know your code much better than I do so I don't want to dictate what you should and should not do I just want to give you some variations that you can play with so that if you're looking to optimize you can and with that said about variations we also covered a couple of different ways that you can have return types for this kind of thing I showed in this example using a tupple that has an exception as the first part and then the value that we're interested in as the second part and we can use that to have one return type that has the state to indicate a pass or fail in this case using an exception or null and the second part actually has the value I also demonstrated you can do the same type of thing with just a Boolean FL FL
as the first part you could go make an enum as well so all of those are options and the other thing I called out but didn't demonstrate in the video is that I use personally something that's like a one of type and you can actually use a Nate package for this and that return type allows you to essentially either have a state piece of information or like an exception or the type that you're interested in if it's successful so using something like that as the return type you can can totally avoid throwing exceptions if you don't want to and instead return the state so there's a couple of different things for you to play around with with handling exceptions in your code if you don't want to be throwing them anymore you actually have some options to speed things up and in my opinion it
makes the code a lot more readable when you don't have to worry about try catching everywhere but can instead explicitly check for different types of Errors personally I like it a lot better so thanks so much for watching I hope you got to learn something cool about this and we'll see you next time
Frequently Asked Questions
Why do we still throw exceptions in our code if they cause so many issues?
As programmers, we often throw exceptions to handle unexpected situations that arise during the execution of our code. While exceptions can be a headache during debugging, they serve a purpose in indicating that something went wrong. However, I believe exceptions should be reserved for truly exceptional cases, and we should consider alternative approaches for handling errors in more routine scenarios.
What are some alternatives to throwing exceptions in C#?
Instead of throwing exceptions, I suggest using return types that indicate success or failure, such as a Boolean or a tuple that contains an error state and the desired value. This way, you can avoid the performance overhead associated with exceptions and make your code more readable.
How significant is the performance difference between throwing exceptions and using alternative error handling methods?
The performance difference is quite staggering. In my benchmarks, using traditional exception handling resulted in execution times that were three orders of magnitude slower compared to approaches that simply returned a state without throwing exceptions. This highlights the importance of reconsidering how we handle errors in our code.
These FAQs were generated by AI from the video transcript.