File I/O in C#: File class, StreamReader/StreamWriter, and Echo Lines example
File I/O Overview
- Listing files is useful when you want to present a user with choices for actions on files.
- Merely listing directory contents does not give access to what's inside a file.
- Solution: use the file IO subsystem, which provides a set of types for interacting with files.
- Primary type: File (static methods) for high-level file operations.
- Important distinction: instead of creating a FileInfo object and querying it, you can often use static members on the File class. The transcript describes this as the
file. existence check via a static property/method referred to as Exists
(in practice, in C# this is a static method: File.Exists(path)
).
The File class and its core methods
- File.Exists(path) (static method) checks whether a file exists and returns a boolean. This avoids creating FileInfo and querying it.
- File.ReadAllText(path) (static method) reads the entire contents of a file and returns it as a string. Works for any file type (binary or text), but is most convenient for text-based files like:
- CSV (.csv)
- Text (.txt)
- Rich Text Format (.rtf)
- HTML (.html / .htm)
- File.WriteAllText(path, contents) (static method) replaces the contents of a file with the provided string.
- Other useful static methods on File include:
- File.Copy, File.Create, File.Delete, File.Move, and many more.
- Summary: the File class provides high-level, convenient operations for common file tasks without manually opening streams.
Reading and writing via streams
- After obtaining a file name, you can create a stream reader to read text from that file. A StreamReader provides methods to read text and is similar to Console.Read/ReadLine but with additional capabilities.
- A StreamWriter lets you send text to a file, analogous to Console.Write/WriteLine.
- The language feature using has two roles:
- It is used to import namespaces (as seen with
using System;
). - It can also be used with disposable resources (such as
StreamReader
and StreamWriter
) to ensure resources are released properly, even if an exception occurs.
- Why use
using
with StreamReader/StreamWriter:- Guarantees that the resources are released when the block finishes, preventing file locks.
- Helps avoid leaving file handles open if something goes wrong.
Using statement and resource management
using (var reader = new StreamReader(fileName))
{
// read from the file
}
// reader is automatically disposed here
- End-of-stream concept: a
StreamReader
exposes an EndOfStream
property. You typically loop while not at the end of the stream, e.g.:
while (!reader.EndOfStream)
{
string currentLine = reader.ReadLine();
// process line
}
- Reading behavior mirrors console input:
ReadLine()
reads a line of text. The difference is the source (file vs. console).
Echo Lines: a practical example
- Program goal: prompt user for the name of a text document, check whether it exists, and if so, print its contents line by line.
- User interaction: display prompt "Please enter the name of the file to display." and read input with
Console.ReadLine()
. - Existence check: use
File.Exists(fileName)
to determine if the file is present. - If the file does not exist: display an informative message, for example, something like "The file 'filename' does not exist." (the transcript uses a version that encloses the entered name in single quotes to highlight the exact name).
- If the file exists:
- Use a
using
block to create a StreamReader
for that file. - Read and display contents line by line with a
while
loop using reader.EndOfStream
and reader.ReadLine()
. - Each line printed to standard output with
Console.WriteLine(currentLine)
.
- The program flow in the transcript includes variable naming and debugging steps to observe behavior in an IDE like Visual Studio.
Example outline (as described in the transcript)
- Prompt user: "Please enter the name of the file to display."
- Read file name:
string fileName = Console.ReadLine();
- Check existence:
- If not exists:
Console.WriteLine("The file '{0}' does not exist.", fileName);
- Else:
- Open reader:
using (var reader = new StreamReader(fileName)) { ... }
- Loop to read each line:
while (!reader.EndOfStream)
string currentLine = reader.ReadLine();
Console.WriteLine(currentLine);
- Debugging observations mentioned in the transcript:
- Copy-pasting a file path into the debugger to inspect values.
- In the debugger, observe
reader.EndOfStream
and currentLine
contents as you single-step (e.g., using F10). - When reading the program’s own source file, you’ll see lines like
using System;
first, then namespace EchoLines
, etc. - An empty line in the file appears as a string of length zero, i.e. a line such that its length is 0: | ext{line}| = 0, and the EndOfStream remains false until the final line is read.
Practical considerations and caveats
- File.Exists vs FileInfo approach: Using
File.Exists(path)
is a straightforward existence check without extra object creation. - When reading binary files, be mindful:
ReadAllText
returns text; for binary data, use streams appropriate to binary I/O (e.g., FileStream
) instead of ReadAllText
. - Security and permissions: file access depends on user permissions; designated security constraints may cause access to fail even if a file exists.
- Resource management: always prefer
using
blocks when dealing with StreamReader
/StreamWriter
to avoid file locks and leaks. - Real-world applicability: reading large text files, processing CSVs, HTML, or configuration text; streaming line-by-line avoids loading entire large files into memory.
Connections to foundational principles
- Abstraction layers: high-level
File
class simplifies common tasks; lower-level streams give fine-grained control for advanced scenarios. - Resource management principle: deterministically releasing resources (RAII-like pattern in C# via
using
) reduces resource contention and improves stability. - Separation of concerns: IO operations (reading/writing) are separated from program logic, enabling cleaner and more testable code.
Ethical, philosophical, and practical implications
- Data privacy: reading files may expose sensitive information; ensure proper authorization and handle user input securely.
- Error handling: robust feedback when files are missing or inaccessible improves user experience and debuggability.
- Deterministic disposal of resources: prevents file locks that could affect other applications or subsequent runs.
Quick reference: key methods and concepts
- File.Exists(path) — checks for file existence (boolean)
- File.ReadAllText(path) — reads entire file into a string
- File.WriteAllText(path, contents) — writes a string to a file (overwrites)
- File.Copy, File.Create, File.Delete, File.Move — additional file operations
- StreamReader — read text from a file line by line
- StreamWriter — write text to a file
- using { … } — ensures disposal of disposable resources
- EndOfStream — boolean indicating if the end of the stream has been reached
- ReadLine() — reads a single line of text
- Read(), ReadToEnd() — other reading options from StreamReader
- Console.ReadLine(), Console.WriteLine() — input/output in the console, used in conjunction with file IO in examples
Recap: why this matters for exam readiness
- You should be able to explain why and when to use the File class vs StreamReader/StreamWriter.
- You should be able to outline the typical pattern for reading a file line-by-line with proper resource management using
using
. - You should understand how to check for file existence and handle the case when a file is missing.
- You should recognize the role of the
using
keyword in both namespace imports and resource management, and why it matters for avoiding file locks and leaks. - You should be able to sketch a simple program (like Echo Lines) that prompts for a file name, checks for existence, and prints contents line-by-line.