Exception Types - Understanding What Can Go Wrong

Vaibhav • September 10, 2025

In the last article, you learned how to use try, catch, and finally blocks to handle exceptions gracefully. You saw how exceptions occurs, and how to clean up resources reliably. Now it’s time to look deeper into the kinds of exceptions you’ll encounter in real-world C# programs.

C# provides a rich hierarchy of exception types, each designed to represent a specific kind of failure. Some are thrown by the runtime, others by the .NET libraries, and some you’ll throw yourself. Understanding these types helps you write more precise catch blocks, choose the right exception when signaling failure, and avoid masking real problems with overly broad handlers.

What Is an Exception Type?

Every exception in C# is an object derived from the base class System.Exception. This class provides common properties like Message, StackTrace, and InnerException. Specific exception types inherit from it and add context for particular failure scenarios.

// Base class
Exception ex = new Exception("Something went wrong.");

// Specific type
DivideByZeroException dbz = new DivideByZeroException("Cannot divide by zero.");

The second example is more informative. It tells you not just that something failed, but what kind of failure occurred. This is why catching specific types is better than catching Exception directly.

Common Built-In Exception Types

Let’s look at some of the most frequently encountered exceptions in beginner and intermediate C# programs. These are part of the .NET base class library and are thrown automatically when certain conditions are violated.

DivideByZeroException

Thrown when you attempt to divide an integer by zero. This is one of the first exceptions most learners encounter.

int x = 10;
int y = 0;
int result = x / y; // Throws DivideByZeroException

This exception is thrown by the runtime. You can catch it and provide a fallback value or message.

NullReferenceException

Thrown when you try to access a member on a null object reference. This is a common source of bugs in object-oriented code.

string name = null;
Console.WriteLine(name.Length); // Throws NullReferenceException

You can avoid this by checking for null before accessing members, or by using nullable-aware features introduced in later chapters.

IndexOutOfRangeException

Thrown when you try to access an array or list element using an invalid index.

int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[5]); // Throws IndexOutOfRangeException

This exception signals a logic error in your indexing. Always validate indices before accessing elements.

FormatException

Thrown when a string is not in the correct format for parsing. This often occurs when converting user input.

string input = "abc";
int value = int.Parse(input); // Throws FormatException

Use int.TryParse to avoid exceptions when parsing user input.

ArgumentException and Its Variants

These exceptions are thrown when a method receives an invalid argument. There are several variants:

// ArgumentException
void SetName(string name)
{
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException("Name cannot be empty.");
}

// ArgumentNullException
void Print(string message)
{
    if (message == null)
        throw new ArgumentNullException(nameof(message));
}

// ArgumentOutOfRangeException
void SetAge(int age)
{
    if (age < 0 || age > 120)
        throw new ArgumentOutOfRangeException(nameof(age), "Age must be between 0 and 120.");
}

These exceptions help you enforce method contracts and validate inputs early.

InvalidOperationException

Thrown when a method call is invalid for the object’s current state. This is useful when the object is technically valid, but the operation doesn’t make sense.

Queue queue = new Queue();
int item = queue.Dequeue(); // Throws InvalidOperationException if queue is empty

This exception is common in collection operations and stateful APIs.

IOException

Thrown when an I/O operation fails-like reading a file that doesn’t exist or writing to a locked file. You’ll encounter this in Chapter 12 when working with file I/O.

IOException is a base class for many file-related exceptions, including FileNotFoundException, DirectoryNotFoundException, and PathTooLongException.

Exception Properties

Every exception object provides useful properties that help you understand what went wrong:

try
{
    int[] data = new int[2];
    Console.WriteLine(data[5]);
}
catch (Exception ex)
{
    Console.WriteLine("Type: " + ex.GetType().Name);
    Console.WriteLine("Message: " + ex.Message);
    Console.WriteLine("Stack Trace: " + ex.StackTrace);
}

These properties are invaluable for logging and debugging. The StackTrace shows where the exception occurred, and Message gives a human-readable description.

Custom Exceptions

Sometimes the built-in types aren’t expressive enough. You can define your own exception classes by inheriting from Exception.

class InvalidUserInputException : Exception
{
    public InvalidUserInputException(string message) : base(message) { }
}

Use custom exceptions when you want to signal domain-specific errors in your application. But don’t overuse them-prefer built-in types when they’re sufficient.

Choosing the Right Exception

When throwing exceptions, choose the most specific type that accurately describes the problem. This helps callers handle the error appropriately and makes your code easier to debug.

Don’t throw Exception or SystemException directly. Use specific types like ArgumentException, InvalidOperationException, or define your own.

Avoiding Overly Broad Catch Blocks

Catching Exception or using empty catch blocks can hide real problems and make debugging harder. Always catch the exceptions you expect and can handle meaningfully.

// ❌ Avoid this
try
{
    // risky code
}
catch
{
    // silently ignore
}

// ✅ Better
try
{
    int value = int.Parse("abc");
}
catch (FormatException)
{
    Console.WriteLine("Invalid number format.");
}

This approach makes your error handling intentional and transparent.

Summary

In this article, you explored the landscape of exception types in C#. You learned about common built-in exceptions like DivideByZeroException, NullReferenceException, and ArgumentException, and saw how to use them to write precise and meaningful error handling code. You also learned how to inspect exception objects, define custom exceptions, and avoid common pitfalls like overly broad catch blocks.

Understanding exception types is key to writing resilient software. It helps you anticipate failure, communicate clearly with other developers, and build systems that behave predictably under stress. In the next article, we’ll explore Creating Custom Exceptions: how to design your own exception types for domain-specific errors and integrate them into your application’s error handling strategy.