Welcome to Managing Input/Output (I/O)

Welcome to one of the most practical and essential topics in your CCEA AS 1 Software Systems Development journey! In programming, a system is only as good as its ability to communicate. Whether taking data from a user typing at the keyboard, reading saved records from a disk file, or printing neat reports to the screen, Input/Output (I/O) is the bridge between your C# code and the outside world.

Don't worry if working with external files and parsing inputs seems daunting at first. We will break every single concept down into small, digestible steps, show you clear code patterns, and point out exactly what examiners look for in your 2-hour AS 1 written exam.

---

1. Standard Console Input and Output

The console is your standard command-line interface. In C#, the Console class provides ready-made methods to send text to the user and receive text back.

Console Output: Displaying Information

There are two primary methods for sending output to the console window:

Console.WriteLine(): Prints the text and automatically moves the cursor to the next line.
Console.Write(): Prints the text but leaves the cursor on the exact same line (very useful when prompting a user for input on the same line).

Formatting Your Output

In your exam, you may need to display variables combined with descriptive text. C# offers three standard approaches:

1. String Concatenation: Joining strings and variables with the + operator.
Example: Console.WriteLine("Score: " + score + " points.");

2. String Formatting (string.Format): Using numbered placeholders.
Example: Console.WriteLine(string.Format("Score: {0} points.", score));

3. String Interpolation: Prefixing the string with a dollar symbol and placing variables directly inside curly braces.
Example: Console.WriteLine($"Score: {score} points.");

Console Input: Capturing User Responses

To read data entered by the user, we use standard input methods:

Console.ReadLine(): Pauses the program, waits for the user to type their input and press Enter, and returns the entire input as a string.
Console.Read(): Reads just the next single character from the input stream.

Parsing and Data Conversion

Crucial Rule: Console.ReadLine() always returns a string data type. If you ask a user for their age, exam mark, or product price, you cannot immediately perform mathematical calculations on it. You must convert or parse that string into a numeric or boolean primitive type.

Direct Parsing (int.Parse / double.Parse):
\nConverts a valid string directly into a number.
Example: int age = int.Parse(Console.ReadLine());
Danger: If the user types "ten" or leaves it blank, the program will immediately crash with a FormatException.

Safe Parsing (int.TryParse):
\nAttempts to convert the string. It returns a boolean (true if successful, false if failed) without throwing a fatal crash.
Example:
bool isValid = int.TryParse(Console.ReadLine(), out int age);

Quick Review: Console I/O

• Always remember that Console.ReadLine() captures text as a string.
• Use int.Parse() or double.Parse() for conversions, but protect against invalid formats.
• Use $"..." (string interpolation) for clean, readable output.

---

2. File Input and Output (Streams & Text Files)

Console data is temporary; once the application closes, console data vanishes. To save data permanently (persistence), we read from and write to secondary storage files using Streams.

Analogy: Think of a stream like a water pipe connecting your program to a text file. Data flows character by character through the pipe.

The Namespace Requirement

Before using any file-handling classes in C#, you must include the file input/output namespace at the very top of your code file:
using System.IO;
Examiner Warning: Leaving this namespace out in coding questions is a very common mistake!

Writing to Text Files with StreamWriter

The StreamWriter class creates a stream to write characters to a sequential file.

Overwriting vs. Appending:
When instantiating a StreamWriter, its constructor takes an optional boolean parameter that determines whether existing file content is kept or wiped:
1. new StreamWriter("results.txt") or new StreamWriter("results.txt", false): Overwrites the file. Any previous data in the file is erased.
2. new StreamWriter("results.txt", true): Appends to the file. New data is added onto the end of the existing file.

Writing Methods:
- sw.WriteLine("Text"): Writes the line followed by a line break.
- sw.Write("Text"): Writes the text without adding a line break.

Reading from Text Files with StreamReader

The StreamReader class allows your program to open an existing text file and read its contents sequentially from start to finish.

Key Reading Methods and Properties:
- sr.ReadLine(): Reads one line of text. When it reaches the end of the file, it returns null.
- sr.ReadToEnd(): Reads everything from the current position to the very end of the file into one large string.
- sr.EndOfStream: A boolean property that returns true when the file stream has reached the end.

Sequential File Reading Patterns

In the AS 1 exam, you are frequently asked to read through a file line-by-line using a loop. Here are the two standard patterns:

Pattern A: Using the EndOfStream property
StreamReader sr = new StreamReader("Students.txt");
while (!sr.EndOfStream)
{
    string line = sr.ReadLine();
    Console.WriteLine(line);
}
sr.Close();

Pattern B: Checking for null
StreamReader sr = new StreamReader("Students.txt");
string line;
while ((line = sr.ReadLine()) != null)
{
    Console.WriteLine(line);
}
sr.Close();

Resource Cleanup: Closing Streams and the 'using' Block

When writing data to a file, C# holds text in temporary memory called a buffer before flushing it to the physical disk. Furthermore, the operating system locks the file while it is open.

• If you forget to close the stream using .Close(), your output file may end up completely empty or corrupted because buffered data was never flushed!
• Alternatively, you can use a using statement block, which automatically closes and disposes of the stream even if an error occurs:
using (StreamWriter sw = new StreamWriter("Log.txt", true))
{
    sw.WriteLine("Entry recorded.");
} // Stream automatically closes here!

Quick Review: File I/O

• Always import using System.IO;.
StreamWriter(path, true) appends; StreamWriter(path, false) overwrites.
• Check !sr.EndOfStream or test for null to avoid infinite loops when reading.
• Always release file locks with .Close() or a using statement.

---

3. Exception Handling in Input/Output

I/O operations are the most error-prone parts of any software application. A user might enter text when a number is expected, a requested text file might not exist, or a USB drive might be unplugged while saving. To prevent programs from abruptly crashing, we use structured exception handling.

The try...catch...finally Architecture

try block: Encloses the code that might cause an error or raise an exception (such as opening a file or parsing user input).
catch block: Executes only if a specific exception is thrown inside the try block. It intercepts the crash and allows graceful recovery or user notification.
finally block: Always executes, regardless of whether an exception occurred or not. It is the gold standard location for resource cleanup (e.g., closing stream readers or writers).

Common I/O & Parsing Exceptions in C#

You should know these specific exception classes for your AS 1 exam:

FileNotFoundException: Raised when attempting to open a file that does not exist at the specified path.
DirectoryNotFoundException: Raised when part of the folder or directory path cannot be found.
IOException: The general base class for broader file input/output errors (e.g., file in use by another process, disk full).
FormatException: Raised when a parsing method (like int.Parse) receives a string in an invalid layout (e.g., trying to parse "abc" into an integer).

Complete Exception-Handled File Reader Example

StreamReader reader = null;
try
{
    reader = new StreamReader("Scores.txt");
    while (!reader.EndOfStream)
    {
        int score = int.Parse(reader.ReadLine());
        Console.WriteLine($"Score: {score}");
    }
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("Error: The requested file could not be found.");
}
catch (FormatException ex)
{
    Console.WriteLine("Error: File contains non-numeric data.");
}
catch (IOException ex)
{
    Console.WriteLine("General I/O Error: " + ex.Message);
}
finally
{
    if (reader != null)
    {
        reader.Close();
    }
}

Quick Review: Exception Handling

• Specific catch blocks (e.g., FileNotFoundException) must be placed before general ones (e.g., IOException).
• The finally block is guaranteed to run, making it ideal for closing open streams.

---

4. Top Examiner Concerns & Common Pitfalls

Avoid these classic traps identified in examiner reports to ensure you secure maximum marks:

1. Missing .Close() on StreamWriter:
Mistake: Writing data to a file without calling .Close() or flushing.
Consequence: The buffer does not write to the disk, leaving an empty output file.
Fix: Always call sw.Close() or wrap the stream in a using block.

2. The Append Parameter Confusion:
Mistake: Writing new StreamWriter("data.txt", false) when the question asks to add records onto the existing file.
Remember: true = append (keep old data). false = overwrite (wipe old data).

3. Infinite Loops When Reading Files:
Mistake: Using while (reader != null) instead of while (!reader.EndOfStream) or checking the result of ReadLine().
Consequence: The stream reader variable itself is never null once created, leading to an infinite loop.

4. Unchecked User Input:
Mistake: Writing int.Parse(Console.ReadLine()) directly inside program logic without a try...catch block or without using int.TryParse().
Consequence: Immediate program crash on unexpected user input.

5. Forgetting the I/O Namespace:
Mistake: Omitting using System.IO; at the top of written code solutions.
Fix: Whenever a question mentions reading, writing, or text files, write using System.IO; first!