C#

C# Fundamentals

19 lessons

Progress0%
1. Introduction to C#
1What is C#?
2. Variables and Data Types
1Data Types in C#
3. Control Flow
ConditionalsLoops
4. Methods
Defining MethodsOptional Parameters and Overloading
5. Object-Oriented Programming
Classes and PropertiesInheritanceInterfaces and Generics
6. LINQ and Async
LINQ Queriesasync/await
7. Exception Handling
try/catch/finally & Exception TypesCustom Exceptions & IDisposable
8. Delegates & Events
Delegates & LambdaEvents & Event Handlers
9. Records & Pattern Matching
Record TypesPattern Matching & Switch Expressions
10. File I/O & JSON
File & Stream OperationsJSON Serialization
All Tutorials
C#File I/O & JSON
Lesson 18 of 19 min
Chapter 10 · Lesson 1

File & Stream Operations

Working with the file system is a daily task for most applications. .NET provides two levels of abstraction: the static File and Directory helpers for simple, one-shot operations, and the streaming classes StreamReader / StreamWriter for fine-grained control over large files or custom encodings.

File — Static Helper Methods

System.IO.File exposes convenient static methods that open, operate, and close a file in one call:

MethodPurpose
File.ReadAllText(path)Read entire file as a single string
File.WriteAllText(path, text)Create/overwrite file with a string
File.AppendAllText(path, text)Append text to existing file
File.ReadAllLines(path)Read all lines into a string[]
File.WriteAllLines(path, lines)Write a string collection, one line each
File.Exists(path)Check whether a file exists
File.Delete(path)Delete a file
File.Copy(src, dest)Copy a file

These methods are ideal for small files where loading everything into memory is acceptable.

StreamReader / StreamWriter

For large files, or when you need to process line-by-line without loading everything at once, use StreamReader and StreamWriter. Always wrap them in a using statement to guarantee the underlying file handle is closed:

code
using var reader = new StreamReader(path);
string? line;
while ((line = reader.ReadLine()) != null)
    Process(line);

The Path Class

System.IO.Path provides cross-platform path manipulation without string concatenation:

  • Path.Combine("folder", "sub", "file.txt") — OS-correct separator
  • Path.GetExtension(path) — ".txt"
  • Path.GetFileName(path) — "file.txt"
  • Path.GetFileNameWithoutExtension(path) — "file"
  • Path.GetDirectoryName(path) — parent directory
  • Path.GetTempPath() — system temp folder

Directory Class and DirectoryInfo

Directory.GetFiles(path, pattern) returns all matching file paths. Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories) searches recursively. Directory.CreateDirectory(path) creates a directory tree if it does not exist.

FileInfo and DirectoryInfo are the object-oriented counterparts — useful when you need to perform multiple operations on the same path since they cache the path and expose properties like Length, LastWriteTime, and Exists.

Code Examples

Write and Read a Text Filecsharp
using System;
using System.IO;

string tempDir  = Path.GetTempPath();
string filePath = Path.Combine(tempDir, "demo.txt");

// Write — creates or overwrites the file
string[] lines =
{
    "Line 1: C# File I/O",
    "Line 2: Using System.IO",
    "Line 3: Cross-platform paths",
};
File.WriteAllLines(filePath, lines);
Console.WriteLine(````````Written to: ${filePath}````````);

// Append a line
File.AppendAllText(filePath, "Line 4: Appended later\n");

// Read entire file as one string
string allText = File.ReadAllText(filePath);
Console.WriteLine("\n--- File contents ---");
Console.Write(allText);

// Read as array of lines
string[] readLines = File.ReadAllLines(filePath);
Console.WriteLine(````````\nTotal lines: ${readLines.Length}````````);

// File metadata via FileInfo
var info = new FileInfo(filePath);
Console.WriteLine(````````File size : ${info.Length} bytes````````);
Console.WriteLine(````````Extension : ${info.Extension}````````);

File.Delete(filePath);
Console.WriteLine("File deleted.");

File.WriteAllLines creates or overwrites; AppendAllText adds without overwriting. FileInfo caches the path and exposes metadata properties without extra File.* calls.

StreamReader Line-by-Line Processingcsharp
using System;
using System.IO;
using System.Text;

string path = Path.Combine(Path.GetTempPath(), "numbers.csv");

// Create a sample CSV file
var sb = new StringBuilder();
sb.AppendLine("Name,Score");
sb.AppendLine("Alice,95");
sb.AppendLine("Bob,82");
sb.AppendLine("Carol,91");
sb.AppendLine("Dave,78");
File.WriteAllText(path, sb.ToString());

// Process line-by-line with StreamReader (efficient for large files)
int lineNum = 0;
double total = 0;
int count  = 0;

using (var reader = new StreamReader(path))
{
    string? line;
    while ((line = reader.ReadLine()) != null)
    {
        lineNum++;
        if (lineNum == 1) { Console.WriteLine(````````Header: ${line}````````); continue; }

        var parts = line.Split(',');
        if (parts.Length == 2 && double.TryParse(parts[1], out double score))
        {
            Console.WriteLine(````````  ${parts[0],-8} ${score}````````);
            total += score;
            count++;
        }
    }
}

Console.WriteLine(````````Average score: ${total / count:F1}````````);
File.Delete(path);

StreamReader.ReadLine() reads one line at a time, keeping memory usage constant regardless of file size. The 'using' statement guarantees the file handle is closed even if an exception occurs mid-processing.

Directory Traversal with Directory.GetFilescsharp
using System;
using System.IO;

// Create a temp directory structure for the demo
string root = Path.Combine(Path.GetTempPath(), "demo_dir");
Directory.CreateDirectory(Path.Combine(root, "src"));
Directory.CreateDirectory(Path.Combine(root, "docs"));

File.WriteAllText(Path.Combine(root, "src",  "Program.cs"),   "// main");
File.WriteAllText(Path.Combine(root, "src",  "Helper.cs"),    "// helper");
File.WriteAllText(Path.Combine(root, "docs", "readme.txt"),   "Read me");
File.WriteAllText(Path.Combine(root, "docs", "notes.txt"),    "Notes");
File.WriteAllText(Path.Combine(root, "build.log"),            "Build ok");

// Find all C# files recursively
string[] csFiles = Directory.GetFiles(root, "*.cs", SearchOption.AllDirectories);
Console.WriteLine(````````C# files found: ${csFiles.Length}````````);
foreach (var f in csFiles)
    Console.WriteLine(````````  ${Path.GetRelativePath(root, f)}````````);

// Find all text files in the docs folder only
string[] txtFiles = Directory.GetFiles(Path.Combine(root, "docs"), "*.txt");
Console.WriteLine(````````\nText files in docs: ${txtFiles.Length}````````);
foreach (var f in txtFiles)
    Console.WriteLine(````````  ${Path.GetFileName(f)}````````);

// List subdirectories
string[] dirs = Directory.GetDirectories(root);
Console.WriteLine(````````\nSubdirectories: ${string.Join(", ", Array.ConvertAll(dirs, Path.GetFileName))}````````);

// Clean up
Directory.Delete(root, recursive: true);
Console.WriteLine("Cleaned up.");

SearchOption.AllDirectories enables recursive search. Path.GetRelativePath produces platform-correct relative paths. Directory.Delete with recursive:true removes a directory tree in one call.

Quick Quiz

1. Which method should you use to read a large text file line-by-line without loading it entirely into memory?

2. What is the primary advantage of using Path.Combine() over string concatenation for file paths?

3. What does Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories) do?

Was this lesson helpful?

PreviousNext