Getting Started With Exception Handling in C#

Description:

This article explores exception handling mechanisms in C#. It describes how C# provides built-in support for handling anomalous situations, known as exceptions, that may occur during the execution of your program. We will start out by understanding exceptions and will see why we need to catch various exceptions.

What is an Exception?

An exception is an error condition or unexpected behavior encountered by an executing program during runtime. In fact the name exception comes from the fact that although a problem can occur, the problem occurs infrequently.

Trapping and handling of runtime errors is one of the most crucial tasks ahead for any programmer. As a developer, you sometimes seem to spend more time checking for errors and handling them than you do on the core logic of the actual program. You can address this issue using system exceptions that are designed to handle errors. In this article, you will learn how to catch and handle exceptions in C#.

Let's see an example of a user entering input that does not represent a valid number. During the int.Parse method call, the code recognizes this exceptional event and throws a FormatException.

// Example ShowingExceptions
using System;
class ShowingExceptions
{
static void Main()
{
Console.Write("Please enter a number");
string str = Console.ReadLine();
int iNum = int.Parse(str);
Console.WriteLine("You entered {0}. Converted to {1}", str, iNum);
Console.ReadLine();
}
}

Execution :
Please enter a number : Hello

Output:

An unhandled exception of type 'System.FormatException' occurred in mscorlib.dll
Additional information: Input string was not in a correct format.

Now there are various ways of dealing with such problems. The first way is to "ignore it". Do not be surprised because that is what most of us tend to do. Though this solution is easy for the programmer, in the bargain, the program looses robustness and is more prone to errors and crashes during execution. The second solution could be using "Exception Avoidance". In this technique, we try to avoid errors before hand using "Condition Checking" wherever possible. One way of "Condition Checking" could be to read each character in the string, ensuring if it is a digit and then parsing it. "Exception avoidance" is important at times and should be used whenever possible, but not always, since it could be more trouble than it is worth. Can you imagine how tedious it would be to check every letter of every line read in from the keyboard, just to avoid a FormatException and a program crash?

N
ot only is exception avoidance inconvenient at times, but also impossible to use at others. Let's think of a situation where you want to open a file using a StreamReader. If the file you specify does not exist, a FileNotFoundException can occur. But using the Exception Avoidance technique we have explained so far, you cannot accurately avoid this exception ahead of time and determine that the file exists before trying to read it.

What is Exception Handling and why do we need it?

The mark of a good, robust program is planning for the unexpected, and recovering if it does happen. Errors can happen nearly any time during the compilation or execution of a program. We can detect and deal with these errors using Exception Handling. Exception handling is a built-in mechanism in the .NET framework to detect and handle run time errors. At the heart of the .NET Framework is the Common Language Runtime (CLR) that, in addition to acting as a virtual machine and interpreting and executing IL code on the fly, performs numerous other functions, such as type safety checking, memory management, garbage collection and exception handling. The .NET framework contains many standard exceptions. It allows you to define how the system reacts in unexpected situations like "File not found", "Out of Memory", bad logic, non-availability of operating system resources (other than memory), "Index out of bounds" and so on.

When the code that has a problem, is executed, it "raises an exception". If a programmer does not provide a mechanism to handle these anomalies, the .NET run time environment provides a default mechanism, that terminates the program execution. This mechanism is a structured exception handling mechanism.

Exceptions in C#

Exceptions in C# are represented by objects with type names representative of the type of error that occurred in the program. Exceptions in C# provide a structured, uniform, and type-safe way of handling both system-level and application-level error conditions. In C#, all exceptions must be represented by an instance of a class type derived from System.Exception. The System.Exception class is the base type of all exceptions. An object of an exception is that which describes the exceptional conditions occuring in code, in other words, we are catching an exception, creating an object of it, and then throwing it.

Like other modern and structured programming languages, C# also provides a rich mechanism of Exception Handling. Exceptions are handled in C# using the "try/catch/finally" statements. These keywords are also named as Structured Exception Handling (SEH). The code that "may cause an exception" is enclosed within a "try" block as shown below:

try
{
// this code may cause an exception.
}

Your code feels safe now since it has been protected. If an exception does occur in the code enclosed within the "try" block then you can handle it. To handle an exception, attach a "catch" block to the "try".

try
{
// this code may cause an exception.
// If it does cause an exception, the execution will not continue.
// Instead,it will jump to the 'catch' block.
}
catch (Exception ex)
{
// I get executed when an exception occurs in the try block.
// Write the error handling code here.
}

Having understood the basic syntax, let's rewrite the example ShowingExceptions:

// Example ShowingExceptions using structured exception handling the C# way.

class ShowingExceptions
{
static void main()
{
Console.Write("Please enter a number :");
string str = Console.ReadLine();
double dNo;
}
try
{
dNo =
double.Parse(str);
Console.WriteLine("Parsing successfully without errors");
}
catch(Exception ex)
{
Console.WriteLine("Error : {0} is not a valid number", str);
}

}

Execution :
Please enter a number : Hello

Output:

Error: Hello is not a valid number.

Thus here we notice that when the exception does occur, the program stops executing code from the try block and immediately jumps to the catch block. This is why the output, "Parsing successfully without errors" is not displayed when the exception occurs.

NOTE:

It is not a good idea to display the raw exception message to the user. Instead, show a friendly message to the user and log the actual exception message to some log file for troubleshooting purposes.

Moreover, in the preceding example, we are handling all types of exceptions. But it is a bad practise to catch all exceptions. You should catch only specific exceptions that are expected in the specific code block. For example, if you are attempting to allocate the memory then you may catch "System.OutOfMemoryException" and if you are performing mathematical operations then you may catch
the "System.DivideByZeroException" exception. Also, you can create and throw custom exceptions.

For example:

ArgEx ArgumentException = new ArgumentException("You entered bad data");
throw ArgEx;

Ok, But what about the finally block?

C# provides the finally block to enclose a set of statements that must be executed regardless of the course of control flow. So as a result of normal execution, if the control flow reaches the end of the try block then the statements of the finally block are executed. Moreover, if the control leaves a try block as a result of a throw statement or a jump statement, such as a break, continue, or goto then the statements of the finally block are executed. The finally block is basically useful in three situations: to avoid duplication of statements, to release resources after an exception has been thrown and to assign a value to an object or display a message that may be useful to the program.

Let's see the example ShowingExceptions again:

// Example ShowingExceptions using finally
class ShowingExceptions
{
static void main()
{
Console.Write("Please enter a number :");
string str = Console.ReadLine();
double dNo;
}
try
{
dNo =
double.Parse(str);
Console.WriteLine("Parsing successfully without errors");
}
finally
{
Console.WriteLine("Storing the number
as 1.0");
dNo = 1.0;
// dNo is assigned the value 1.0 irrespective of an error.
}
}

What are the other Exception Classes?

Here's a list of a few common Exception classes:

  • System.ArithmeticException: A base class for exceptions that occur during arithmetic operations, such as System.DivideByZeroException.

  • System.ArrayTypeMismatchException: Thrown when a store into an array fails because the actual type of the stored element is incompatible with the actual type of the array.

  • System.DivideByZeroException: Thrown when an attempt to divide an integral value by zero occurs.

  • System.IndexOutOfRangeException: Thrown when an attempt to index an array via an index that is less than zero or outside the bounds of the array.

  • System.InvalidCastExceptionThrown: Thrown when an explicit conversion from a base type or interface to derived types fails at run time.

  • System.MulticastNotSupportedException: Thrown when an attempt to combine two non-null delegates fails, because the delegate type does not have a void return type.

  • System.NullReferenceException: Thrown when a null reference is used in a way that causes the referenced object to be required.

  • System.OutOfMemoryException: Thrown when an attempt to allocate memory (via new) fails.

  • System.OverflowException: Thrown when an arithmetic operation in a checked context overflows.

Summary

In this article we saw how Exceptions are handled in C# using "try/catch/finally" statements. In the next article, I will describe the advanced situations where we can use exception handling, as well as best practices for Exceptions.

Up Next
    Ebook Download
    View all
    Learn
    View all