Exceptions in C#, Done Right
This tutorial will teach you how to correctly use C# exceptions and when to use them.
Depending on how they are thrown and how they are treated afterward, exceptions can either be exceedingly useful, or terribly annoying. After several years of experience, here are some strategies that I’ve found work well.
The Exception–Not the Rule
Exceptions should not be thrown under normal, expected circumstances. Sometimes you’ll find a method that looks like this:
public User validate(string username, string password)
{
if(!UserExists(username))
throw new UserNotFoundException();
if(!IsValid(username, password))
throw new InvalidCredentialsException();
return GetUser(username);
}
This is wrong! Exceptions are meant to be thrown for circumstances that might make it impossible for your program to continue in its normal flow of operation. They’re for things like “my hard drive is missing.” Invalid credentials, on the other hand, are a completely normal and expected part of everyday life. When that’s the case, you should make your method’s return type reflect the different possible results of calling your method, under normal circumstances:
public ValidationResult validate(string username, string password)
{
if(!UserExists(username))
return new ValidationResult(ValidationStatus.UserNotFound);
if(!IsValid(username, password))
return new ValidationResult(ValidationStatus.InvalidPassword);
var user = GetUser(username);
return new ValidationResult(ValidationStatus.Valid, user);
}
Know When to Throw Exceptions
Any time you have a situation where your method makes basic assumptions about its state or input parameters, you should check that assumption and throw an exception if it turns out not to be the case. Using the validate method above, what assumptions are we making?
username
andpassword
should not be null. Looking at this method, we cannot know howUserExists
,IsValid
, orGetUser
would behave if they are given null strings. Rather than depending on their behavior, we should throw an exception if this basic assumption is not met.GetUser
should not be returning a null value. By the time we call it, we should already have checked that the user exists. If we get a null value here, it’s because something is terribly wrong with either theUserExists
or theGetUser
method, or there was some kind of race condition where the user got deleted just as you were about to validate them. None of these are situations that our method can handle gracefully. If we allowed a null value to be passed back with aValid
result, we’d probably end up with aNullReferenceException
getting thrown in code that may not be anywhere near to the source of the problem.
So let’s rewrite the method to test our assumptions:
public ValidationResult validate(string username, string password)
{
if(username == null)
throw new ArgumentNullException("username");
if(password == null)
throw new ArgumentNullException("password");
if(!UserExists(username))
return new ValidationResult(ValidationStatus.UserNotFound);
if(!IsValid(username, password))
return new ValidationResult(ValidationStatus.InvalidPassword);
var user = GetUser(username);
if(user == null)
throw new InvalidOperationException("GetUser should not return null.");
return new ValidationResult(ValidationStatus.Valid, user);
}
Chances are, you will never see any of these exceptions get thrown. However, if they are, the stack trace will tell you exactly where the problem happened, and it’ll be way easier to debug.
Know How to Avoid Exceptions
While throwing exceptions is important when your program ends up in an unexpected state, it’s even better when you can write code that makes it impossible to do the wrong thing in the first place.
Consider the way we use the ValidationResult
class in the examples above. It would be wrong to provide a User
object with anything except a Valid
status. Doing this would indicate that there was a defect that didn’t get caught by the calling code. What if we re-wrote our class to make it impossible to provide a User
except when creating a Valid
result?
public abstract class ValidationResult
{
public ValidationStatus Status {get; private set;}
protected ValidationResult(ValidationStatus status) {
Status = status;
}
}
public class UserNotFoundResult : ValidationResult
{
public UserNotFoundResult () :
base(ValidationStatus.UserNotFound) {}
}
public class InvalidPasswordResult : ValidationResult
{
public InvalidPasswordResult () :
base(ValidationStatus.InvalidPassword) {}
}
public class ValidResult : ValidationResult
{
public User {get; private set;}
public ValidResult(User user) : base(ValidationStatus.Valid)
{
if(user == null) throw new ArgumentNullException("user");
User = user;
}
}
Now we’re forcing consumers to choose a specific constructor for each situation. We won’t allow them to provide a User
for non-valid results, and we will force them to provide a User
for valid results. Furthermore, once a ValidationResult
is returned, the calling code cannot access the User
property without first casting it as a ValidResult
, ensuring that they’ll never get a null
value from the User
property.
This is only one of many strategies we could have used. Just be sure that when you’re tempted to throw an exception, you consider whether the exception can be avoided entirely by leveraging compile-time constructs.
Don’t Eat Exceptions
Because Exceptions aren’t expected to happen as part of your program’s normal workflow, you often won’t know what exactly do to with an exception when it happens. Avoid this anti-pattern:
try
{
DoIntegration();
}
catch (Exception e)
{
}
With the code above, your integrations could be failing for weeks before you realize there’s a problem, leaving you with mountains of bad or lost data.
Here are some good ways to deal with exceptions:
1: Ignore them
That’s right. In the vast majority of cases, there’s no real reason to catch an exception in the first place. If you can’t do something useful with it, don’t even bother with a try/catch
.
2: Catch and Re-throw
Sometimes you’ll want to do something in response to a thrown exception, while still throwing the original exception.
try
{
DoSomethingImportant();
}
catch (Exception e)
{
EmailEmergencyResponseTeam(e);
throw;
}
Note that the above code says throw;
. This re-throws the exception that was caught, while preserving the stack trace it has accumulated up to this point. Do not use throw e;
, as this will throw the exception as if it were just created, expunging the existing stack trace.
2: Catch and Wrap
Exception messages can be super useful when it comes to tracking down bugs, if they provide all the relevant details about the system’s state when the error happened. It’s often a good idea to catch exceptions just so that you can wrap them with more useful information:
try
{
DoIntegration(integrationId, paramString);
}
catch (Exception e)
{
throw new Exception("Failed to run integration: " +
new{ integrationId, paramString }, e);
}
Here we include the original exception as an InnerException, so we don’t lose any information about what went wrong originally, but we also include information from this level of the call stack that may have not been available where the original exception was thrown. Anonymous objects in C# will produce a nice string with the names and values of all their properties, making them practically ideal for building exception messages.
Note: If your message-building code throws an exception, the original exception data will be lost. You may find it worthwhile to create helper methods that can wrap exceptions and build debug messages while gracefully handling any errors.
catch (Exception e)
{
throw e.Wrap(() => "Failed to run integration: " +
DebugStringHelper.GetDebugString(integrationParams));
}
You maybe tempted to use a Serializer to produce debug strings for complex objects, but that can be very dangerous. You could end up logging the entire contents of your database from a lazy-loading ORM entity, for example. I recommend using helpers that explicitly limit the amount of debug information they’ll produce.
3. Catch and Respond
This is the classic case for try/catch
: you know that something might go wrong with an operation, and you know the best way for your program to respond in that case. For example, if the connection to your database sometimes fails, you might want to reset your connection pools and try again before giving up:
try
{
return query.ToList();
}
catch (EntityCommandExecutionException e)
{
Log.Error("Failed to query the DB. Trying again.", e);
return query.ToList();
}
In cases like this, you usually want to catch the specific exception type that you know how to deal with, rather than catching Exception
s generally.
4. Catching in the Presentation Layer
Exceptions happen. Whether it’s due to a hardware problem or your own crappy software, at some point an exception will bubble to the top layer of your application’s stack. At that point, it must be caught. Users will occasionally put up with errors–they are far less forgiving if your software crashes. You must ensure that all exceptions are caught at the presentation layer, logged, and transformed into a user-friendly error message. Depending on the framework you’re using, you can often do this globally with a single handler.
At this level of your application, you won’t typically have enough information about the error to tell the user what went wrong. Most of the time, they don’t care about the details. The best you can do in this case is to tell them you’re sorry, recommend a catch-all solution that might solve their problem, and give them an easy way to report the issue, especially if it persists.
_ Rule of Thumb : Always throw errors the moment they occur, and always log exceptions that you catch and don’t rethrow.If you do this consistently, it won’t be necessary to log errors anywhere else in your code. You can simply trust that the exception will eventually be logged when it gets caught, and at that point, the exception will have a much more informative stack trace associated with it.
Conclusion
Learning to use exceptions correctly is vital to avoiding data loss and enabling developers to easily reproduce errors when they occur. Do you have any additional suggestions? Please share them in the comments section below.
I think I disagree. I don’t think you’ve adequately made the case against exceptions here. Please convince me.
You don’t actually say why here. Let me make the case for exceptions.
(Pseudo code)
Three lines. If I didn’t use exception handling, I would have to check the output of each of those functions, I wouldn’t get the rich information from the exception. Every time I updated one of those functions, I would have to update SaveUser. I would have to produce a result from SaveUser that could be checked from the controller, and I could only provide some generic response.
4. Exceptions are easier and more concise to test
*int.Parse
Thanks for taking the time to respond to my article with so much thought and detail. I don’t think we actually disagree nearly as much as you seem to think. But I’d be happy to respond to some of your comments.
I am not trying to make a case against exceptions. In fact, I go on to show code that throws even more exceptions! But you want to throw exceptions because assumptions are not being met, not as part of the expected logic flow.
There are cases where using middleware to triage exceptions is a good idea, and I actually explore one flavor of this in this blog post. What you need to watch out for is when the middleware becomes too tightly coupled to specific implementation details in your code, since those details could change over time and developers may not know they need to update the middleware accordingly.
You also need to be careful about assuming that a particular type of exception always equates to a particular response. For example, if a request was made to
/api/user-profile/j2jensen
and there’s no user by that id, then a 404 is a better response header. The client can deduce a 404 means the requested user doesn’t exist. Assuming it’s normal for user profiles to be deleted, this would be an ordinary occurrence which you’d expect to encounter in the course of running your application.Now let’s say you have another controller than handles replying to an article (
api/articles/234/reply
), and it calls into another class that sends out notifications to users who were@
-mentioned in the post, and that class calls into your UserRepository to get the information for the user that was mentioned. In this case, the correct behavior is to skip sending the notification, but continue to save the comment to the article. You shouldn’t throw an exception, and you shouldn’t return a 404. So you want to write UserRepository in a way that forces the calling code to decide what an appropriate response is to the user not being there. Throwing a UserException and using middleware to respond to that UserException would be a bad idea.Side note: 400 is almost never an appropriate response for anything but your web framework to return, as it implies something was wrong with the request syntax itself. A good general-purpose “something unexpected happened so I couldn’t do that” code is 500.
It depends on what you’re trying to do.
/api/user-profile/j2jensen
clearly cannot complete if that user doesn’t exist. Butapi/articles/234/reply
should totally be allowed to complete–it should just skip trying to send a notification to the nonexisting@
-mentioned user.I’m assuming with this example that you’re expecting the caller of
SaveUser
(or something further up the call stack) to know about all of the kinds of exceptions that could be thrown by the methods that SaveUser calls, and catch and handle those exceptions. If you want your code to work correctly, then you need to handle the different possible failures in some way. You still have to do the work of checking the output (exceptions) of those functions: you’re just doing it without changing the SaveUser contract.I see what you’re saying about being able to keep your controller code cleaner, but I think it’s coming at the expense of maintainability. Ideally you should be able to find an abstraction that allows you to capture what’s generally important about the various types of errors that ValidateUser and AuthenticateUser are aware of, without tightly coupling your top-level code to those implementation details. But either way, I’d say your program is probably more fragile if top-level code is tightly coupled to implementation details in the low-level code that “skip” the middle layer and aren’t represented in the method signatures along the way.
When used correctly, they can significantly reduce debugging effort as well.
You can get rich information from a return type. The question is what kind of detail you really want your high-level code to be coupled to. Information Hiding is a useful practice.
If you make a change to the contract that SaveUser establishes with its calling code, then it’s a good thing to have to change its method signature. If you change the types of exceptions that could be thrown by ValidateUser, then presumably you’d want to also change whatever code is supposed to handle those exceptions? What indication would you have that you’d need to change that handling code? None: just your personal knowledge of the code base. On the other hand, if that same change caused SaveUser to change its method signature then any code calling SaveUser would have to be updated in order to compile. This helps you to prevent bugs.
Interestingly, Java is set up so that methods have to either handle any exceptions that can be thrown inside of them or declare that they’ll throw them. So unless you use a specific subclass of exceptions that’s immune to that rule, throwing a new type of exception in ValidateUser would force you to change the method signature for SaveUser and update its calling code before your application would compile. But C# doesn’t provide any compile-time checking for exception handling like this.
Not so. In my experience, testing exceptions is slightly harder than testing returned values. Using custom attributes to test them is also less flexible than using assertions in your code, too. For example, you can’t specify the scope of code where you expect the exception to come from, or validate the values of properties on the Exception besides the Message. I like using TestCases and FluentAssertions to create flexible, readable tests like this:
If something is happening every day that shouldn’t be happening, that’s a sign of buggy code. I don’t want to be on a project where this is considered normal.
Many of the native .NET functions (
int.Parse
) also have counterparts (int.TryParse
) which don’t throw exceptions. You’re expected to choose which one to use based on whether your code should be able to assume the operation will be successful. What you’re not supposed to do is callint.Parse
and then catchInvalidOperationException
to handle cases where it couldn’t be parsed.Totally agree. Performance is not among the reasons I have for avoiding Exceptions. Purely thinking in terms of developer time, I recognize that defects tend to cost a lot of developer time, and that cost increases exponentially the longer the defects go undetected. Exceptions’ greatest strength lies in their ability to make defects noticeable and provide context from the point of failure to help you discover the cause of the defect.
Although your approach could be useful in some scenarios, your user validation implementation suffers security risks that weren’t there in the first place, introduces unnecessary complexities, and other conceptual problems.
About security risks:
By directly reporting if a user as been found or not, you expose your system. In facts your are giving the opportunity to know that a user actually exists. Once an attacker knows that a user actually exists, he is a step closer in violating that account.
Tipically i would never give such an insight. I would always return something indicating that either the username OR the password was wrong.
About the implementation:
Introducing a ValidationResult simply move the problem from checking for null user to checking for ValidationStatus.Valid on the calling method.
Throw exception or check a return state? The eternal dilemma. I avoid to return states. In my opinion, generally, enums are meant to provide options, not states on which I control the flow of my program. I never ask for a state i ask for something else and i don’t want a state in response. Unless I explicty call a method that is called something like GetControllerState or alike, i’m not asking for a state.
What i really want from a validation routine is the instance of the user. If, for any reason, a user cannot be provided i quit my procedure. An exception is ok.
This line is just an abomination:
if(user == null) throw new InvalidOperationException(“GetUser should not return null.”);
You don’t know why GetUser returned null, but rethrow an InvalidOperationException.
What if the database was simply offline? If you would have let GetUser to throw exception, you would have known what happended. Here you are probably a victim of your own approach applied even in GetUser method (even if you are not returning a ValidationState but allowing nulls, same thing: you return some state).
The caller of Validate does not know how the method is implemented and don’t want to. He is only interested in knowing if the user is authenticated or not. You are not asnwering. So you should do one of the following things:
2.1 Following your approach you should return ValidationResult.Invalid. Here you throw an exception so you break your own principles.
2.2 On a correct implementaion i’d probably let GetUser throw or Try catch the method and rethrow if ever it was necessary.
Conclusion:
To validate, my fellow readers, a reasonable method is the following:
Mauro, thanks for your feedback. Perhaps I was unclear about a number of points. Please allow me to explain.
I was not assuming that this method’s result is being serialized and sent directly back to a client. The fact that the original code was throwing two types of exceptions implied that the calling code was expected to try/catch those different exception types and respond to them in different ways. Perhaps the code calling this method would have logged some debug messages, or added a counter to the user’s failed login attempts before returning a generic error response to the client. By using a specific return type instead of exceptions, the calling code handles these different validation scenarios as part of its normal logic flow, rather than using try/catch blocks.
You seem to be assuming that returning a
null
User would have been a valid thing for the original code to do. There’s no valid reason for the original code to ever returnnull
, and the calling code most likely never would have checked fornull
. An invalid result in the original code would have thrown an exception, which the person calling the code might not have thought to check for. In my suggested code, we’re returning an object which makes it obvious that a failed login is something we expect to happen on a regular basis.When you’re trying to validate a user’s credentials, then there’s a high likelihood of failure due to user error. It’s not really acceptable to allow your program to crash in this case. Getting the User is not a necessary step in some larger set of tasks you’re trying to accomplish here: the whole purpose of this routine is to find out if the user’s credentials are correct, and respond accordingly.
In my opinion, the behavior you describe here would be appropriate for a method called
GetUser()
, which should be expected to return the User. A method calledValidate
should return the validation result. There’s certainly room for different people to take different approaches, but I will argue that your method names should indicate what you’re expecting to get back as a result.As you suggest, I am allowing
GetUser
to throw an exception if the database was offline, or if getting the user fails for any other reason. I never expectGetUser
to returnnull
. Returningnull
is, literally, an invalid operation for that method. The purpose of this code is not to handle a situation that we expect, but rather to fail immediately if the system is behaving in a way that our code was not expecting. Throwing an exception under exceptional (i.e. unexpected) circumstances is exactly the principle I’m trying to teach.On a related side-note, lately I’ve been using Fody NullGuard so I don’t have to write code like this to guard against unexpected null values. This plugin automatically inserts null checks into the compiled assembly for all of my input parameters and return values, so my actual code can happily assume that a method like
GetUser
cannot possibly return null.Hi James,
You are right, the appropriate name for the method, to me, should be AuthenticateUser.
You called the method Validate, but in facts you are eventually returning a user instance inside ValidationResult. So you are not really just validating.
Since validating a user per-se has no meaning, it’s clear you are trying to authenticate, isn’t it? And exactly because you are authenticating you should not respond with a state, but with a authenticated user instance, and only if everything was ok. This was the chain of thoughs i had.
You could just remove User from the ValidationResult and the example turns out clearer to me.
Obviously, trying to figure out why you structured the example that way, I had to make a few suppositions that may not reflect the scenario you had in mind while writing the example. I just wanted to point out that the example is open to be interpreted differently from what you would have wantend to represent.
I only suggest you to review the first part of the article with a more meaningful, precise and pertinent example.
I hope I have been constructive :)