Common C# exceptions and how to fix them
Posted May 22, 2023 | 13 min. (2688 words)C# is a powerful programming language, but like all code, comes with its fair share of errors. Even experienced developers can find themselves stumped when they encounter a strange exception or error code. Fortunately, with the right knowledge and techniques, you can tackle any C# exception.
In this article, we’ll discuss some of the most common exceptions in C# programming and how they can be fixed. Before we get into the nuts and bolts, we’ll give you a brief overview of standard exceptions and their composing parts and also provide tips on how to avoid these errors in the first place.
By following our advice, you’ll be able to write better code and produce better results.
What is an exception?
An exception is a mechanism you can use to deal with errors. It’s that simple. An exception is triggered — thrown — by code that registers an error and caught by code that can correct or get around the error. In certain scenarios where, say, a C programmer would return an error code, a Java or C# programmer would most likely throw an exception.
This is where the common language runtime (CLR) comes into its own - using try/catch blocks to handle exceptions and show relevant error messages. This allows us to either catch the exception and handle it at runtime or throw it back up to the calling method for proper handling.
By the end of this article, you’ll have a great grasp of exception fundamentals, become familiar with the most common C# errors and exceptions, and know exactly how to handle these programming mishaps like a pro. So without further ado, let’s explore the different parts of any C# exception.
Anatomy of C# exceptions
Ready to become a C# exception handling pro? Let’s kick things off with a quick rundown of the anatomy of these pieces of code. Our focus will be on the key components that allow you to skillfully catch, handle, and even throw your own exceptions.
Starting off strong, we’ll take a closer look at the trusty ’try’ keyword and block. By mastering this essential concept, you’ll be well on your way to writing cleaner, more efficient code and handling any exceptions that crop up.
Try
The first part of the exception-handling anatomy is the try block. Inside the try block, your code can run without worrying about exceptions being thrown. You can then practice with code that may throw exceptions.
The try block encloses the code and is always followed by either a catch or a finally block, or both.
Here’s an example of a C# code excerpt that demonstrates the try block in action:
string content = string.Empty;
try
{
content = System.IO.File.ReadAllText(@"C:\file.txt");
}
The code above declares a variable and assigns the empty string to it. Then, we have the try block. The single line of code inside the try block uses the ReadAllText static method from the System.IO.File class. We fear that the file represented by the path may not exist, in which case an exception will be raised. But of course, that’s not enough. Our code doesn’t do anything to handle the exception. It doesn’t even currently compile! This is the message from the compiler:
Expected catch or finally
The message is self-explanatory. We need either a catch or finally block since it doesn’t make any sense to try to handle an exception and then forget to do the handling part. Let’s get to that.
Catch
If an exception is thrown within a try block, then you can use the catch keyword and clause to handle it. Catch clauses are used to inspect the type of error that occurred in order to decide what action should be taken next. This can range from re-throwing the exception to providing an alternate option or simply logging the error. We’ll expand the previous example with more code.
static void Main(string[] args)
{
string content = string.Empty;
try
{
Console.WriteLine("First message inside try block.");
Console.WriteLine("Second message inside try block.");
content = System.IO.File.ReadAllText(@"C:\file.txt");
Console.WriteLine("Third message inside try block. This shouldn't be printed.");
}
catch
{
Console.WriteLine("First message inside the catch block.");
Console.WriteLine("Second message inside the catch block.");
}
Console.WriteLine("Outside the try-catch.");
Console.ReadLine();
}
If you run the code above, this is what you should see:
First message inside try block.
Second message inside try block.
First message inside the catch block.
Second message inside the catch block.
Outside try-catch.
The file at that path doesn’t exist, so a System.IO.FileNotFoundException is thrown. The execution flow is immediately interrupted when that happens. So the line that would print “Third message inside try block. This shouldn’t be printed.” never gets executed.
The execution flow is picked up by the catch block, which executes its two lines. When it finishes, it hands control back to the main method, which then shows Outside try-catch.
and waits for user input.
The catch block can be extended to take the thrown Exception as an argument. This is useful for catching certain types of errors, or for error logging. For example:
catch (System.IO.FileNotFoundException e)
{
Console.WriteLine("If you're reading this, an exception was thrown.");
Console.WriteLine("Message: " + e.Message);
}
The above catch block takes an Exception of type System.IO.FileNotFoundException, which means it will only run if that Exception is thrown. This also means that catch blocks can be stacked on top of each other, so that different Exceptions can be handled differently. For example:
catch (System.IO.FileNotFoundException e)
{
Console.WriteLine("If you're reading this, a FileNotFoundException was thrown.");
} catch (System.IO.FileLoadException e)
{
Console.WriteLine("If you're reading this, a FileLoadException was thrown.");
} catch (Exception e)
{
Console.WriteLine("If you're reading this, an exception was thrown.");
}
In the code above, if a System.IO.FileNotFoundException was thrown, the program will output:
If you're reading this, a FileNotFoundException was thrown.
If instead a System.IO.FileLoadException was thrown, the program will output:
If you're reading this, a FileLoadException was thrown.
If any other exception was thrown which doesn’t match the other two types, the program will output:
If you're reading this, an exception was thrown.
This is because every exception is of type Exception, so if it wasn’t caught already by the other catch blocks, it will be caught by this one.
Finally
The “finally” block allows developers to execute code after a try/catch block regardless of whether or not an exception was thrown. This ensures that any necessary clean-up operations are performed, such as releasing resources and closing connections.
The finally block also provides a means for the developer to catch any unhandled exceptions that may occur during execution in order to log them or take appropriate action.
Consider the code below:
try
{
content = System.IO.File.ReadAllText(path);
Console.WriteLine("If you're reading this, no exception was thrown.");
}
catch (System.IO.FileNotFoundException e)
{
Console.WriteLine("If you're reading this, an exception was thrown.");
Console.WriteLine("Message: " + e.Message);
}
finally
{
Console.WriteLine("This will be printed, no matter what.");
}
Console.WriteLine("Outside the try-catch.");
That’s still the same example, but now it’s a lot simpler. Notice the finally block at the end. If you run this code, you should see the following messages:
If you're reading this, an exception was thrown.
Message: Could not find file 'C:\file.txt'.
This will be printed, no matter what.
Outside the try-catch.
Let’s now simulate the file’s existence. Comment out the line trying to read from the file and run the application again. Here’s what you should see now:
If you're reading this, no exception was thrown.
This will be printed, no matter what.
Outside the try-catch.
As you can see, in the most recent scenario, the exception wasn’t thrown, so no lines were executed in the catch block. On the other hand, the finally block was executed in both scenarios.
Throw
The “throw” block allows you to manually throw an exception from within the code, which is helpful for validating user input. By using the throw keyword, you can generate your own exceptions, use existing ones, or re-throw an already caught exception to provide more information about where and why it occurred.
Use the throw keyword, followed by an instantiation of the class for the exception you wish to throw:
public ProductService(IProductRepository repository)
{
if (repository == null)
throw new ArgumentNullException();
this.repository = repository;
Common .NET exceptions
When programming with C#, there are lots of errors you might encounter that can cause your code to crash and burn. As with anything related to programming, it’s often a case of trial and error, fixing issues as you find them.
While you can’t avoid all errors, knowing how to handle issues as they arise is the key to any developer’s success. If you want your code to be reliable and bug-free, understanding the common exceptions is the best place to start.
In this section, we’ll explore common issues you might face and share the best practices for exceptions.
System.NullReferenceException
The System.NullReferenceException is one of the most famous (or rather infamous) exceptions out there. It’s thrown when you try to call a method/property/indexer on a variable that contains a null reference - one not pointing to any object.
As an example, the code below will cause a null reference exception:
Person p = people.Where(x => x.SSN == verifySsn).FirstOrDefault();
string name = p.Name;
In the example above, we filter a sequence comparing the SSN property on each item to the verifySsnvariable variable. We then use the FirstOrDefault() method to take just the first item from the sequence. If the sequence doesn’t produce any items, then it returns the default value for the type. Since Person is a reference type, its return value is null.
In the next line, we try to dereference the Name property on a null reference and, you guessed it, a null reference exception.
If you wanted to communicate to clients of your code that a given method doesn’t accept null as valid values for its parameters, the right exception type to use is the System.ArgumentNullException.
If you’re writing any piece of code that’s going to be used by third parties—even if those third parties are your coworkers—think very hard about whether to accept null references as valid values or not.
System.IndexOutOfRangeException
The System.IndexOutOfRangeException is another common exception to be on the lookout for. It appears if you try to access an index that isn’t inside the range of a collection’s indices. For example, a five-integer code will raise an exception if the input doesn’t fall within those preset parameters.
Make sure your code never tries to access entries outside of the range of the array to avoid this issue. Using the appropriate loop iteration control, such as for or foreach loops, makes this simple to do.
System.IO.IOException
The System.IO.IOException is a fairly straightforward exception. It appears when the code you’ve inputted is trying to access a file or directory that it doesn’t have the right permissions for. You could also encounter this exception if the system is trying to perform a restricted operation like deleting, renaming or rewriting a locked file.
To avoid this issue, make sure your process has permission to access the relevant files and that they aren’t being locked by other processes. You can also use try/catch blocks around any code that might cause an IOException, as needed.
System.Net.WebException
The System.Net.WebException is thrown when the external resource you’re trying to access fails.
The code below shows how this exception appears when attempting to make a request using the WebRequest class:
try
{
WebRequest request = WebRequest.Create("http://www.example.com");
var response = request.GetResponse();
}
catch (WebException ex)
{
//Handle exception here
}
There are a few reasons this could occur, including the server being unavailable, the URL being incorrect or network issues. The best way to fix this error is to troubleshoot, starting by checking if the URL is valid and that the server is online. If you’re unsure if the URL is valid, use a tool to inspect the request and response.
System.Data.SqlClient.SqlException
The System.Data.SqlClient.SqlException displays when a program tries to interact with a database using Microsoft’s SQL Server. This exception is usually caused by an execution query that contains syntax errors, or when there’s an invalid column name, table name, or stored procedure name in the query.
To fix this error, check that the query syntax is correct and that all column names, table names etc are valid. You can further safeguard against this exception by using proper parameterisation when executing queries to avoid errors.
System.StackOverflowException
The System.StackOverflowException occurs when a program recurses too deeply. This happens when a method calls itself (or when multiple methods call each other) and gets stuck in an infinite loop. It’s a particularly tricky one to debug because the root cause of the exception isn’t always obvious.
To avoid this error, look out for infinite loops when writing your code. Analyse the recursion structure of your program and check that all recursive methods have termination points.
System.OutOfMemoryException
This is arguably one of the most confusing C# exceptions out there. There are resources around the web that do a great job at clarifying the issue, but we find this analogy helpful:
You know when you want to park your car and you can’t because other drivers haven’t parked properly? If you could just add all of the available spaces between the parked cars, it’d be more than enough to fit your vehicle. But currently, it’s not possible because it’s not a contiguous area.
That’s pretty much what happens when you get this exception. There may be plenty of total memory available, but there’s no continuous portion of it to meet the needed allocation. In practice, this exception happens, for instance, if you try to expand a StringBuilder beyond its MaxCapacity property.
Avoid this error by checking your program isn’t trying to allocate more memory than is available. You can also try running the program with a larger heap size or limiting the number of allocated objects.
System.InvalidCastException
The System.InvalidCastException occurs when an invalid type conversion is attempted. This exception occurs when a value of one type is cast to another type, like an integer to a string. The following code example shows how this exception could be thrown:
int number = 10;
string strNumber = (string)number; //Invalid cast!
To fix this error, ensure that the type of the value you are attempting to convert is compatible with the target type.
System.InvalidOperationException
The System.InvalidOperationException occurs when a program tries to perform an operation which isn’t valid given the current state of the object. For example, attempting to start a stopped service or accessing a closed connection may throw this exception. It can also be thrown if you attempt to access a collection which has been modified while it’s being enumerated.
To fix this error, make sure your code can handle any state changes that may occur in the objects it interacts with. Additionally, use locks when accessing collections to prevent them being modified to ensure thread safety.
System.ObjectDisposedException
The last C# exception we’ll cover in this post falls firmly in the category of developer error. This exception is thrown when you try to do something with an IDisposable that was already disposed of.
This typically happens when the developer calls the Dispose, Close, or similar method and afterwards tries to access a member of the object.
Error handling for the win
Many new software developers don’t fully understand the importance of error handling and instead aim for a make-no-mistakes approach. But realistically, knowing how to identify, troubleshoot and fix errors is one of the most critical skills for developers at any level. Take the time to understand the main C# exceptions, and you’ll soon develop a solid foundation for future error handling.
Finally, it’s important to remember that many exceptions are avoidable if try/catch blocks and proper debugging are used. With these tips in mind, you’ll be able to handle the most common C# exceptions with ease.
Happy coding!
If you enjoyed this article, you may also be interested in Java exceptions: Common terminology with examples.
To get error-free code in C# or any other major language, use an error monitoring tool. Never miss another error, and easily group, refine, diagnose and resolve errors. Try Raygun Error Monitoring and Crash Reporting free for 14 days.