Skip to main content

Command Palette

Search for a command to run...

Understanding PipeReader in .NET

Understanding PipeReader in .NET and the New .NET 10 JsonSerializer Support

Published
•3 min read•View as Markdown
Understanding PipeReader in .NET
M

Morteza Jangjoo, Senior .NET Backend Developer with 15+ years of experience in C#, ASP.NET Core, SQL Server, and Microservices. Skilled in building scalable, high-performance systems.

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