1. Introduction: why do files like to "act up"?
Ever opened a document and the system suddenly says: "File not found"? Or trying to save something ends with: "Access denied"? Those are exactly the moments when files start behaving weird. If with encodings we see "garbage characters", here the problems are tied to the file system itself.
You can imagine the file system as a big library, and the app as a librarian. When it asks for a file, different answers are possible:
- The book (i.e. the file) isn't there — it simply doesn't exist.
- The section of the library where the book should be doesn't exist — the required directory is missing.
- The book is "locked" or checked out — the file is used by another process or there are no access rights.
- The shelf is full — there's not enough disk space to create a new file.
- Or, for example, you're trying to put a book into the DVD return area — i.e. performing an unsupported operation.
In C# all these situations are expressed via exceptions. The developer's job is not just to watch the program "crash", but to anticipate possible problems and handle them gracefully. Nobody wants the user to see a mysterious error message full of confusing text.
We're already familiar with try-catch. It's our lifebuoy that lets us "catch" an exception and take action instead of letting the program terminate abruptly.
// This is our old friend, a reminder from Lecture 57
try
{
// Put code here that may throw
// For example, trying to read a file
}
catch (Exception ex) // Catch any exception
{
// Handle the error here
Console.WriteLine($"Oops, an error occurred: {ex.Message}");
}
Today we'll dig into specific exceptions that occur when working with files. This will let us write more robust code that can "talk" to the file system even when it "acts up".
Exceptions are not bugs, they are distress signals!
It's important to understand: an exception isn't always a bug in your code. Often it's a signal that something went wrong in the external environment your code interacts with. The file system is a vivid example of such an external environment. You might write perfectly correct code to read a file, but if the user deleted that file before your program could read it, you'll get an exception. And that's fine! Your task as a developer is to teach the program how to react to such situations.
Let's look at the most common "distress signals" you might encounter when working with files.
2. FileNotFoundException: the file simply wasn't there
This is probably the most common exception when working with files. It occurs when you try to open, read, or perform another operation on a file that doesn't exist at the specified path.
Real-life example: You ask a friend to bring you the book "Programming in C# 14 for Beginners" from his library, and he replies: "I don't have that book". Similarly your program can ask the OS: "Give me settings.txt", and the system replies: "Sorry, not found".
Let's write code that reads the file abracadabra.txt for our task manager app. If the file is missing, we should inform the user instead of just "crashing".
try
{
using var reader = new StreamReader("abracadabra.txt");
Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException ex)
{
Console.WriteLine("File not found: " + ex.FileName);
}
In everyday life it's like arriving at a bus stop and the bus doesn't show up — and there's no shuttle either. Sad.
Note: This exception is often accompanied by an incorrect file path (for example, you forgot you're running from a different directory).
3. DirectoryNotFoundException: folders that evaporated
This exception is very similar to FileNotFoundException, but it concerns not the file itself, but the directory (folder) where that file should be. If you specify a path like "C:\MyDocuments\MyProject\Data\report.txt" and the Data folder doesn't exist, you'll get DirectoryNotFoundException.
In our app, if we wanted to save settings in a subfolder data, e.g. "./data/app_settings.txt", and that folder didn't exist, attempt to write or read would hit this problem.
DirectoryNotFoundException can be caught separately like FileNotFoundException, or it can be part of the more general IOException, which we'll discuss a bit later.
try
{
using var writer = new StreamWriter(@"C:\very\strange\path\file.txt");
writer.WriteLine("Hello world");
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine("Directory not found!");
}
Common mistake: A folder can be deleted at any time (for example, someone clears temp files), or your write path is misconfigured.
4. UnauthorizedAccessException: entry forbidden!
Imagine you want to place a book on a shelf and there's a sign "Staff only". That's UnauthorizedAccessException! It occurs when your program doesn't have the necessary access rights to a file or directory. This can be because of:
- Insufficient user rights: You're trying to write to a folder only admins can write to (for example, C:\Windows).
- The file is marked read-only: You're trying to modify a file with the "read-only" attribute.
- The file is system or hidden: And it has specific restrictions.
This is very common in corporate environments or when users install apps into protected directories.
Let's try to write a file to a system folder where a normal user usually has no rights. (Warning: run such code carefully to avoid cluttering system directories, or use a sandbox.)
try
{
using var writer = new StreamWriter("/system/settings.conf");
writer.WriteLine("All power to the students!");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("No access to the file or directory!");
}
If you run this without admin rights you'll likely see a message "Access to this folder is denied". If you run as administrator, the file will probably be created. This example shows how important it is to handle UnauthorizedAccessException so your user understands why the app can't perform the operation.
5. IOException: the file system's generic "uh-oh"
IOException is the most general exception related to I/O operations. It's thrown when there's some problem with the I/O device or the file system that doesn't fall under more specific exceptions like FileNotFoundException or UnauthorizedAccessException.
Typical scenarios where you might get IOException:
- The file is already used by another program: For example, you try to delete a file that's open in Notepad or in another app.
- The disk is full: Not enough space to write the file.
- Corrupted file or file system: Rare, but happens.
- Network issues: If the file is on a network drive and the connection drops.
- The file or directory name is too long. (This might be PathTooLongException, but sometimes it falls under IOException).
IOException is a kind of "universal key" for many problems. Often when you catch IOException you should check its Message property to get more detailed info about what exactly went wrong.
try
{
using var file = new FileStream("busyfile.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.None);
// keep the file open somehow
// at the same time elsewhere:
using var writer = new StreamWriter("busyfile.txt");
writer.WriteLine("Attempt to write...");
}
catch (IOException ex)
{
Console.WriteLine("I/O error: " + ex.Message);
}
Important note: IOException is a base class for many other "file" exceptions.
6. Other, but no less important, "unpleasantnesses"
PathTooLongException
This is less common but still encountered: your path (or file/directory name) is too long for the OS. For example, if you decide to put a short summary of "War and Peace" into the file name, Windows won't forgive you.
On Windows the historical limit is 260 characters for the full path. Newer OS versions and .NET allow enabling "long paths", but that doesn't always work by default.
try
{
string veryLongPath = new string('a', 300); // 300 chars!
using var writer = new StreamWriter(veryLongPath + ".txt");
writer.WriteLine("This file name is too long!");
}
catch (PathTooLongException ex)
{
Console.WriteLine("File name or path is too long!");
}
NotSupportedException
This is a rare but "surreal" case when you pass an invalid path string to a constructor like StreamReader or FileStream, for example a string with prohibited characters or using "magical" paths like C:::\wow???\file.txt.
7. Useful nuances
Which exceptions correspond to what
| Exception | Cause | Example situation |
|---|---|---|
|
File not found | Opening a non-existent file |
|
Directory not found | Opening a file in a deleted folder |
|
No access (permissions) | Writing to a protected directory |
|
General I/O error | File opened by another process |
|
Path too long | Too long file/folder name |
|
Invalid path format | Path with prohibited characters |
Common mistakes and special cases
Example: File busy by another process
Imagine you're a real hacker who opened a text file in Notepad and forgot to close it. Meanwhile your program tries to write to the same file. Here you'll get IOException (or even a "sharing violation").
Example: No access
Try saving data to C:\Windows without admin rights — you'll get UnauthorizedAccessException. Same happens if you opened a file read-only but try to write to it.
Example: Invalid path
On Windows you can't name files with characters <>:"/\|?*. If you try to write such a file you'll get NotSupportedException (or ArgumentException).
Example: No disk space
Surprisingly, this also throws IOException — for example, when the disk is full (that's why it's useful to occasionally clean the Downloads folder).
How not to mess up: best practices for error detection
- Check file existence with File.Exists and directory existence with Directory.Exists before trying to open a file. But be careful: the file can disappear or appear after the check (classic race condition).
- Never "swallow" exceptions completely (don't do just catch { }) unless you implement a strict error logger. Always at least log or show the user what happened.
- Try to catch specific exceptions (FileNotFoundException, DirectoryNotFoundException), not only the general Exception.
- For cross-platform apps consider that permissions, path formats, and name length limits differ between Windows, Linux, macOS.
- If you handle text files, always explicitly specify the encoding — otherwise surprises are inevitable.
- For bulk file operations use bulk processing patterns that account for possible failures at each step.
Cheat-sheet for common exceptions
| Exception | When it occurs? | How to prevent/handle? |
|---|---|---|
|
No file at the specified path | Check the file with File.Exists or create it |
|
The path contains a non-existent directory | Verify the path, create directories with Directory.CreateDirectory |
|
No rights to the file/folder, file read-only, file busy by another process | Run as appropriate user, check ACLs, close files properly |
|
General I/O error, file busy, no disk space | Use try-catch, avoid keeping files open |
|
Path or file name too long | Shorten paths, use relative paths |
|
Invalid path format | Check the path string for prohibited characters |
Flowchart "What to do on a file error?"
flowchart TD
A[File operation] --> B{Exception thrown?}
B -- No --> C[Operation completed successfully]
B -- Yes --> D{What type of exception?}
D -- FileNotFound --> E[Ask user to provide the correct file or create it]
D -- DirectoryNotFound --> F[Create the missing directory]
D -- UnauthorizedAccess --> G[Ask user to restart with appropriate permissions]
D -- IOException --> H[Check who has the file open, check the disk]
D -- PathTooLong --> I[Shorten the path]
D -- NotSupported --> J[Validate the path format]
D -- Other --> K[Show a message and re-check the logic]
8. How common exceptions look in a real app?
Developing our demo project, let's say we have a mini app that saves user notes to a file and then reads them back.
string notesPath = "notes.txt";
Console.Write("Enter a note: ");
string note = Console.ReadLine();
try
{
// Save the note
using var writer = new StreamWriter(notesPath, true, Encoding.UTF8);
writer.WriteLine(note);
// Read all notes
Console.WriteLine("Your notes:");
using var reader = new StreamReader(notesPath, Encoding.UTF8);
Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException)
{
Console.WriteLine("Notes file not found. Try creating it manually.");
}
catch (DirectoryNotFoundException)
{
Console.WriteLine("Path to notes file is invalid. Check if the folder exists.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("No rights to write or read the file. Run the program as administrator.");
}
catch (IOException ex)
{
Console.WriteLine("An I/O error occurred: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Unexpected error: " + ex.Message);
}
This example shows a typical situation any app can face: trying to save data to a file and then read it back. Each catch block handles a specific class of errors — from missing file or folder to permission issues and general I/O errors. Thanks to this the program doesn't "crash" on the first failure but tells the user what went wrong and what can be done. This approach makes the app more reliable and user-friendly.
GO TO FULL VERSION