ZetCode

C# exception

last modified October 15, 2026

In this article we show how to work with exceptions in C#.

An exception represents an error that occurs during application execution.

During the execution of our application many things might go wrong. A disk might get full and we cannot save our file. An Internet connection might go down while our application tries to connect to a site. These errors might cause problems including an application crash. It is a responsibility of a programmer to handle errors that can be anticipated.

The try, catch and finally keywords are used to work with exceptions.

Exception is the base class for all exceptions. The errors are reported by exceptions being thrown. After an exception is thrown, it is handled by the application or by the default exception handler. The exception contains information about the error.

Different kinds of exceptions are thrown during the development of the application and after the development has finished. Developers receive exceptions with lots of technical details, while users get only short, basic informative messages.

C# catching exception

The catch keyword is used to catch an exception that is thrown.

Program.cs
int x = 100;
int y = 0;
int z;

try
{
    z = x / y;
}
catch (ArithmeticException e)
{
    Console.WriteLine("An exception occurred");
    Console.WriteLine(e.Message);
}

In the above program, we intentionally divide a number by zero. This leads to an error. Note that this is a scholarly example to demonstrate how exceptions work. In reality, division by zero is a program error which is resolved by ensuring that the denominator is not zero.

try
{
    z = x / y;
}

Statements that are error prone are placed in the try block.

catch (ArithmeticException e)
{
    Console.WriteLine("An exception occurred");
    Console.WriteLine(e.Message);
}

Exception types follow the catch keyword. In our case we have an ArithmeticException. This exception is thrown for errors in an arithmetic, casting, or conversion operation. The DivideByZeroException, which is actually thrown, derives from ArithmeticException; a catch block also catches exceptions of derived types. Statements that follow the catch keyword are executed when an error occurs. When an exception occurs, an exception object is created. From this object we get the Message property and print it to the console.

$ dotnet run
An exception occurred
Attempted to divide by zero.

C# uncaught exception

Any uncaught exception in the current context propagates to a higher context and looks for an appropriate catch block to handle it. If it can't find any suitable catch blocks, the default mechanism of the .NET runtime will terminate the execution of the entire program.

Program.cs
int x = 100;
int y = 0;

int z = x / y;

Console.WriteLine(z);

In this program, we divide by zero. There is no custom exception handling.

$ dotnet run
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
...

The .NET runtime terminates the program and prints the above error message together with the stack trace.

C# IOException

The IOException is thrown when an I/O error occurs. In the following example we read the contents of a file. The FileNotFoundException and DirectoryNotFoundException derive from IOException.

Program.cs
FileStream? fs = null;

try
{
    fs = new FileStream("langs.txt", FileMode.Open);
    var sr = new StreamReader(fs);
    string? line;

    while ((line = sr.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}
catch (IOException e)
{
    Console.WriteLine("IO Error");
    Console.WriteLine(e.Message);
}
finally
{
    Console.WriteLine("Inside finally block");
    fs?.Close();
}

The statements following the finally keyword are always executed. It is often used for clean-up tasks, such as closing files or clearing buffers.

catch (IOException e)
{
    Console.WriteLine("IO Error");
    Console.WriteLine(e.Message);
}

In this case, we catch a specific IOException exception.

finally
{
    Console.WriteLine("Inside finally block");
    fs?.Close();
}

These lines guarantee that the file handle is closed. If the file could not be opened, the fs variable is null; the null-conditional operator ensures that we call Close only on an existing object. In modern C#, we would rather use the using statement, which closes the file automatically.

$ cat langs.txt
C#
Java
Python
Ruby
PHP
JavaScript

These are the contents of the langs.txt file.

$ dotnet run
C#
Java
Python
Ruby
PHP
JavaScript
Inside finally block

The finally block is executed also when an exception occurs.

$ rm langs.txt
$ dotnet run
IO Error
Could not find file '/home/jano/Documents/prog/csharp/exceptions/langs.txt'.
Inside finally block

C# multiple exceptions

We often need to deal with multiple exceptions.

Program.cs
try
{
    Console.Write("Enter first number: ");
    int x = Convert.ToInt32(Console.ReadLine());

    Console.Write("Enter second number: ");
    int y = Convert.ToInt32(Console.ReadLine());

    int z = x / y;
    Console.WriteLine($"Result: {x} / {y} = {z}");
}
catch (DivideByZeroException e)
{
    Console.WriteLine("Cannot divide by zero");
    Console.WriteLine(e.Message);
}
catch (FormatException e)
{
    Console.WriteLine("Wrong format of number.");
    Console.WriteLine(e.Message);
}

In this example, we catch various exceptions. Note that more specific exceptions must precede the generic ones; otherwise, the compiler reports an error. We read two numbers from the console and check for zero division error and for wrong format of number.

$ dotnet run
Enter first number: we
Wrong format of number.
The input string 'we' was not in a correct format.
$ dotnet run
Enter first number: 12
Enter second number: 0
Cannot divide by zero
Attempted to divide by zero.

C# exception filters

An exception filter is a condition specified with the when keyword. The catch block is executed only if the condition is true.

Program.cs
using System.Net;

int[] codes = [404, 500, 503];

foreach (var code in codes)
{
    try
    {
        throw new HttpRequestException("Request failed", null, (HttpStatusCode) code);
    }
    catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.NotFound)
    {
        Console.WriteLine("Resource not found");
    }
    catch (HttpRequestException e) when ((int?) e.StatusCode >= 500)
    {
        Console.WriteLine($"Server error: {(int?) e.StatusCode}");
    }
}

We simulate failed HTTP requests. Depending on the status code, the exception is handled by different catch blocks.

$ dotnet run
Resource not found
Server error: 500
Server error: 503

C# rethrowing exceptions

Sometimes we want to do some work when an exception occurs, such as logging, but let the caller handle the exception. For this, we use the throw statement without an argument.

Program.cs
try
{
    ReadConfig("config.json");
}
catch (FileNotFoundException e)
{
    Console.WriteLine($"Caught in caller: {Path.GetFileName(e.FileName)}");
}

void ReadConfig(string fileName)
{
    try
    {
        var content = File.ReadAllText(fileName);
        Console.WriteLine(content);
    }
    catch (IOException e)
    {
        Console.WriteLine($"Logging error: {e.GetType().Name}");
        throw;
    }
}

The ReadConfig method logs the error and rethrows the exception. Using throw; instead of throw e; preserves the original stack trace.

$ dotnet run
Logging error: FileNotFoundException
Caught in caller: config.json

C# throw helpers

Methods should validate their arguments. .NET provides static throw helper methods that check a condition and throw the appropriate exception.

Program.cs
try
{
    Console.WriteLine(GetArea(3, 4));
    Console.WriteLine(GetArea(-3, 4));
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine(e.Message);
}

try
{
    PrintName(null);
}
catch (ArgumentNullException e)
{
    Console.WriteLine(e.Message);
}

int GetArea(int width, int height)
{
    ArgumentOutOfRangeException.ThrowIfNegative(width);
    ArgumentOutOfRangeException.ThrowIfNegative(height);

    return width * height;
}

void PrintName(string? name)
{
    ArgumentNullException.ThrowIfNull(name);
    Console.WriteLine(name);
}

We use the ThrowIfNegative and ThrowIfNull helper methods. The name of the parameter is added to the error message automatically.

$ dotnet run
12
width ('-3') must be a non-negative value. (Parameter 'width')
Actual value was -3.
Value cannot be null. (Parameter 'name')

C# custom exception

Custom exceptions are user defined exception classes that derive from the System.Exception class.

Program.cs
int x = 340004;
const int LIMIT = 333;

try
{
    if (x > LIMIT)
    {
        throw new BigValueException("Exceeded the maximum value");
    }
}
catch (BigValueException e)
{
    Console.WriteLine(e.Message);
}

class BigValueException : Exception
{
    public BigValueException(string msg) : base(msg) { }
}

We assume that we have a situation in which we cannot deal with big numbers.

class BigValueException : Exception

We have a BigValueException class. This class derives from the built-in Exception class.

const int LIMIT = 333;

Numbers bigger than this constant are considered to be "big" by our program.

public BigValueException(string msg) : base(msg) {}

Inside the constructor, we call the parent's constructor. We pass the message to the parent.

if (x > LIMIT)
{
    throw new BigValueException("Exceeded the maximum value");
}

If the value is bigger than the limit, we throw our custom exception. We give the exception a message "Exceeded the maximum value".

catch (BigValueException e)
{
    Console.WriteLine(e.Message);
}

We catch the exception and print its message to the console.

$ dotnet run
Exceeded the maximum value

Source

Exceptions and Exception Handling

In this article we worked with exceptions in C#.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.