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
171: The using Statement and using Declarations
A few years ago, I was reviewing a PR for a junior developer who had written a small utility to parse several large CSV files. The code looked clean, but as soon as we ran it against a folder with a few hundred files, the application crashed with a IOException: "The process cannot access the file because it is being used by another process." He was baffled because he wasn't opening the same file twice. The culprit? He was opening StreamReader instances inside a loop but never closing them. He assumed the Garbage Collector would just clean them up when he was done, but unmanaged resources like file handles don't work that way—they stay locked until the GC decides to run a collection, which might be way too late.
Managing Resources with the using Statement
In C#, any class that implements the IDisposable interface is telling you: "I am holding onto something that the .NET runtime can't manage automatically." This could be a file handle, a database connection, or a network socket. If you just let these objects go out of scope without calling Dispose(), you're essentially gambling with your system's resources.
The using statement is the gold standard for handling this. It creates a scope where the resource is guaranteed to be disposed of the moment the code exits that block, regardless of whether it exited normally or threw an exception. Under the hood, the compiler actually transforms this into a try-finally block, ensuring Dispose() is called in the finally section.
using (StreamWriter writer = new StreamWriter("log.txt"))
{
writer.WriteLine("Log entry: Application started.");
writer.WriteLine("Processing data...");
// If an exception happens here, the file is still closed properly.
}
// writer is disposed here automatically.
I generally recommend this block-based approach when you want the resource released as quickly as possible. If you're doing a lot of heavy lifting after the file operation in the same method, don't keep the file open longer than you need to.
Streamlining with using Declarations
If you find the nested curly braces of the traditional using statement make your code look like a pyramid of indentation, C# 8.0 introduced using declarations. This is a much leaner syntax that removes the need for the explicit block. Instead, the resource is disposed of when the variable goes out of scope—typically at the end of the method.
public void SaveUserPreferences(UserPrefs prefs)
{
using var stream = new FileStream("prefs.dat", FileMode.Create);
using var writer = new StreamWriter(stream);
writer.WriteLine(prefs.Theme);
writer.WriteLine(prefs.FontSize);
// No curly braces needed.
// writer and stream are disposed when the method returns.
}
It's a cleaner look, and in many cases, it's exactly what you want. However, a word of caution: because the disposal happens at the end of the method, you lose fine-grained control. If you have a long-running method that does a quick file write at the beginning and then spends ten seconds calling a slow API, that file remains locked for those ten seconds. In those specific scenarios, stick to the block-based using statement to free the resource immediately.
📋 Practical Task
Exercise: Building a Robust Log Append Utility
You need to create a method called AppendToSystemLog that takes a string message and appends it to a file named system.log. To make this exercise effective, you must ensure the file is handled safely to prevent locking issues.
Requirements:
- Implement the
AppendToSystemLogmethod. - Use a
StreamWriterto append the text to the file. - Apply a
usingdeclaration or statement to ensure theStreamWriteris disposed of immediately after the write operation is complete. - Include a timestamp at the beginning of each log entry (e.g.,
[2023-10-27 10:00:00] Message here). - Wrap the logic in a
try-catchblock to handle potentialIOExceptionerrors, printing a user-friendly message to the console if the file is unavailable.
There are no comments for now.