Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for C# exception handling best practices
Thomas Ardal
Thomas Ardal

Posted on • Originally published atblog.elmah.io

     

C# exception handling best practices

I'm getting near my 20th anniversary in the tech industry. During the years, I have seen almost every anti-pattern when dealing with exceptions (and made the mistakes personally as well). This post contains a collection of my best practices when dealing with exceptions in C#.

Don't re-throw exceptions

I see this over an over again. People are confused that the original stack trace "magically" disappear in their error handling. This is most often caused by re-throwing exceptions rather than throwing the original exception. Let's look at an example where we have a nestedtry/catch:

try{try{// Call some other code thay may cause the SpecificException}catch(SpecificExceptionspecificException){log.LogError(specificException,"Specific error");}// Call some other code}catch(Exceptionexception){log.LogError(exception,"General erro");}
Enter fullscreen modeExit fullscreen mode

As you probably already figured out, the innertry/catch catches, logs, and swallow the exception. To throw theSpecificException for the globalcatch block to handle it, you need to throw it up the stack. You can either do this:

catch(SpecificExceptionspecificException){// ...throwspecificException;}
Enter fullscreen modeExit fullscreen mode

Or this:

catch(SpecificExceptionspecificException){// ...throw;}
Enter fullscreen modeExit fullscreen mode

The main difference here is that the first example re-throw theSpecificException which causes the stack trace of the original exception to reset while the second example simply retain all of the details of the orignal exception. You almost always want to use the second example.

Decorate exceptions

I see this used way to rarely. All exceptions extendException, which has aData dictionary. The dictionary can be used to include additional information about an error. Whether or not this information is visible in your log depends on what logging framework and storage you are using. For elmah.io,Data entries are visible in the Data tab within elmah.io.

To include information in theData dictionary, add key/value pairs:

varexception=newException("En error happened");exception.Data.Add("user",Thread.CurrentPrincipal.Identity.Name);throwexception;
Enter fullscreen modeExit fullscreen mode

In the example, I add a key nameduser with a potential username stored on the thread.

You can decorate exceptions generated by external code too. Add atry/catch:

try{service.SomeCall();}catch(Exceptione){e.Data.Add("user",Thread.CurrentPrincipal.Identity.Name);throw;}
Enter fullscreen modeExit fullscreen mode

The code catches any exceptions thrown by theSomeCall method and includes a username on the exception. By adding thethrow keyword to thecatch block, the original exception is thrown further up the stack.

Catch the more specific exceptions first

You know you have code similar to this:

try{File.WriteAllText(path,contents);}catch(Exceptione){logger.Error(e);}
Enter fullscreen modeExit fullscreen mode

Simply catchingException and logging it to your preferred logging framework is quick to implement and get the job done. Most libraries available in .NET can throw a range of different exceptions, and you might even have a similar pattern in your code-base. Catching multiple exceptions ranging from the most to the least specific error is a great way to differentiate how you want to continue on each type.

In the following example, I'm explicit about which exceptions to expect and how to deal with each exception type:

try{File.WriteAllText(path,contents);}catch(ArgumentExceptionae){Message.Show("Invalid path");}catch(DirectoryNotFoundExceptiondnfe){Message.Show("Directory not found");}catch(Exceptione){varsupportId=Guid.NewGuid();e.Data.Add("Support id",supportId);logger.Error(e);Message.Show($"Please contact support with id:{supportId}");}
Enter fullscreen modeExit fullscreen mode

By catchingArgumentException andDirectoryNotFoundException before catching the genericException, I can show a specialized message to the user. In these scenarios, I don't log the exception since the user can quickly fix the errors. In the case of anException, I generate a support id, log the error (using decorators as shown in the previous section) and show a message to the user.

Please notice that while the code above serves the purpose of explaining exception order, it is a bad practice to implement control flow using exception like this. Which is a perfect introduction to the next best practice:

Avoid exceptions

It may sound obvious to avoid exceptions. But many methods that throw an exception can be avoided by defensive programming.

One of the most common exceptions isNullReferenceException. In some cases, you may want to allow null but forget to check for null. Here is an example that throws aNullReferenceException:

Addressa=null;varcity=a.City;
Enter fullscreen modeExit fullscreen mode

Accessinga throws an exception but play along and imagine thata is provided as a parameter.

In case you want to allow a city with anull value, you can avoid the exception by using the null-conditional operator:

Addressa=null;varcity=a?.City;
Enter fullscreen modeExit fullscreen mode

By appending? when accessinga, C# automatically handles the scenario where the address isnull. In this case, thecity variable will get the valuenull.

Another common example of exceptions is when parsing numbers or booleans. The following example will throw aFormatException:

vari=int.Parse("invalid");
Enter fullscreen modeExit fullscreen mode

Theinvalid string cannot be parsed as an integer. Rather than including atry/catch,int provides a fancy method that you probably already used 1,000 times:

if(int.TryParse("invalid",outinti)){}
Enter fullscreen modeExit fullscreen mode

In caseinvalid can be parsed as anint, theTryParse returnstrue and put the parsed value in thei variable. Another exception avoided.

Create custom exceptions

It's funny to think back on my years as a Java programmer (back when .NET was in beta). We created custom exceptions for everything. Maybe it was because of the more explicit exception implementation in Java, but it's a pattern that I don't see repeated that often in .NET and C#. By creating a custom exception, you have much better possibilities of catching specific exceptions, as already shown. You can decorate your exception with custom variables without having to worry if your logger supports theData dictionary:

publicclassMyVerySpecializedException:Exception{publicMyVerySpecializedException():base(){}publicMyVerySpecializedException(stringmessage):base(message){}publicMyVerySpecializedException(stringmessage,Exceptioninner):base(message,inner){}publicintStatus{get;set;}}
Enter fullscreen modeExit fullscreen mode

TheMyVerySpecializedException class (maybe not a class name that you should re-use :D) implements three constructors that every exception class should have. Also, I have added aStatus property as an example of additional data. This will make it possible to write code like this:

try{service.SomeCall();}catch(MyVerySpecializedExceptione)when(e.Status==500){// Do something specific for Status 500}catch(MyVerySpecializedExceptionex){// Do something general}
Enter fullscreen modeExit fullscreen mode

Using thewhen keyword, I can catch aMyVerySpecializedException when the value of theStatus property is500. All other scenarios will end up in the general catch ofMyVerySpecializedException.

Log exceptions

This seem so obvious. But I have seen too much code failing in the subsequent lines when using this pattern:

try{service.SomeCall();}catch{// Ignored}
Enter fullscreen modeExit fullscreen mode

Logging both uncaught and catched exceptions is the least you can do for your users. Nothing is worse than users contacting your support, and you had no idea that errors had been introduced and what happened. Logging will help you with that.

There are several great logging frameworks out there like NLog and Serilog. If you are an ASP.NET (Core) web developer, logging uncaught exceptions can be done automatically using elmah.io or one of the other tools available out there.

Would your users appreciate fewer errors?

elmah.io is the easy error logging and uptime monitoring service for .NET. Take back control of your errors with support for all .NET web and logging frameworks.

➡️Error Monitoring for .NET Web Applications ⬅️

This article first appeared on the elmah.io blog athttps://blog.elmah.io/csharp-exception-handling-best-practices/

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
kj2whe profile image
Jason
  • Joined

This YouTube had an interesting approach to Exceptions

youtu.be/4UEanbBaJy4

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Entrepreneur and builder of online things. Bootstrapping elmah.io. Dad to 👦👧 Powered by ☕&🍜 Likes Star Wars, stonk trading, 3D printing, and retro games.
  • Location
    Aarhus, Denmark
  • Work
    Founder at elmah.io
  • Joined

More fromThomas Ardal

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp