- Built-in Exception Classes in C#
- Exception Handling in C#
- throw keyword in C#
- Custom Exception Type in C#
Built-in Exception Classes in C#
Here you will learn about the built-in exception classes in C#.
C# .NET includes built-in exception classes for every possible error. The Exception class is the base class of all the exception classes.
The following is a hierarchy of exception classes in .NET:
In the above figure, the Exception class is the base class of the SystemException
and ApplicationException classes. The SystemException class is the base class for
all the built-in exception classes in .NET Framework.
The ApplicationException was recommended to be base class for all your custom exceptions classes
(The custom exeception class should be created if non of the system exception classes can be used and you
need new exception class for business rule violations or for other application related errors). It was meant
to differentiates between exceptions defined by applications versus exceptions defined by the system.
However, Microsoft now recommends to derive custom exception classes from the Exception class
rather than the ApplicationException class .
The following figure shows how the NullReferenceException is thrown in Visual Studio debug mode
when you access a null object property at runtime.
Built-in Exception Classes
The following table lists important built-in exception classes in .NET.
| Exception Class | Description |
|---|---|
| ArgumentException | Raised when a non-null argument that is passed to a method is invalid. |
| ArgumentNullException | Raised when null argument is passed to a method. |
| ArgumentOutOfRangeException | Raised when the value of an argument is outside the range of valid values. |
| DivideByZeroException | Raised when an integer value is divide by zero. |
| FileNotFoundException | Raised when a physical file does not exist at the specified location. |
| FormatException | Raised when a value is not in an appropriate format to be converted from a string by a conversion method such as Parse. |
| IndexOutOfRangeException | Raised when an array index is outside the lower or upper bounds of an array or collection. |
| InvalidOperationException | Raised when a method call is invalid in an object's current state. |
| KeyNotFoundException | Raised when the specified key for accessing a member in a collection is not exists. |
| NotSupportedException | Raised when a method or operation is not supported. |
| NullReferenceException | Raised when program access members of null object. |
| OverflowException | Raised when an arithmetic, casting, or conversion operation results in an overflow. |
| OutOfMemoryException | Raised when a program does not get enough memory to execute the code. |
| StackOverflowException | Raised when a stack in memory overflows. |
| TimeoutException | The time interval allotted to an operation has expired. |
When an error occurs, either the application code or the default handler handles the exception.
Exception Handling in C#
Here, you will learn about exception handling in C# using try, catch, and finally blocks.
Exceptions in the application must be handled to prevent crashing of the program and unexpected result, log
exceptions and continue with other functionalities. C# provides built-in support to handle the exception
using try, catch & finally blocks.
trycatchfinally
try block: Any suspected code that may raise exceptions should be put inside a try block.
During the execution, if an exception occurs, the flow of the control jumps to the first matching
catch block.
catch block: The catch block is an exception handler block where you can perform some
action such as logging and auditing an exception. The catch block takes a parameter of an
exception type using which you can get the details of an exception.
finally block: The finally block will always be executed whether an exception raised or
not. Usually, a finally block should be used to release resources, e.g., to close any stream or
file objects that were opened in the try block.
The following may throw an exception if you enter a non-numeric character.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Enter a number: ");
var num = int.Parse(Console.ReadLine());
Console.WriteLine($"Squre of {num} is {num * num}");
}
}
To handle the possible exceptions in the above example, wrap the code inside a try block and
handle the exception in the catch block, as shown below.
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("Enter a number: ");
var num = int.parse(Console.ReadLine());
Console.WriteLine($"Squre of {num} is {num * num}");
}
catch
{
Console.Write("Error occurred.");
}
finally
{
Console.Write("Re-try with a different number.");
}
}
}
In the above example, we wrapped this code inside a try block. If an exception occurs inside a
try block, then the program will jump to the catch block. Inside a
catch block, we display a message to instruct the user about his mistake, and in the
finally block, we display a message about what to do after running a program.
try block must be followed by catch or
finally or both blocks. The try block without a catch or
finally block will give a compile-time error.
Ideally, a catch block should include a parameter of a built-in or custom exception class to get
an error detail. The following includes the Exception type parameter that catches all types of
exceptions.
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("Enter a number: ");
var num = int.parse(Console.ReadLine());
Console.WriteLine($"Squre of {num} is {num * num}");
}
catch(Exception ex)
{
Console.Write("Error info:" + ex.Message);
}
finally
{
Console.Write("Re-try with a different number.");
}
}
}
Exception Filters
You can use multiple catch blocks with the different exception type parameters. This is called
exception filters. Exception filters are useful when you want to handle different types of exceptions in
different ways.
class Program
{
static void Main(string[] args)
{
Console.Write("Please enter a number to divide 100: ");
try
{
int num = int.Parse(Console.ReadLine());
int result = 100 / num;
Console.WriteLine("100 / {0} = {1}", num, result);
}
catch(DivideByZeroException ex)
{
Console.Write("Cannot divide by zero. Please try again.");
}
catch(InvalidOperationException ex)
{
Console.Write("Invalid operation. Please try again.");
}
catch(FormatException ex)
{
Console.Write("Not a valid format. Please try again.");
}
catch(Exception ex)
{
Console.Write("Error occurred! Please try again.");
}
}
}
In the above example, we have specified multiple catch blocks with different exception types. We
can display an appropriate message to the user, depending upon the error, so the user does not repeat the
same mistake again.
catch blocks with the same exception type are not allowed. A
catch block with the base Exception type must be the last block.
Invalid catch Block
A parameterless catch block and a catch block with the Exception
parameter are not allowed in the same try-catch statements, because they both do the same
thing.
try
{
//code that may raise an exception
}
catch //cannot have both catch and catch(Exception ex)
{
Console.WriteLine("Exception occurred");
}
catch(Exception ex) //cannot have both catch and catch(Exception ex)
{
Console.WriteLine("Exception occurred");
}
Also, parameterless catch block catch{ } or general catch block
catch(Exception ex) must be the last block. The compiler will give an error if you have other
catch blocks after a catch or catch(Exception ex) block.
try
{
//code that may raise an exception
}
catch
{
//code that may raise an exception
}
catch
{
// this catch block must be last block
}
catch (NullReferenceException nullEx)
{
Console.WriteLine(nullEx.Message);
}
catch (InvalidCastException inEx)
{
Console.WriteLine(inEx.Message);
}
finally Block
The finally block is an optional block and should come after a try or catch block.
The finally block will always be executed whether or not an exception occurred. The
finally block generally used for cleaning-up code e.g., disposing of unmanaged objects.
static void Main(string[] args)
{
FileInfo file = null;
try
{
Console.Write("Enter a file name to write: ");
string fileName = Console.ReadLine();
file = new FileInfo(fileName);
file.AppendText("Hello World!")
}
catch(Exception ex)
{
Console.WriteLine("Error occurred: {0}", ex.Message );
}
finally
{
// clean up file object here;
file = null;
}
}
finally blocks are not allowed. Also, the finally
block cannot have the return, continue, or break keywords. It doesn't let control to leave the
finally block.
Nested try-catch
C# allows nested try-catch blocks. When using nested try-catch blocks, an exception will be caught in the
first matching catch block that follows the try block where an exception occurred.
static void Main(string[] args)
{
var divider = 0;
try
{
try
{
var result = 100/divider;
}
catch
{
Console.WriteLine("Inner catch");
}
}
catch
{
Console.WriteLine("Outer catch");
}
}
An inner catch block will be executed in the above example because it is the first
catch block that handles all exception types.
If there isn't an inner catch block that matches with raised exception type, then the control
will flow to the outer catch block until it finds an appropriate exception filter. Consider the
following example.
static void Main(string[] args)
{
var divider = 0;
try
{
try
{
var result = 100/divider;
}
catch(NullReferenceException ex)
{
Console.WriteLine("Inner catch");
}
}
catch
{
Console.WriteLine("Outer catch");
}
}
In the above example, an exception of type DivideByZeroException will be raised. Because an
inner catch block handles only the NullReferenceTypeException, it will be handle
by an outer catch block.
C# - throw keyword
We have seen in the previous section how to handle exceptions which are automatically raised by CLR. Here, we will see how to raise an exception manually.
An exception can be raised manually by using the throw keyword. Any type of exceptions which is derived from Exception class can be raised using the throw keyword.
static void Main(string[] args)
{
Student std = null;
try
{
PrintStudentName(std);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message );
}
Console.ReadKey();
}
private static void PrintStudentName( Student std)
{
if (std == null)
throw new NullReferenceException("Student object is null.");
Console.WriteLine(std.StudentName);
}
In the above example, PrintStudentName() method raises NullReferenceException if Student object is null.
Please notice that throw creates an object of any valid exception type using the new keyword. The throw keyword cannot be used with any other type which does not derive from the Exception class.
Re-throwing an Exception
You can also re-throw an exception from the catch block to pass on to the caller and let the caller handle it the way they want. The following example re-throws an exception.
static void Main(string[] args)
{
try
{
Method1();
}
catch(Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
}
static void Method1()
{
try
{
Method2();
}
catch(Exception ex)
{
throw;
}
}
static void Method2()
{
string str = null;
try
{
Console.WriteLine(str[0]);
}
catch(Exception ex)
{
throw;
}
}
In the above example, an exception occurs in Method2(). The catch block simply throws that exception using only throw keyword (not throw e). This will be handled in catch block in Method1() where it again re-throw the same exception and finally it is being handled in the Main() method. The stack trace of this exception will give you the full detail of where exactly this exception occurred.
If you re-throw an exception using exception parameter then it will not preserve the original exception and creates a new exception. The following example demonstrates this.
static void Main(string[] args)
{
try
{
Method1();
}
catch(Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
}
static void Method1()
{
try
{
Method2();
}
catch(Exception ex)
{
throw ex;
}
}
static void Method2()
{
string str = null;
try
{
Console.WriteLine(str[0]);
}
catch(Exception ex)
{
throw;
}
}
In the above example, exception caught in the Main() method will display stack trace from Method1 and Main
method. It will not display Method1 in stack trace as we re-throw exception in Method1() using
throw ex. So, never throw an exception using throw <exception parameter>.
Custom Exception Type in C#
C# includes the built-in exception types such as NullReferenceException,
MemoryOverflowException, etc. However, you often like to raise an exception when the business
rule of your application gets violated. So, for this, you can create a custom exception class by deriving
the ApplicationException class.
The .Net framework includes ApplicationException class since .Net v1.0. It was designed to use
as a base class for the custom exception class. However, Microsoft now recommends Exception
class to create a custom exception class. You should not throw an ApplicationException
exception in your code, and you should not catch an ApplicationException exception unless you
intend to re-throw the original exception.
For example, create InvalidStudentNameException class in a school application, which does not allow any special character or numeric value in a name of any of the students.
class Student
{
public int StudentID { get; set; }
public string StudentName { get; set; }
}
[Serializable]
class InvalidStudentNameException : Exception
{
public InvalidStudentNameException() { }
public InvalidStudentNameException(string name)
: base(String.Format("Invalid Student Name: {0}", name))
{
}
}
Now, you can raise InvalidStudentNameException in your program whenever the name contains
special characters or numbers. Use the throw keyword to raise an exception.
class Program
{
static void Main(string[] args)
{
Student newStudent = null;
try
{
newStudent = new Student();
newStudent.StudentName = "James007";
ValidateStudent(newStudent);
}
catch(InvalidStudentNameException ex)
{
Console.WriteLine(ex.Message );
}
Console.ReadKey();
}
private static void ValidateStudent(Student std)
{
Regex regex = new Regex("^[a-zA-Z]+$");
if (!regex.IsMatch(std.StudentName))
<b>throw new InvalidStudentNameException(std.StudentName);</b>
}
}
Thus, you can create custom exception classes to differentiate from system exceptions.