File I/O
Reading and writing files, JSON serialization, using statement
Introduction
In this lesson, you'll learn about file i/o in C#. Coming from JavaScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In JavaScript, you're familiar with reading and writing files, json serialization, using statement.
C# has its own approach to reading and writing files, json serialization, using statement, which we'll explore step by step.
The C# Way
Let's see how C# handles this concept. Here's a typical example:
using System.IO;
using System.Text.Json;
// Simple read/write (File class)
string text = File.ReadAllText("data.txt");
File.WriteAllText("out.txt", "Hello!");
File.AppendAllText("log.txt", "entry\n");
// Line-by-line (StreamReader)
using var reader = new StreamReader("data.txt");
while (!reader.EndOfStream)
{
string? line = reader.ReadLine();
Console.WriteLine(line);
}
// using ensures Dispose() is called — like JS finally{}
using var writer = new StreamWriter("out.txt");
writer.WriteLine("Hello!");
// JSON (System.Text.Json)
var obj = JsonSerializer.Deserialize<MyClass>(
File.ReadAllText("data.json"));
File.WriteAllText("out.json",
JsonSerializer.Serialize(obj, new JsonSerializerOptions
{ WriteIndented = true }));
// Path
string full = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data","file.txt");
Path.GetDirectoryName(full);
Path.GetExtension(full); // ".txt"Comparing to JavaScript
Here's how you might have written similar code in JavaScript:
import fs from "fs";
import path from "path";
// Sync read/write
const text = fs.readFileSync("data.txt", "utf-8");
fs.writeFileSync("out.txt", "Hello!");
fs.appendFileSync("log.txt", "entry\n");
// Async (recommended)
const text2 = await fs.promises.readFile("data.txt","utf-8");
await fs.promises.writeFile("out.txt", "data");
// JSON
const obj = JSON.parse(fs.readFileSync("data.json","utf-8"));
fs.writeFileSync("out.json", JSON.stringify(obj, null, 2));
// Path
const full = path.join(__dirname, "data", "file.txt");
path.dirname(full);
path.extname(full); // ".txt"You may be used to different syntax or behavior.
File.ReadAllText/WriteAllText are the simplest one-liner equivalents
You may be used to different syntax or behavior.
using statement calls Dispose() at end of block — equivalent to finally{stream.close()}
You may be used to different syntax or behavior.
StreamReader/StreamWriter for large files (streaming); File class for small files
You may be used to different syntax or behavior.
System.Text.Json is the built-in JSON library; Newtonsoft.Json is a popular alternative
You may be used to different syntax or behavior.
Path.Combine() replaces path.join() — handles OS path separators automatically
Step-by-Step Breakdown
1. File Class (Simple)
For small files, File.ReadAllText and File.WriteAllText are the simplest approach — synchronous and one-liner.
fs.readFileSync("f","utf-8")
fs.writeFileSync("f","data")File.ReadAllText("f")
File.WriteAllText("f","data")
File.ReadAllLines("f") // string[]2. using Statement
The using statement automatically calls Dispose() when the block exits. StreamReader/Writer implement IDisposable.
const f = fs.createReadStream("file");
try { ... } finally { f.close(); }using var reader = new StreamReader("file");
// reader.Dispose() called automatically at }3. JSON Serialization
System.Text.Json is built into .NET. Use JsonSerializer.Deserialize<T> and Serialize. Add [JsonPropertyName] for custom field names.
JSON.parse(text)
JSON.stringify(obj, null, 2)var obj = JsonSerializer.Deserialize<Config>(text);
string json = JsonSerializer.Serialize(obj,
new JsonSerializerOptions { WriteIndented = true });4. Path Operations
Path.Combine handles OS-specific separators. Use Path.GetExtension, GetFileName, GetDirectoryName for path parts.
path.join(dir, "sub", "file.txt")
path.extname("file.txt")Path.Combine(dir, "sub", "file.txt")
Path.GetExtension("file.txt") // ".txt"
Path.GetFileName("dir/file.txt") // "file.txt"Common Mistakes
When coming from JavaScript, developers often make these mistakes:
- File.ReadAllText/WriteAllText are the simplest one-liner equivalents
- using statement calls Dispose() at end of block — equivalent to finally{stream.close()}
- StreamReader/StreamWriter for large files (streaming); File class for small files
Key Takeaways
- File.ReadAllText/WriteAllText for simple cases; StreamReader/Writer for large/streaming files
- using statement auto-disposes streams — always use it with IO objects
- System.Text.Json for JSON: Deserialize<T>() and Serialize() with JsonSerializerOptions
- Path.Combine() for cross-platform paths; GetExtension/GetFileName for path parts