C# Exception Handling with Try, Catch, and Finally
An exception is an error condition that occurs while a C# program is running. Invalid user input, a missing file, insufficient permissions, a failed network request, an unavailable database, or division by zero can interrupt the normal flow of a program.
C# exception handling lets a program detect these conditions and respond in a controlled way. The main keywords are try, catch, finally, and throw.
trycontains code that may throw an exception.catchhandles an exception thrown from the associatedtryblock.finallycontains cleanup code that should run whether or not an exception occurs.throwcreates an exception or passes a caught exception to calling code.
C# Try Catch
A C# try-catch statement executes potentially unsafe operations inside a try block. When an exception occurs, the runtime stops executing the remaining statements in that block and searches for a compatible catch block.
C# Try Catch Syntax
Following is the syntax of Try-Catch in C#.
try {
// code that may throw an exception
}
catch(Exception ex) {
// handle exception
}
The try block contains the statements that may fail. The catch block runs only when a matching exception is thrown. The variable ex refers to the exception object and provides information such as its message and stack trace.
Although catch (Exception ex) can catch most application exceptions, production code should generally catch a more specific exception type when it can handle that condition meaningfully.
C# Try Catch Example with Invalid User Input
Following is an example, where we read two numbers from user, and print the sum. We are converting each line provided by the user into integer using Convert.ToInt32() function.
If user provides an invalid number (say containing alphabets), Convert.ToInt32() throws an exception and if there is no code to catch the Exception, the program terminates. But, in here, we are using try-catch block to catch an exception if something goes wrong with the code in try block.
Program.cs
using System;
namespace CSharpExamples {
class Program {
static void Main(string[] args) {
try {
Console.Write("Enter number, a: ");
int a = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter number, b: ");
int b = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("a+b : " + (a + b));
} catch(Exception ex){
Console.WriteLine(ex.Message);
}
Console.WriteLine("Execution after try-catch block continues.");
}
}
}
Output
PS D:\workspace\csharp\HelloWorld> dotnet run
Enter number, a: 25
Enter number, b: 14
a+b : 39
Execution after try-catch block continues.
PS D:\workspace\csharp\HelloWorld> dotnet run
Enter number, a: 4asd
Input string was not in a correct format.
Execution after try-catch block continues.
During the first run, we have provided numbers in correct format. But in the second run, we provided input as 4asd for a number. This throws an exception. We catch the exception, printed the error message to the user and continued with the execution of program statements after the try-catch block.
For expected input-validation failures, int.TryParse() is often preferable because invalid input does not need to be treated as an exceptional event.
Console.Write("Enter an integer: ");
string? input = Console.ReadLine();
if (int.TryParse(input, out int number))
{
Console.WriteLine($"You entered {number}.");
}
else
{
Console.WriteLine("Enter a valid integer.");
}
Handling Specific C# Exception Types
Different failures produce different exception types. Catching specific types makes the intended recovery behavior clearer and prevents unrelated errors from being handled incorrectly.
FormatException: a string is not in the expected format.OverflowException: a converted number is outside the supported range.DivideByZeroException: integer or decimal division uses zero as the divisor.FileNotFoundException: a requested file cannot be found.UnauthorizedAccessException: the program lacks permission for an operation.ArgumentException: a method receives an invalid argument.
C# Multiple Catch Blocks Example
A single try block may have several catch blocks. Place more specific exception types before more general ones because C# evaluates them from top to bottom.
using System;
class Program
{
static void Main()
{
try
{
Console.Write("Enter the numerator: ");
int numerator = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter the denominator: ");
int denominator = Convert.ToInt32(Console.ReadLine());
int result = numerator / denominator;
Console.WriteLine($"Result: {result}");
}
catch (FormatException)
{
Console.WriteLine("Enter integers only.");
}
catch (OverflowException)
{
Console.WriteLine("The entered number is outside the Int32 range.");
}
catch (DivideByZeroException)
{
Console.WriteLine("The denominator cannot be zero.");
}
}
}
Each catch block handles one known failure. If no matching block exists, the exception continues up the call stack until another method handles it or the application terminates.
C# Try Catch Finally for Cleanup
The finally block is used for cleanup that must normally occur after a try statement. It runs after successful execution and after a handled exception.
try
{
// Operation that may fail
}
catch (SpecificException ex)
{
// Handle the known failure
}
finally
{
// Cleanup that should always be attempted
}
C# Finally Block Example
using System;
using System.IO;
class Program
{
static void Main()
{
StreamReader? reader = null;
try
{
reader = new StreamReader("notes.txt");
Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"File not found: {ex.FileName}");
}
finally
{
reader?.Dispose();
Console.WriteLine("File operation finished.");
}
}
}
For objects that implement IDisposable, such as streams and database connections, a using statement is usually simpler and safer than calling Dispose() manually in finally.
using System;
using System.IO;
try
{
using StreamReader reader = new StreamReader("notes.txt");
Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"File not found: {ex.FileName}");
}
Throwing and Rethrowing Exceptions in C#
Use throw when a method detects a condition that it cannot handle locally. Choose an exception type that accurately describes the problem and include a useful message.
static decimal CalculateUnitPrice(decimal totalPrice, int quantity)
{
if (quantity <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(quantity),
"Quantity must be greater than zero."
);
}
return totalPrice / quantity;
}
A caught exception can also be rethrown when the current method needs to log or partially process it but cannot complete recovery.
try
{
ProcessFile();
}
catch (IOException ex)
{
Console.Error.WriteLine($"File processing failed: {ex.Message}");
throw;
}
Use throw; by itself to preserve the original stack trace. Writing throw ex; resets stack-trace information and can make the original source of the failure harder to diagnose.
C# Exception Filters with the When Keyword
An exception filter adds a condition to a catch block. The block handles the exception only when both the exception type and the filter expression match.
try
{
SendRequest();
}
catch (TimeoutException ex) when (retryCount < 3)
{
retryCount++;
Console.WriteLine($"Request timed out. Retry {retryCount}.");
}
Filters are useful when the same exception type requires different handling based on an error code, retry count, file name, or another property available at the time of failure.
How C# Selects a Catch Block
When an exception is thrown, C# examines the associated catch blocks in order. It runs the first compatible block and skips the remaining ones. A general block such as catch (Exception) must therefore appear after specific exception handlers.
try
{
// Code that may fail
}
catch (FileNotFoundException ex)
{
// Most specific handler
}
catch (IOException ex)
{
// Handles other input/output failures
}
catch (Exception ex)
{
// Last-resort application-level handler
}
A broad handler placed first would make the more specific handlers unreachable, and the compiler reports an error.
Why Use Try Catch in C#?
If an exception thrown by the code is not handled, it could terminate the program abruptly after the exception.
There are different types of Exceptions in C# that could be thrown. Many system functions that we use throw an exception. A developer should handle those exceptions considering all the fail-over scenarios for each C# statement in the program.
Exception handling is useful when the program can recover, provide a clearer message, release resources, record diagnostic information, retry a temporary operation, or move the failure to a layer that has enough context to decide what to do.
C# Try Catch Practices to Avoid Hidden Errors
- Catch only exceptions you can handle: allow unexpected failures to propagate to an appropriate application-level handler.
- Keep the try block focused: include only the operations associated with the exceptions being handled.
- Prefer specific exception types: avoid using
Exceptionas the default handler for every operation. - Do not leave catch blocks empty: silently ignoring a failure can leave the program in an invalid state.
- Do not expose internal details to users: log technical data securely and display a clear, appropriate message.
- Use validation for expected conditions: methods such as
TryParseare better for routine invalid input. - Preserve the stack trace: use
throw;when rethrowing the current exception. - Clean up disposable resources: prefer
usingdeclarations or statements where possible. - Do not use exceptions for ordinary control flow: exceptions are intended for conditions outside the normal successful path.
C# Try Catch Frequently Asked Questions
Can a C# try block have more than one catch block?
Yes. A try block can have multiple catch blocks for different exception types. Put the most specific exception types first and broader types last.
Can try be used without catch in C#?
Yes. A try block may be followed by a finally block without a catch block. This is useful when cleanup must occur locally but the exception should continue to calling code.
Does finally always execute in C#?
The finally block normally executes whether the try block succeeds or throws an exception. It may not complete in exceptional process-level situations, such as immediate process termination or a fatal runtime failure.
What is the difference between throw and throw ex in C#?
throw; rethrows the current exception while preserving its original stack trace. throw ex; throws the referenced exception again but resets important stack-trace context, so throw; is generally the correct choice inside a catch block.
Should invalid user input be handled with try catch?
A try-catch block can handle invalid input from conversion methods, but int.TryParse, decimal.TryParse, and similar methods are usually better when invalid input is expected and can be handled through validation.
C# Exception Handling Editorial QA Checklist
- Confirm that every example places specific exception handlers before
catch (Exception). - Verify that code using
finallyperforms cleanup and does not hide the original exception. - Check that rethrow examples use
throw;instead ofthrow ex;. - Use
TryParseexamples for expected input validation rather than relying only on exceptions. - Ensure disposable streams, files, and connections are closed with
usingor reliable cleanup code. - Confirm that user-facing messages do not reveal stack traces, credentials, file-system secrets, or other sensitive implementation details.
TutorialKart.com