Mastering I/O in C# 14 / .NET 10

A Complete Guide to JSON, Text Files, and Stream Handling in Modern .NET

Input/Output (I/O) operations are the backbone of almost every application, web APIs, games, desktop tools, microservices, IoT devices, analytics engines, and backend systems all rely on efficient reading and writing of data.

While I/O used to be a source of frustration in .NET – complex buffering, verbose file APIs, blocking calls etc, modern C# has transformed it into a clean, intuitive, high-performance ecosystem.
C# 14 and .NET 10 continue this transformation with optimised async pipelines, System.Text.Json improvements, smarter buffering, better stream abstractions, and unified patterns across file I/O.

This tutorial explores the three core pillars of modern .NET I/O:

  1. JSON Handling (fast, efficient, zero-allocation parsing where possible)
  2. Text File Processing (async, safe, memory-aware API usage)
  3. Streams (the foundation of all I/O – networking, files, compression, pipelines)

By the end of this tutorial, you’ll understand how to write I/O code that is:

  • Fast
  • Safe
  • Memory-efficient
  • Modern and idiomatic
  • Easy to maintain
  • Leverages .NET 10 improvements

Let’s go deep.


🔥 1. JSON I/O in Modern .NET

Fast, Low-Allocation, High-Throughput JSON with System.Text.Json

JSON is the dominant interchange format for APIs and web services. In earlier .NET versions we used Newtonsoft.Json, but modern .NET ships with System.Text.Json, built for:

  • Extreme performance
  • Low allocations
  • Source-generated serialization
  • Built-in UTF-8 support
  • Streaming access
  • Fully async APIs

⭐ Recommended Approach for .NET 10

Use JsonSerializer for standard scenarios and Utf8JsonReader/Writer for high-performance streaming.


Serialize an object to JSON (async)

var person = new Person("James", 58);

await using var stream = File.Create("person.json");
await JsonSerializer.SerializeAsync(stream, person);

You avoid storing the JSON in memory – the object is written directly to the file stream.


Deserialize JSON from a file (async)

await using var stream = File.OpenRead("person.json");
var person = await JsonSerializer.DeserializeAsync<Person>(stream);

Efficient, UTF-8 native, and avoids buffering the entire file into memory.


Pretty-printing JSON

var options = new JsonSerializerOptions { WriteIndented = true };

await using var stream = File.Create("pretty.json");
await JsonSerializer.SerializeAsync(stream, person, options);

Great for config files, logs, or user-visible JSON.


High-performance streaming JSON reading

When dealing with:

  • massive JSON files
  • continuous JSON feeds
  • IoT telemetry
  • log ingestion
  • real-time pipelines

Use Utf8JsonReader.

await using var stream = File.OpenRead("data.json");
var buffer = new byte[4096];
var json = new Utf8JsonReader(buffer);

int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer)) > 0)
{
    json = new Utf8JsonReader(buffer.AsSpan(0, bytesRead), json.IsFinalBlock, json.CurrentState);

    while (json.Read())
    {
        // Process tokens
    }
}

This is zero-alloc, span-based, and stream-friendly — ideal for large-scale performance-critical systems.


Source-generated JSON (maximum performance)

[JsonSerializable(typeof(Person))]
public partial class PersonContext : JsonSerializerContext { }

Then:

await JsonSerializer.SerializeAsync(stream, person, PersonContext.Default.Person);

Faster startup, fewer allocations, tight AOT compatibility.


📄 2. Text File I/O in .NET 10

Async, safe, UTF-8 by default, and file-system friendly

Text is the simplest form of I/O – logs, config files, CSVs, templates, scripts.

C# 14 continues the pattern of providing clean, async-first APIs that are easy to use and secure by default.


Write all text (async)

await File.WriteAllTextAsync("log.txt", "Hello World");

Simplest and safest.


Append text

await File.AppendAllTextAsync("log.txt", "New entry\n");

Great for log files.


Read all text (async)

string content = await File.ReadAllTextAsync("config.txt");

Line-by-line reading (streaming)

Best for:

  • huge files
  • CSVs
  • log files
  • memory-sensitive scenarios
using var reader = File.OpenText("bigfile.txt");

string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
    Console.WriteLine(line);
}

Efficient writing using StreamWriter

await using var stream = File.Create("output.txt");
await using var writer = new StreamWriter(stream);

await writer.WriteLineAsync("Line 1");
await writer.WriteLineAsync("Line 2");

Uses internal buffering for high throughput.


🔄 3. Streams — The Heart of Modern I/O

Everything in .NET is a stream – files, network sockets, pipes, memory buffers.

Streams provide:

  • buffering
  • async read/write
  • pipelines
  • composition (e.g., GZipStream → FileStream → NetworkStream)
  • cancellation support

Reading bytes from a stream

await using var fs = File.OpenRead("image.png");

byte[] buffer = new byte[8192];
int bytesRead;

while ((bytesRead = await fs.ReadAsync(buffer)) > 0)
{
    // process buffer
}

Writing bytes to a stream

await using var fs = File.Create("copy.png");

await fs.WriteAsync(data);

Chaining streams – compression example

await using var fs = File.Create("compressed.gz");
await using var gzip = new GZipStream(fs, CompressionLevel.SmallestSize);

await gzip.WriteAsync(data);

MemoryStream for in-memory I/O

using var ms = new MemoryStream();
await JsonSerializer.SerializeAsync(ms, person);
byte[] bytes = ms.ToArray();

Used extensively in testing, APIs, and blob generation.


NetworkStream example

(Works with TCP clients, WebSockets, WebSocketStream, etc.)

await using var ns = tcpClient.GetStream();

byte[] buffer = Encoding.UTF8.GetBytes("Hello");
await ns.WriteAsync(buffer);

🧱 Best Practices for Modern I/O

1. Always use async I/O

Prevents thread starvation and scales better.


2. Prefer UTF-8 everywhere

.NET 10 uses UTF-8 as the default encoding in many APIs.


3. Avoid blocking I/O on the main/UI thread

No .Result
No .GetAwaiter().GetResult()


4. Reuse buffers for performance

Use:

  • ArrayPool<byte>.Shared
  • spans
  • pooled writers

5. Use streaming for large files

Never do:

File.ReadAllBytes("10GBfile.dat");

6. Prefer System.Text.Json over Newtonsoft.Json

Unless:

  • you need advanced polymorphic handling
  • you consume external JSON that breaks STJ rules

7. Use await using

Ensures early flush + disposal.


🧠 Summary Table

ScenarioAPINotes
Serialize JSON to fileJsonSerializer.SerializeAsyncFast, UTF-8, async
Read JSON fileDeserializeAsyncAutomatically infers types
Pretty JSONJsonSerializerOptions { WriteIndented = true }Human-friendly
Read text fileFile.ReadAllTextAsyncSimple
Stream linesStreamReader.ReadLineAsyncBest for large files
Write textFile.WriteAllTextAsyncEasy & safe
File streamingFileStreamReal-time processing
CompressionGZipStreamChained streams
High-performance JSONUtf8JsonReaderZero allocation

Final Thoughts — The Modern .NET I/O Mindset

Modern C# I/O isn’t just a collection of APIs — it’s a unified, async-first, streaming-first design philosophy.
.NET 10 and C# 14 make JSON faster, text handling simpler, and stream handling more consistent and composable than ever.

If you master:

  • JsonSerializer
  • async file APIs
  • streaming with FileStream / NetworkStream
  • Utf8JsonReader for high performance

…you’ve mastered the backbone of real-world .NET engineering.