Understanding PipeReader in .NET

Morteza JangjooMorteza Jangjoo
3 min read

When working with high-performance I/O in .NET, the System.IO.Pipelines API is a game changer. At the heart of it lies PipeReader, a key component that allows you to efficiently read streaming data without unnecessary memory allocations or copying.

In .NET 10, PipeReader has received a notable upgrade: JsonSerializer.Deserialize now directly supports it. This removes a common performance bottleneck for developers working with JSON data over pipelines.


What is PipeReader?

PipeReader is the read side of a Pipe in .NET's System.IO.Pipelines API. It’s designed for high-throughput, low-allocation reading of data streams.

A Pipe acts as a bridge between a producer (data source) and a consumer (data processor):

  • Producer writes to a PipeWriter

  • Consumer reads from a PipeReader

This approach:

  • Reduces memory copying

  • Supports async I/O naturally

  • Allows partial reads without blocking

Example – Reading from a PipeReader:

var pipe = new Pipe();

// Producer
_ = Task.Run(async () =>
{
    var writer = pipe.Writer;
    byte[] message = Encoding.UTF8.GetBytes("Hello from Pipe!");
    writer.Write(message);
    await writer.FlushAsync();
    writer.Complete();
});

// Consumer
while (true)
{
    ReadResult result = await pipe.Reader.ReadAsync();
    ReadOnlySequence<byte> buffer = result.Buffer;

    foreach (var segment in buffer)
        Console.Write(Encoding.UTF8.GetString(segment.Span));

    pipe.Reader.AdvanceTo(buffer.End);

    if (result.IsCompleted) break;
}

pipe.Reader.Complete();

The Old Problem with PipeReader and JSON

Before .NET 10, if you wanted to deserialize JSON from a PipeReader, you couldn’t feed it directly into JsonSerializer.
Instead, you had to convert it to a Stream first:

using var stream = pipeReader.AsStream();
var person = await JsonSerializer.DeserializeAsync<Person>(stream);

This extra conversion step:

  • Added overhead (memory allocations, data copying)

  • Made the code less clean


What’s New in .NET 10

In .NET 10, JsonSerializer natively supports PipeReader for deserialization.

Now you can write:

Person? person = await JsonSerializer.DeserializeAsync<Person>(pipeReader);

No conversion, no intermediate Stream, just direct pipeline → object mapping.


Benefits of This Change

  • Performance: Avoids unnecessary copying and allocations

  • Cleaner code: No boilerplate AsStream() calls

  • Better integration: Works naturally with pipeline-based networking code, like Sockets


Real-World Example – Deserializing JSON from a Socket

var pipe = new Pipe();
_ = FillPipeFromSocketAsync(socket, pipe.Writer);

Person? person = await JsonSerializer.DeserializeAsync<Person>(pipe.Reader);
Console.WriteLine($"Hello {person?.Name}!");

Here, FillPipeFromSocketAsync reads from the socket directly into the pipe, and the JsonSerializer processes the stream of bytes as they arrive.


Conclusion

PipeReader has always been a powerful tool for building high-performance data processing in .NET. With .NET 10’s new direct JsonSerializer support, working with JSON in pipelines becomes simpler and faster.

If you’re already using System.IO.Pipelines in networking or file processing, this upgrade can help you eliminate unnecessary steps and gain a performance boost.

I’m Morteza Jangjoo and “Explaining things I wish someone had explained to me”


Tags: .NET, CSharp, Pipelines, JsonSerializer, PipeReader, Performance


0
Subscribe to my newsletter

Read articles from Morteza Jangjoo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Morteza Jangjoo
Morteza Jangjoo