C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
66: Reading and Writing Files with System.IO
I've seen this exact scenario play out in code reviews more times than I can count. A developer is building a simple logging system, everything works perfectly during the first test run, and then suddenly, the application crashes with a IOException claiming the file is being used by another process. Here is the kind of code that causes that headache:
public void LogEvent(string message)
{
// We want to append the message to the log file
StreamWriter writer = new StreamWriter("log.txt", true);
writer.WriteLine($"{DateTime.Now}: {message}");
// Oops, we forgot to close the writer!
}
The "File in Use" Nightmare
At first glance, this looks fine. You open the file, write the line, and the method ends. But here is the catch: the StreamWriter holds a "lock" on that file. Because we never called writer.Close() or writer.Dispose(), the operating system thinks the program is still actively writing to log.txt. If you call LogEvent a second time immediately after the first, the second call tries to open the same file, finds the lock still active from the first call, and throws an exception.
You might be tempted to just add writer.Close() at the end, but what happens if writer.WriteLine throws an exception? The code will jump straight to your catch block, skipping the Close() call entirely, and you're right back where you started with a locked file.
Letting Go with the Using Statement
The professional way to handle this in C# is the using statement. It ensures that the IDisposable object (like our writer) is cleaned up and the file is closed, even if the code crashes halfway through.
public void LogEvent(string message)
{
using (StreamWriter writer = new StreamWriter("log.txt", true))
{
writer.WriteLine($"{DateTime.Now}: {message}");
}
// The file is guaranteed to be closed here, no matter what.
}
In modern C# (8.0+), you can make this even cleaner with a using declaration. By removing the curly braces and just putting using before the variable, the object is disposed of the moment it goes out of scope (usually at the end of the method). I personally prefer this style because it reduces the "pyramid of doom" indentation in your code.
Quick Wins with File Static Methods
Now, if you aren't doing something complex—like streaming a massive file line-by-line—you probably shouldn't be using StreamWriter or StreamReader at all. The System.IO.File class provides static helper methods that handle the opening, writing, and closing in one single line of code.
File.WriteAllText(path, contents): Overwrites the file with a string.File.AppendAllText(path, contents): Adds text to the end of the file (perfect for our logger).File.ReadAllText(path): Slurps the entire file into a single string.File.ReadAllLines(path): Reads the file into astring[]array.
Just a word of caution: ReadAllText and ReadAllLines are convenient, but they load the entire file into your RAM. If you're dealing with a 2GB log file, your app will likely crash. In those cases, go back to the StreamReader and process the file line-by-line using a while loop.
Avoiding Pathing Disasters
One last thing: stop hardcoding slashes in your paths. If you write "C:\\Logs\\log.txt", your code will break the moment you try to run it on Linux or macOS. C# gives us Path.Combine to handle this for us. It automatically detects which directory separator the current OS uses.
string folder = "Logs";
string fileName = "app_log.txt";
string fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folder, fileName);
// Also, always make sure the directory actually exists before writing to it!
if (!Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folder)))
{
Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folder));
}📋 Practical Task
Building a Simple Config-File Tracker
Your task is to create a small utility that manages a "Last Run" configuration file. This will test your ability to write, read, and handle paths correctly.
Requirements:
- Create a folder named
Settingsin the application's base directory. - Create a file inside that folder called
config.txt. - The program should:
- Read the current contents of
config.txt(if it exists) and print them to the console. - Write the current date and time to
config.txt, overwriting the previous value. - Print a confirmation message to the console stating that the timestamp has been updated.
- Read the current contents of
- Use
Path.Combinefor all path operations and ensure you use theusingstatement orFilestatic methods to avoid locking the file.
There are no comments for now.