How to correctly throw Exceptions in C#

I’ve seen it many times that throwing Exceptions is done in the wrong way. That’s why I write a short blog post about Exception Handling in C# and how to do it in the right way:

There are mainly three ways of throwing an exception:

try { ... } catch (Exception ex) { throw ex; } // bad
try { ... } catch (Exception ex) { throw; } // good
try { ... } catch (Exception ex) { throw MyCustomException("message", ex); } // better

The main difference between the three ways above is, what you’ll get out of the stack trace. Let’s create a simple C# console application to see the differences:

using System;
 
namespace CodeHollow.Samples.ExceptionHandling
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Bad implementation:");
            try { BadImplementation(); }
            catch (Exception ex) { Console.WriteLine(ex.ToString()); }
 
            Console.WriteLine();
            Console.WriteLine("Good implementation:");
            try { GoodImplementation(); }
            catch (Exception ex) { Console.WriteLine(ex.ToString()); }
 
            Console.WriteLine();
            Console.WriteLine("Better implementation:");
            try { BetterImplementation(); }
            catch (Exception ex) { Console.WriteLine(ex.ToString()); }
        }
 
        private static void BadImplementation()
        {
            try { ExceptionMethod(); }
            catch (Exception ex)
            {
                throw ex;
            }
        }
 
        private static void GoodImplementation()
        {
            try { ExceptionMethod(); }
            catch (Exception)
            {
                throw;
            }
        }
 
        private static void BetterImplementation()
        {
            try { ExceptionMethod(); }
            catch (Exception ex)
            {
                throw new MyCustomException("my exception", ex);
            }
        }
 
        private static void ExceptionMethod()
        {
            System.IO.File.ReadAllLines("nonexistingfile");
            //System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection("wrong connection string");
        }
    }
}

Bad implementation

This is the bad example of throwing an exception:

try { ... } catch (Exception ex) { throw ex; } // bad

Let’s have a look at the output of our console application:

System.IO.FileNotFoundException: Could not find file ‘C:\Data\CodeHollow\CodeHollow.Samples.ExceptionHandling\CodeHollow.Samples.ExceptionHandling\bin\Release\nonexistingfile’.
File name: ‘C:\Data\CodeHollow\CodeHollow.Samples.ExceptionHandling\CodeHollow.Samples.ExceptionHandling\bin\Release\nonexistingfile’
at CodeHollow.Samples.ExceptionHandling.Program.BadImplementation()
at CodeHollow.Samples.ExceptionHandling.Program.Main(String[] args)

The stack trace only contains the parts beginning from BadImplementation() method. Let’s assume we use our own logic of reading text from a file. If this error occurs, then we only know, that the method BadImplementation throws an exception, but what’s happening inside of the File.ReadAllLines()? This is not part of the output. If our method has much more than this one line, then it will take much longer to find the real issue. So let’s see how this changes if we use a good implementation.

Good implementation

try { ... } catch (Exception ex) { throw; } // good

The output already contains much more information:

System.IO.FileNotFoundException: Could not find file ‘C:\Data\CodeHollow\CodeHollow.Samples.ExceptionHandling\CodeHollow.Samples.ExceptionHandling\bin\Release\nonexistingfile’.
File name: ‘C:\Data\CodeHollow\CodeHollow.Samples.ExceptionHandling\CodeHollow.Samples.ExceptionHandling\bin\Release\nonexistingfile’
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize, Boolean checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding)
at System.IO.File.InternalReadAllLines(String path, Encoding encoding)
at System.IO.File.ReadAllLines(String path)
at CodeHollow.Samples.ExceptionHandling.Program.GoodImplementation()
at CodeHollow.Samples.ExceptionHandling.Program.Main(String[] args)

You can see, that it contains the whole stack trace. You can see the ReadAllLines method and also what happened inside of it. This can only be seen if the correct way of throwing exceptions is used. Imagine that you have your own big application with a lot of calls. If an error occurs at the customers system, then this way will help you a lot to find the reason for the error and where it happens in the code.

Better implementation

The following way is the best one. Using custom exceptions with your own useful error messages will make it again easier to find the issue, but it’s important to set the InnerException:

try { ... } catch (Exception ex) { throw MyCustomException("message", ex); } // better

If you just use throw MyCustomException(“message”), then you have the same issue as mention in the first (bad) implementation.

Look at the following output and you’ll see that it contains your message plus the whole stack trace:

CodeHollow.Samples.ExceptionHandling.MyCustomException: my exception —> System.IO.FileNotFoundException: Could not find file ‘C:\Data\CodeHollow\CodeHollow.Samples.ExceptionHandling\CodeHollow.Samples.ExceptionHandling\bin\Release\nonexistingfile’.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize, Boolean checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding)
at System.IO.File.InternalReadAllLines(String path, Encoding encoding)
at System.IO.File.ReadAllLines(String path)
at CodeHollow.Samples.ExceptionHandling.Program.BetterImplementation()
— End of inner exception stack trace —
at CodeHollow.Samples.ExceptionHandling.Program.BetterImplementation()
at CodeHollow.Samples.ExceptionHandling.Program.Main(String[] args)

Custom Exceptions

The typical developer usually throws the generic Exception (System.Exception). Throwing custom exceptions is nicer and you can add additional information to it (objects and so on). Btw.: Objects should not be part of the message, it should be part of the exception (property in my custom exception).
One of the reasons why we usually don’t use custom exceptions is by sure, that it takes a bit longer to create a custom exception, but here are two ways how to do it really fast:

1.) Type “throw new MyCustomException();”, press Ctrl+. (dot) and return. This will create a new serializable internal class which derives from Exception. This class already contains the 4 basic constructors defined.

2.) By using the snippet: Create a new class, remove the class definition and type “Exc” (intellisense shows “Exception”), press Tab, Tab and enter the name of your exception followed by a return.

The outcome will look like:

[Serializable]
public class MyException : Exception
{
    public MyException() { }
    public MyException(string message) : base(message) { }
    public MyException(string message, Exception inner) : base(message, inner) { }
    protected MyException(
      SerializationInfo info,
      StreamingContext context) : base(info, context) { }
}

Categories:

No responses yet

Leave a Reply

Your email address will not be published. Required fields are marked *

About
about armin

Armin Reiter
Blockchain/Web3, IT-Security & Azure
Vienna, Austria

Reiter ITS Logo

Cryptix Logo

Legal information