Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
59m 46s
Exception handling is a crucial aspect of robust application development. Tim Corey's video on "Handling Exceptions in C# - When to catch them, where to catch them, and how to catch them," provides a detailed explanation of what exceptions are, how to handle them, and where to handle them.
This article aims to explain Exception handling in C# using Tim Corey's video. It is a powerful feature that allows developers to manage errors and exceptional conditions that arise during program execution. Using the try, catch, and finally blocks, C# provides a structured way to handle runtime errors, log exceptions, and maintain program flow.
Tim begins by explaining that many developers have an incorrect view of exceptions and their handling. He emphasizes the importance of understanding what exceptions are and where and how to handle them properly to create more robust applications.
Tim creates a console application in Visual Studio 2017 to demonstrate exception handling. He recommends using console applications for testing new topics as they require minimal setup and are easy to work with.
using System;
namespace ExceptionsDemo
{
class Program
{
static void Main(string[] args)
{
// Placeholder for input and output operations
Console.ReadLine();
}
}
}Imports System
Namespace ExceptionsDemo
Module Program
Sub Main(args As String())
' Placeholder for input and output operations
Console.ReadLine()
End Sub
End Module
End NamespaceTim adds a class library to the solution to simulate a real-world scenario where different methods call each other.

He deletes the default class and creates a new class named DemoCode.
public class DemoCode
{
// Method to retrieve a number based on the provided position
public int GetNumber(int position)
{
int[] numbers = { 1, 4, 7, 2 };
return numbers[position];
}
// Intermediate method calls GetNumber
public int ParentMethod(int position)
{
return GetNumber(position);
}
// Top-level method calls ParentMethod
public int GrandparentMethod(int position)
{
return ParentMethod(position);
}
}Public Class DemoCode
' Method to retrieve a number based on the provided position
Public Function GetNumber(position As Integer) As Integer
Dim numbers As Integer() = {1, 4, 7, 2}
Return numbers(position)
End Function
' Intermediate method calls GetNumber
Public Function ParentMethod(position As Integer) As Integer
Return GetNumber(position)
End Function
' Top-level method calls ParentMethod
Public Function GrandparentMethod(position As Integer) As Integer
Return ParentMethod(position)
End Function
End ClassThe DemoCode class contains methods that call each other, ultimately retrieving a number from an array based on the given position.
Tim explains that the application is intended to demonstrate failures rather than successes. He introduces an out-of-bounds exception by passing an invalid position to the GrandparentMethod.
DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");Dim demo As New DemoCode()
Dim result As Integer = demo.GrandparentMethod(4) ' This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}")Running the above code with an invalid position results in an IndexOutOfRangeException. Tim shows how the Visual Studio debugger highlights the issue and provides detailed information about the exception.
Tim explains a common mistake developers make when they first learn about try-catch blocks. They often wrap the entire block of code where they expect an exception might occur, which can lead to improper handling.
try
{
int output = 0;
output = numbers[position];
return output;
}
catch (Exception ex)
{
// Avoid returning default values that can mask the problem
return 0;
}Try
Dim output As Integer = 0
output = numbers(position)
Return output
Catch ex As Exception
' Avoid returning default values that can mask the problem
Return 0
End TryTim highlights that this approach is problematic because it hides the exception and continues execution with incorrect assumptions. For example, returning 0 as a default value might not be appropriate and can cause further issues.
Tim stresses that exceptions provide critical information about unexpected states in the application. If the application continues in such a state without proper handling, it can lead to further errors and data corruption.
Instead of swallowing exceptions, it is essential to handle them appropriately. Here is a better approach:
try
{
return numbers[position];
}
catch (Exception ex)
{
// Log the exception or handle it appropriately
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to be handled by a higher-level handler
}Try
Return numbers(position)
Catch ex As Exception
' Log the exception or handle it appropriately
Console.WriteLine(ex.Message)
Throw ' Re-throw the exception to be handled by a higher-level handler
End TryBy re-throwing the exception, you ensure that the issue is propagated and can be handled at a higher level if necessary.
Tim explains that while some exceptions can be handled gracefully without crashing the application, it is important to provide useful feedback to the user. For example, showing a message box or a notification with an option to retry the operation.
Tim demonstrates how to use the StackTrace property of the exception object to get detailed information about where the exception occurred. This includes the class, method, and line number, which is invaluable for debugging.
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
throw;
}Try
Return numbers(position)
Catch ex As Exception
Console.WriteLine(ex.StackTrace)
Throw
End TryThe StackTrace property provides a complete trace of the call stack, helping developers pinpoint the exact location of the issue.
Tim explains that handling exceptions properly is not just about catching them but also about knowing where to place your try-catch blocks. The key is to place try-catch blocks at a level where you have enough context to handle the exception appropriately.
Placing a try-catch block deep in the call stack often doesn't allow you to handle the exception effectively because you lack the context of the higher-level operations.
// Deep level exception handling (not ideal)
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}' Deep level exception handling (not ideal)
Try
Return numbers(position)
Catch ex As Exception
Console.WriteLine(ex.Message)
Throw
End TryPlacing the try-catch block at the top level, such as in the user interface or entry point of the application, allows you to handle exceptions with the full context of the operation.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}Try
Dim result As Integer = demo.GrandparentMethod(4) ' This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}")
Catch ex As Exception
Console.WriteLine(ex.Message)
Console.WriteLine(ex.StackTrace)
End TryThis way, you can provide more informative messages to the user and decide whether the application can continue running or if it should be terminated.
Tim emphasizes the importance of stack trace information in diagnosing exceptions. The stack trace provides a detailed call history, showing where the exception occurred and the chain of method calls that led to it.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}Try
Dim result As Integer = demo.GrandparentMethod(4) ' This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}")
Catch ex As Exception
Console.WriteLine(ex.Message)
Console.WriteLine(ex.StackTrace)
End TryThis output gives the exact location of the exception and the path taken through the code, making it easier to debug and fix the issue.
Tim demonstrates how to handle logic at the appropriate level. For example, if a method is responsible for opening and closing a database connection, it should handle exceptions to ensure resources are properly managed.
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
Console.WriteLine("Close database connection");
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Ensure the exception is propagated
}
}Public Function GrandparentMethod(position As Integer) As Integer
Try
Console.WriteLine("Open database connection")
Dim output As Integer = ParentMethod(position)
Console.WriteLine("Close database connection")
Return output
Catch ex As Exception
Console.WriteLine(ex.Message)
Throw ' Ensure the exception is propagated
End Try
End FunctionIn this example, if an exception occurs, the database connection is not properly closed, leading to potential resource leaks. By adding a try-catch block, you can ensure that the connection is closed even if an exception occurs.
Tim introduces the finally block, which ensures that certain code runs regardless of whether an exception occurs. This is particularly useful for cleaning up resources, such as closing database connections.
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to ensure it's handled by a higher-level handler
}
finally
{
Console.WriteLine("Close database connection");
}
}Public Function GrandparentMethod(position As Integer) As Integer
Try
Console.WriteLine("Open database connection")
Dim output As Integer = ParentMethod(position)
Return output
Catch ex As Exception
Console.WriteLine(ex.Message)
Throw ' Re-throw the exception to ensure it's handled by a higher-level handler
Finally
Console.WriteLine("Close database connection")
End Try
End FunctionThe finally block runs after the try and catch blocks, ensuring that the connection is closed even if an exception is thrown.
Tim explains the importance of re-throwing exceptions to pass them up the call stack. This allows higher-level handlers to process the exceptions appropriately.
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throws the exception to be handled by the calling method
}Catch ex As Exception
Console.WriteLine(ex.Message)
Throw ' Re-throws the exception to be handled by the calling method
End TryRe-throwing the exception with throw; ensures that the full stack trace is preserved, providing valuable context for debugging.
Tim demonstrates how exceptions bubble up through the call stack. Each method checks for a try-catch block and either handles the exception or passes it up to the caller.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}Try
Dim result As Integer = demo.GrandparentMethod(4) ' This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}")
Catch ex As Exception
Console.WriteLine(ex.Message)
Console.WriteLine(ex.StackTrace)
End TryIn this example, the GrandparentMethod catches the exception, logs it, and re-throws it. The top-level try-catch block in the console application then handles the exception and displays the error message and stack trace.
Tim highlights several common mistakes developers make when handling exceptions:
Using throw ex;:
Rewriting the stack trace and losing valuable context.
Example:
catch (Exception ex)
{
// Incorrect
throw ex; // Rewrites stack trace
}
Throwing a New Exception:
Creating a new exception with a custom message but losing the original stack trace.
Example:
catch (Exception ex)
{
// Incorrect
throw new Exception("I blew up");
}
Tim explains how to create a new exception while preserving the original stack trace. This can be useful when you want to provide a more meaningful error message or a different exception type while still retaining the context of the original error.
catch (Exception ex)
{
throw new ArgumentException("You passed in bad data", ex);
}Catch ex As Exception
Throw New ArgumentException("You passed in bad data", ex)
End CatchBy passing the original exception (ex) as the inner exception, you maintain the original stack trace, which is crucial for debugging.
Tim demonstrates how to access the original exception's message and stack trace when creating a new exception.
catch (Exception ex)
{
Console.WriteLine("You passed in bad data");
Console.WriteLine(ex.StackTrace);
throw new ArgumentException("You passed in bad data", ex);
}Catch ex As Exception
Console.WriteLine("You passed in bad data")
Console.WriteLine(ex.StackTrace)
Throw New ArgumentException("You passed in bad data", ex)
End TryThis ensures that the exception thrown up the stack contains both the new message and the original exception details.
Tim provides a method to loop through all inner exceptions to extract their messages and stack traces.
catch (Exception ex)
{
Exception inner = ex;
while (inner != null)
{
Console.WriteLine(inner.StackTrace);
inner = inner.InnerException;
}
throw;
}Catch ex As Exception
Dim inner As Exception = ex
While inner IsNot Nothing
Console.WriteLine(inner.StackTrace)
inner = inner.InnerException
End While
Throw
End TryThis loop iterates through each inner exception, printing its stack trace, ensuring that all layers of exceptions are accounted for.
Tim discusses how to handle different types of exceptions using multiple catch blocks. This allows for specific handling based on the exception type.
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}Try
' Code that might throw an exception
Catch ex As ArgumentException
Console.WriteLine("You gave us bad information. Bad user!")
Catch ex As Exception
Console.WriteLine(ex.Message)
Console.WriteLine(ex.StackTrace)
End TryIn this example, ArgumentException is handled specifically by printing a custom message, while all other exceptions fall back to a general handler that prints the exception message and stack trace.

Tim emphasizes the importance of order when using multiple catch blocks. The most specific exceptions should be caught first, followed by more general exceptions.
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}Try
' Code that might throw an exception
Catch ex As ArgumentException
Console.WriteLine("You gave us bad information. Bad user!")
Catch ex As Exception
Console.WriteLine(ex.Message)
Console.WriteLine(ex.StackTrace)
End TryIf a more general catch block appears before a specific one, it will catch all exceptions, and the specific catch block will never be reached, leading to compilation errors.
Tim Corey's advanced video guide on exception handling in C# covers critical techniques for creating new exceptions, preserving stack traces, and using multiple catch blocks effectively. By following his best practices, developers can create robust applications that handle exceptions gracefully and provide valuable debugging information.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy