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.