1. Introduction
File operations are almost always affected by things outside your program: a file can be deleted, moved, locked by another program, the user might lose permissions or the disk might run out of space. All that can lead to exceptions — special situations when program execution is interrupted and an exception object (Exception) is "thrown". If you don't catch and handle such an exception, your program will crash with an error and show the user an ugly red text in the console (or, worse — silently die).
In C# you use the try-catch block (or, simply, an "error trap") to handle such situations. It lets you not only "catch" the error, but also decide what to do next: show a friendly message, ask the user to pick another file, write the error to a log, or just retry.
In real apps
If you’re writing a console calculator, you might get away without catching file errors. But if you have a document-processing app at a company or a game that saves progress — not handling such errors is like walking a tightrope without a safety net: sooner or later something will go wrong.
2. Quick recap of exceptions
Basic try-catch syntax
Before diving into file-specific cases, let’s recall the basic syntax of the try-catch construct (which we already met in lectures about exceptions):
try
{
// Here is code that might "throw" an exception
}
catch (Exception ex)
{
// Here we catch all possible exceptions... But better not to do this!
Console.WriteLine("Something went wrong: " + ex.Message);
}
Inside the try we put potentially risky code, and in the catch — what to do if something goes wrong.
Programmer joke: "try-catch is like a digital invisibility cloak for errors. The error exists, but you don't see it!"
File exceptions
When you work with files via .NET, you'll most often encounter exceptions when creating streams, reading or writing. Here are some common scenarios and the exceptions related to them:
- File not found → FileNotFoundException
- No access to file/directory → UnauthorizedAccessException
- Path problems (e.g., invalid characters, path too long) → PathTooLongException, ArgumentException
- File is used by another process → IOException
- No free space on disk → IOException
Most file-related issues are derivatives of IOException. They all inherit from the base type System.Exception.
3. Practice: catching and handling file errors
Let’s look at typical cases using examples from our "growing app": suppose we try to read a greeting from a file, process it and show it to the user. We'll add error checking with try-catch.
Example 1: Handling "file not found"
string filePath = "hello.txt";
try
{
string greeting = File.ReadAllText(filePath);
Console.WriteLine("File content: " + greeting);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("File not found! Check that the file " + filePath + " exists.");
// We can also print details
Console.WriteLine("Technical details: " + ex.Message);
}
If the file "hello.txt" is missing, the program won't crash; it will print a friendly message. That's how you make your program resilient to user "silly" mistakes.
Example 2: Access denied
Let's take a trickier case: the file exists, but we don't have permission to read it (for example, someone set write-only rights or the file is in a protected folder).
try
{
string secret = File.ReadAllText("C:\\Windows\\System32\\config.txt");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("No access to the file! Try running the program as administrator.");
Console.WriteLine("Technical reason: " + ex.Message);
}
Example 3: General IOException
Some errors can be due to locking conflicts (another process holds the file open), lack of disk space, or hardware issues:
try
{
File.WriteAllText("important.txt", "Important information!");
}
catch (IOException ex)
{
Console.WriteLine("File operation error: most likely it's used by another program or there's not enough disk space.");
Console.WriteLine("Technical reason: " + ex.Message);
}
4. Catching multiple exceptions: selective handling
Sometimes you need to respond differently to different exception types. In .NET you can specify multiple catch blocks — from more specific to more general (otherwise the compiler will loudly complain).
try
{
string content = File.ReadAllText("file.txt");
Console.WriteLine(content);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("File not found.");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("No access to the file!");
}
catch (IOException ex)
{
Console.WriteLine("Other I/O error: " + ex.Message);
}
Important: if you put catch (Exception ex) first, the other blocks won't make sense because the base type will catch everything!
5. Nested try-catch and retries
Sometimes you want not only to handle the error but also give the user a chance to "fix" it — for example, prompt for a path to an existing file:
string filePath;
string content;
int attempts = 0;
const int maxAttempts = 3;
do
{
Console.Write("Enter file path: ");
filePath = Console.ReadLine();
try
{
content = File.ReadAllText(filePath);
Console.WriteLine("File content:\n" + content);
break;
}
catch (FileNotFoundException)
{
Console.WriteLine("File not found! Try again.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("No access to the file! Try another file.");
}
attempts++;
}
while (attempts < maxAttempts);
if (attempts == maxAttempts)
Console.WriteLine("Too many failed attempts.");
This code is a small "friendliness interface" for a user who forgot where they saved the file.
6. Common mistakes and nuances: from laziness to awareness
A very common mistake by beginners (and even experienced devs) is catching every exception by writing simply catch (Exception) and printing "An error occurred!" without thinking about causes. That approach is bad for a few reasons. First, it hides real bugs in your business logic. Second, other unrelated errors (like typos in code or math mistakes) can be swallowed accidentally, and finding the real cause will take much longer.
It's much better to explicitly catch only the errors you can handle sensibly. If you don't know what error happened, it's often better to let it "fail" — i.e., not catch it: let the app crash so you can see the stack trace and figure out what to fix.
Note: Some exceptions can have inner causes (InnerException). It's useful to inspect them for more detailed diagnostics, especially when writing error logs.
Another nuance — if after a catch block the program can't continue (for example, you failed to open the main settings file), you can exit via return or even rethrow the exception (throw;) so you don't end up with "zombie programs" that only partly work.
GO TO FULL VERSION