Managing Input and Output (I/O)
Welcome to one of the most practical and exciting areas of Object-Oriented Development! Think about your favourite apps: whether it is a game saving your high score or a banking app recording a transaction, programs must interact with the outside world. They need to receive data (Input) and display or save results (Output).
In this guide, we will explore how programs handle console interactions and permanent file storage in C#. Don't worry if file handling seems daunting at first — by breaking it down step-by-step, you will master it in no time!
1. Console Input and Output
The console is the simplest way for a user to interact with your program while it is running.
Displaying Output: Console.Write vs Console.WriteLine
When sending messages to the screen, C# provides two primary methods:
• Console.Write(): Outputs text to the screen and keeps the cursor on the same line.
• Console.WriteLine(): Outputs text to the screen and automatically moves the cursor to the next line (like pressing the 'Enter' key).
Receiving Input: Console.ReadLine()
To capture what a user types, we use Console.ReadLine(). This method pauses the program and waits for the user to press 'Enter'.
Crucial Rule: Console.ReadLine() always returns data as a string, even if the user enters a number!
Type Conversion (Parsing)
If you want to perform calculations with user input, you must convert the string into a numeric data type such as int or double.
Common Conversion Methods:
• int.Parse(stringInput): Converts text to an integer. Crashes if the input is not a valid number.
• double.Parse(stringInput): Converts text to a floating-point decimal number.
• int.TryParse(stringInput, out int result): A safer conversion method that returns true if successful and false if the input was invalid, preventing crashes.
Example Code Pattern:
Console.Write("Enter your age: ");
string input = Console.ReadLine();
int age = int.Parse(input);
Key Takeaway: Console input always arrives as text. Always parse or convert strings when numeric values are needed for arithmetic operations.
---2. Understanding Files and Streams
When your program finishes running, all the variables stored in memory (RAM) disappear. To keep information permanently, we must save it to secondary storage (such as a hard drive or SSD) using text files.
What is a Stream?
In C#, reading and writing files is handled using the concept of a Stream.
Analogy: Imagine a stream of water flowing through a pipe. A data stream is a continuous sequence of bytes or characters travelling between your program and a file on disk.
• An Input Stream flows data from a file into your program (Reading).
• An Output Stream flows data from your program into a file (Writing).
The System.IO Namespace
To use file handling classes in C#, you must include the input/output library at the very top of your code file:
using System.IO;
Key Takeaway: Streams act as communication pipelines between RAM and permanent storage, allowing data to persist after a program terminates.
---3. Writing Data to Files: StreamWriter
The StreamWriter class is used to write characters and lines of text to a file.
Step-by-Step Process for Writing to a File
1. Create/Open the Stream: Instantiate a StreamWriter object linked to a specific file path.
2. Write Data: Use Write() or WriteLine() to send text down the stream.
3. Close the Stream: Always call .Close() to release the file and save changes.
Overwrite Mode vs Append Mode
When creating a StreamWriter, you can decide whether to wipe existing data or add new data to the bottom of the file:
• Overwrite Mode (Default):
StreamWriter writer = new StreamWriter("scores.txt");
Effect: If the file already exists, all existing content is erased and replaced.
• Append Mode:
StreamWriter writer = new StreamWriter("scores.txt", true);
Effect: Setting the second parameter to true keeps existing content and attaches new data at the very end of the file.
Why Must You Close Streams?
When writing, data is often held temporarily in a memory buffer. If you do not close the stream with writer.Close();:
• Data may be lost or only partially written.
• The file stays "locked" by the operating system, preventing other programs from accessing it.
Key Takeaway: Use StreamWriter to write data. Use the boolean parameter true for append mode, and always ensure the stream is closed.
4. Reading Data from Files: StreamReader
The StreamReader class is used to read text characters from a sequential file.
Common StreamReader Methods and Properties
• ReadLine(): Reads a single line of text and returns it as a string. When it reaches the end of the file, it returns null.
• ReadToEnd(): Reads the entire file from the current position all the way to the end as one large string.
• EndOfStream: A boolean property that returns true when there is no more data left to read in the file.
Reading a File Line-by-Line (The Standard Pattern)
In software development, files usually contain multiple records (e.g., student names or scores). We use a while loop to process files line-by-line until the end is reached.
Example Approach 1 (Using EndOfStream):
StreamReader reader = new StreamReader("students.txt");
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
Console.WriteLine(line);
}
reader.Close();
Example Approach 2 (Checking for Null):
StreamReader reader = new StreamReader("students.txt");
string line = reader.ReadLine();
while (line != null)
{
Console.WriteLine(line);
line = reader.ReadLine(); // Read next line
}
reader.Close();
Key Takeaway: Read sequential files line-by-line using a loop that checks !reader.EndOfStream or confirms the returned line is not null.
5. Defensive File Handling and Exception Handling
Working with external files is unpredictable. A file might be missing, corrupted, or already open in another program. Robust software anticipates these problems using defensive checks and structured exception handling.
Checking if a File Exists
Before trying to read a file, always check if it exists on disk using the File.Exists() method from the System.IO library:
if (File.Exists("data.txt"))
{
// Safe to open StreamReader
}
else
{
Console.WriteLine("Error: File could not be found.");
}
Try-Catch-Finally Blocks
Exceptions are run-time errors that crash a program if not handled. File operations should be wrapped in a try-catch block to handle errors gracefully.
• try: Contains the risky code (e.g., opening and reading a file).
• catch: Executes only if an error occurs inside the try block, displaying a user-friendly error message.
• finally: Always runs, regardless of whether an error occurred. This is the ideal place to close files safely.
Common I/O Exceptions to Know
• FileNotFoundException: Thrown when trying to open a file that does not exist.
• DirectoryNotFoundException: Thrown when part of the folder path is invalid.
• IOException: General input/output error (e.g., file is locked by another program or disk is full).
• FormatException: Thrown when parsing file data fails (e.g., trying to convert text letters to an integer).
The 'using' Statement (Automatic Resource Cleanup)
C# provides a shorthand construct called the using statement. It automatically closes the file stream when the block finishes, even if an exception occurs!
using (StreamReader reader = new StreamReader("data.txt"))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
} // Stream is automatically closed and disposed here!
Key Takeaway: Never assume a file operation will succeed. Protect your program using File.Exists(), try-catch blocks, or using statements.
6. Summary and Common Pitfalls
Common Mistakes to Avoid
• Forgetting to Close Streams: If you forget .Close(), your output file may end up completely blank because data was stuck in the memory buffer.
• Accidental Overwriting: Forgetting to add true to new StreamWriter(path, true) when you intend to append data will wipe all previous records.
• Assuming File Paths: In C#, relative file paths look in the project's output folder (e.g., bin/Debug), not where your source code file (.cs) is saved.
• Infinite Loops: Forgetting to call reader.ReadLine() inside a loop checking for null will cause an infinite loop.
Quick Review Checklist
• Console.ReadLine() captures user input as a string.
• StreamWriter writes text to files (first parameter: path; optional second parameter: append boolean).
• StreamReader reads text from files using ReadLine() or ReadToEnd().
• Always release file locks with .Close() or a using block.
• Use try-catch to handle FileNotFoundException and other run-time I/O errors gracefully.