Error and Exception Handling in C#A look at Error and Exception Handling in C# using try...catch blocks, custom exception classes and some debugging using breakpoints.
This article is part of a series of articles. Please use the links below to navigate between the articles.
- Learn to Program in C# - Full Introduction to Programming Course
- Introdution to Programming - C# Programming Fundamentals
- Guide to C# Data Types, Variables and Object Casting
- C# Operators: Arithmetic, Comparison, Logical and more
- Application Flow Control and Control Structures in C#
- Introduction to Object Oriented Programming for Beginners
- Introduction to C# Object-Oriented Programming Part 2
- C# Collection Types (Array,List,Dictionary,HashTable and More)
- Error and Exception Handling in C#
- Events, Delegates and Extension Methods
- Complete Guide to File Handling in C# - Reading and Writing Files
- Introduction to XML and XmlDocument with C#
- What is LINQ? The .NET Language Integrated Query
- Introduction to Asynchronous Programming in C#
- Working with Databases using Entity Framework
- All About Reflection in C# To Read Metadata and Find Assemblies
- Debugging and Testing in C#
- Introduction to ASP.Net MVC Web Applications and C#
- Windows Application Development Using .Net and Windows Forms
- Assemblies and the Global Assembly Cache in C#
- Working with Resources Files, Culture & Regions in .Net
- The Ultimate Guide to Regular Expressions: Everything You Need to Know

Critical errors are called Exceptions and are raised whenever the compiler encounters a problem with a code segment. Examples of common exceptions are dividing by zero and reading a null value.
Exceptions can be managed using a try...catch...finally
block of code. These will catch any errors and allow your code to handle the error and deal with them without the user's knowledge. Exception handling prevents errors from crashing applications and causing data loss.
Traditionally, methods and functions would return the error code in the function result in C. A value of false or -1 usually indicates an error with the method. This caused a few problems, most notably returning a value from functions, and programmers usually did not test the result code. Also, -1 or false could result from the function, which is not necessarily an error, so it was a little confusing.
In the .Net platform, we have exceptions, which are System objects representing a specific or generalised error condition.
Exception Handling by Catching an Exception
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int x = 0;
int y = 0;
y = 100 / x;
}
}
}
This line of code will cause an exception, as you cannot divide any number by 0. If left like this, the program will crash and stop working, requiring the user to reload the application. The solution is to implement a try...catch...finally block.
Try, catch, and finally, three essential code blocks for problem-free programs. The logic of the block is that we try to do x, catch an error if one occurs, and finally do y.
If an exception is raised in the try block, then the code in the catch block is run. The catch block could be called logging or reporting methods, corrective actions, or alternative code. The final block's code is always executed regardless of whether it is an error. This block should close files or connections, free memory, etc...
Using the divide by zero error above, with a try...catch block implemented, it now looks like this.
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int x = 0;
int y = 0;
try
{
y = 100 / x;
}
catch
{
Console.WriteLine("There was an error but we caught it!");
Console.WriteLine("Please enter a new number:");
y = int.Parse(Console.ReadLine());
}
}
}
}
Should an error occur within the try block, in this example, y = 100 / x, the code within the catch block will be executed, in which you should attempt to fix the problem, notify or log the error and gracefully exit the program if required.
Let's look at a different example and see what code gets executed and skipped over. We will also see the final block in action. In the first example, there is no error handling.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string filename = "c:\azuliadesigns\tutorials\data.csv";
StreamReader myFile = new StreamReader(filename);
string orders = myFile.ReadToEnd();
myFile.Close();
Console.WriteLine(orders);
}
}
}
When executed, the file path does not exist, so an exception is raised when we try to open the file.

As soon as the error occurs, the program cannot continue to run; the user is presented with a horrible error message, and the program is forced to close. Not a very good impression.
A better solution is to use a try...catch block to handle the error.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string filename = "c:\azuliadesigns\tutorials\data.csv";
string orders = string.Empty;
StreamReader myFile = null;
try
{
myFile = new StreamReader(filename);
orders = myFile.ReadToEnd();
myFile.Close();
}
catch
{
Console.WriteLine("Sorry, an error has occurred.");
}
Console.WriteLine(orders);
}
}
}
Notice how the variable declarations have been taken outside the try block. We need the variables in the method's scope, not just the code block.
When the code is run, the screen shows a friendly but unhelpful error message. Luckily, there is a solution to this problem! The catch block can take in a parameter which will hold details about the error, so in our example, a DirectoryNotFoundException was raised.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string filename = "c:\azuliadesigns\tutorials\data.csv";
string orders = string.Empty;
StreamReader myFile = null;
try
{
myFile = new StreamReader(filename);
orders = myFile.ReadToEnd();
myFile.Close();
}
catch(DirectoryNotFoundException ex)
{
Console.WriteLine("Sorry, the path to '" + filename + "' does not exist. Please correct the error and try again.");
}
Console.WriteLine(orders);
}
}
}
When the program is run, the user has a detailed message telling them what the error is and how to fix it. Let's fix the error by creating the directory and re-run the program.

Another unhandled exception! The orders.csv file does not exist this time, and we have a FileNotFoundException. We can implement multiple catch blocks, one for each type of exception we want to capture.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string filename = "c:\azuliadesigns\tutorials\data.csv";
string orders = string.Empty;
StreamReader myFile = null;
try
{
myFile = new StreamReader(filename);
orders = myFile.ReadToEnd();
myFile.Close();
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine("Sorry, the path to '" + filename + "' does not exist. Please correct the error and try again.");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Sorry, the file '" + filename + "' does not exist. Please create the file and try again.");
}
Console.WriteLine(orders);
}
}
}
This time, the user will get another message telling them the cause of the problem and the solution. Now, we have a valid file and path. Let's do something with the data read from the file.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string filename = "c:\azuliadesigns\tutorials\data.csv";
string orders = string.Empty;
string data = string.Empty;
StreamReader myFile = null;
try
{
myFile = new StreamReader(filename);
orders = myFile.ReadToEnd();
data = orders.Substring(0, 1);
myFile.Close();
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine("Sorry, the path to '" + filename + "' does not exist. Please correct the error and try again.");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Sorry, the file '" + filename + "' does not exist. Please create the file and try again.");
}
Console.WriteLine(data);
}
}
}
Again, we have another error! Because the file was empty and we tried to do a substring on an empty string, we got an ArgumentOutOfRangeException, and the program was forced to close. It gets worse, though, since we opened a file, and the program closed before we closed it! This can lead to all kinds of trouble, even more so if it is a database we connect to. The solution is the finally block. The code in the final block is always guaranteed to run, so we can use that to close any files or connections.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string filename = "c:\azuliadesigns\tutorials\data.csv";
string orders = string.Empty;
string data = string.Empty;
StreamReader myFile = null;
try
{
myFile = new StreamReader(filename);
orders = myFile.ReadToEnd();
data = orders.Substring(0, 1);
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine("Sorry, the path to '" + filename + "' does not exist. Please correct the error and try again.");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Sorry, the file '" + filename + "' does not exist. Please create the file and try again.");
}
finally
{
myFile.Close();
}
Console.WriteLine(orders);
}
}
}
There we have a complete functioning try...catch...finally block. Hopefully, you can see how and why this is an essential part of trouble-free programming and how it can be used to avoid unhelpful and meaningless error messages.
Raising or Throwing an Exception
Exceptions can be manually thrown in your code, either for testing or to signal a fault that needs to be dealt with. It is important to validate inputs and notify errors, especially when dealing with untrusted code like that developed by another programmer. In this example, the scenario is that we are developing a class to handle mathematical operations. Another developer will be using this class in their program. For simplicity, I have included both classes in the same project.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
// Writen by us
static class MyMathClass
{
public static decimal Divide(int x, int y)
{
return x / y;
}
}
// Writen by somebody else
class Program
{
static void Main(string[] args)
{
Console.WriteLine(MyMathClass.Divide(10, 0));
}
}
}
Here, we can see that our class and method are fine. It will divide the first parameter by the second. What can go wrong? Well, the other programmer is an "untrusted" source. They can pass in a valid number that causes our code to crash. In this example, there is a divide-by-zero error. Since our code is the one that crashed, the fault and blame is ours.

What we can do to avoid this is validate the inputs and raise an exception for their code to handle. If they pass in invalid data to our method, their code is at fault, leaving us to blame. Raising an exception is done using the throw keyword. For a list of available exceptions you can throw, please click on Debug menu -> Exceptions... or press Ctrl+D, E. You can also create your exceptions in the next section.
You can throw a new exception, the default message, or specify a more specific error message in the constructor, as shown below.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
// Writen by us
static class MyMathClass
{
public static decimal Divide(int x, int y)
{
if (y == 0)
throw new ArgumentOutOfRangeException("Parameter y cannot be 0!");
return x / y;
}
}
// Writen by somebody else
class Program
{
static void Main(string[] args)
{
Console.WriteLine(MyMathClass.Divide(10, 0));
}
}
}
When the program is run, the calling method will receive an ArgumentOutOfRangeException message and our custom error message.
Exception Handling with Custom Exceptions
Suppose there is no predefined exception that suits your needs. In that case, you can easily create a new custom exception by creating a class that inherits from predefined exception classes or the base Exception class. They can be used in the same way as a normal exception.
class myException : System.Exception
{
}
class Program
{
static void Main()
{
throw new myException("Custom Exception");
}
}
Exception Handling Guidelines
When throwing exceptions, you should avoid exceptions for normal cases. These should be handled through proper program logic.
Never create and throw objects of class Exception; throwing exceptions of the most specific class is much better, giving you greater flexibility in catching and handling the exception. Should an unhandled exception occur, the more specific exception class will give you a better idea of where to start debugging. You should also include a description string in an Exception object with details about the error.
When catching exceptions, arrange catch blocks from specific to general; otherwise, the general exception will always be caught, and the specific catch blocks will never be processed. Do not let exceptions go unhandled in the Main method, as this will cause the application to crash, and the user will not be able to recover from the error.
Never use an empty catch block - what is the point?
try
{
var x = new StreamReader(filename);
}
catch
{
}
Console.WriteLine(x.ReadToEnd());