.NET 11 compression Reset lets a service reuse a streamless GZip, ZLib, or Deflate encoder or decoder for a new, independent payload. Call Reset() only at the boundary between operations, after the previous result has been completely finalized and checked. If an operation is abandoned, reset deliberately discards its pending state; it does not finish that payload for you.

The practical payoff of .NET 11 compression Reset is avoiding repeated codec construction in a hot path, not a promise that every workload gets faster. You still need a separate output buffer, a bound on decompression, and exclusive ownership of each mutable codec instance. The example below uses GZip; the same lifecycle applies to the new Reset() methods on DeflateEncoder, DeflateDecoder, ZLibEncoder, and ZLibDecoder.

Reuse a GZip codec across independent payloads

Target a .NET 11 SDK and runtime for this API. The six streamless zlib-based types were introduced in .NET 11, and the RC1 change gives each an instance Reset() method. Do not assume that upgrading only a build machine makes the method available to a service still running an older target and runtime.

This deliberately small C# example owns one encoder and one decoder for a sequential batch. It resets before each payload, finalizes each compressed member, and checks both the operation status and consumption count. It allocates returned byte arrays for clarity; codec reuse and zero-allocation payload processing are different claims.

using System.Buffers;
using System.IO.Compression;
using System.Text;

using var encoder = new GZipEncoder();
using var decoder = new GZipDecoder();

foreach (string message in new[] { "first payload", "another payload" })
{
    byte[] source = Encoding.UTF8.GetBytes(message);
    byte[] compressed = EncodeOne(encoder, source);
    byte[] restored = DecodeOne(decoder, compressed, maxOutputBytes: 1024);

    if (!source.AsSpan().SequenceEqual(restored))
        throw new InvalidOperationException("Independent GZip round trip failed.");
}

static byte[] EncodeOne(GZipEncoder encoder, ReadOnlySpan<byte> source)
{
    encoder.Reset(); // Discard state from any previous operation.
    int capacity = checked((int)GZipEncoder.GetMaxCompressedLength(source.Length));
    byte[] destination = new byte[capacity];

    OperationStatus status = encoder.Compress(
        source, destination, out int consumed, out int written,
        isFinalBlock: true);

    if (status != OperationStatus.Done || consumed != source.Length)
        throw new InvalidOperationException($"GZip encode incomplete: {status}.");

    return destination.AsSpan(0, written).ToArray();
}

static byte[] DecodeOne(
    GZipDecoder decoder, ReadOnlySpan<byte> compressed, int maxOutputBytes)
{
    if (maxOutputBytes < 1)
        throw new ArgumentOutOfRangeException(nameof(maxOutputBytes));

    decoder.Reset(); // A fresh member must not inherit prior history.
    byte[] destination = new byte[maxOutputBytes];
    OperationStatus status = decoder.Decompress(
        compressed, destination, out int consumed, out int written);

    if (status != OperationStatus.Done || consumed != compressed.Length)
        throw new InvalidDataException(
            $"GZip payload incomplete, invalid, or over the output cap: {status}.");

    return destination.AsSpan(0, written).ToArray();
}

GetMaxCompressedLength supplies a destination bound for the one-shot encoder call. A real service should reject oversized input and arithmetic overflow before allocating, and it should choose a decompressed-output cap from its message contract rather than trust a claimed size inside untrusted input. The decoder’s DestinationTooSmall result is a policy failure here, not a request to grow without limit. A truncated member can return NeedMoreData; invalid input can return InvalidData. Neither result should be treated as a successful payload.

The example rejects trailing bytes by requiring consumed == compressed.Length. That is appropriate when each application message is exactly one GZip member. If your protocol deliberately permits concatenated members or framing bytes, parse that protocol explicitly instead of silently accepting a prefix. The same principle applies to the encoder: isFinalBlock: true is necessary to produce a complete independent member. A synchronous flush is not a substitute for finishing it.

Understand .NET 11 compression Reset boundaries

The .NET 11 RC1 libraries notes list Reset() for all six Deflate, GZip, and ZLib encoder/decoder types. The merged runtime change describes a return to the initial state for another independent operation, including reset after partial work. For an encoder, quality and window configuration chosen at construction are retained, while unflushed input and pending output from an unfinished operation are discarded. For a decoder, previous sliding-window history is discarded. A disposed object cannot be reset.

That boundary is easy to misuse. If a write fails halfway through and you call Reset(), the bytes already sent are still an incomplete member in the external sink. Reset repairs the in-memory codec state for a new message; it does not make that earlier message valid or roll back a partially written response, file, or queue record. Mark the old operation failed, discard or isolate its destination, and use a fresh output frame for the next one.

Likewise, a decoder should not be reset in the middle of a valid multi-chunk member merely because the current source span ran out. Continue feeding that member until the decoder reports completion, or reject it as truncated according to your protocol. Reset only when you intentionally start a distinct member. Do not confuse Flush(), which emits pending compressed data during an ongoing operation, with Reset(), which abandons the old state.

Verify round trips and abandoned operations

Run the sample against the exact .NET 11 build you intend to deploy. Then extend the check with several independent payloads: empty input, a small UTF-8 message, a larger incompressible buffer, and a payload larger than a single destination chunk if your production code loops over spans. Compare restored bytes with original bytes, not just decompressed lengths. Repeat the sequence with GZip, ZLib, and Deflate separately; their formats are not interchangeable just because the reset lifecycle looks similar.

Test an abandoned encode separately. Begin a non-final compression into a bounded buffer, intentionally stop, call Reset(), and then finalize a different payload into a new buffer. Decode only the second result with a fresh or reset decoder and assert exact byte equality. Also start a partial decode, reset, and decode a different complete member. These tests demonstrate that the new member does not inherit the unfinished operation; they do not claim that the abandoned output was valid.

Negative tests matter just as much. Corrupt the GZip trailer or truncate the member and require failure, set the output cap below the expected plain size and require failure, and append trailing bytes to confirm the one-member contract rejects them. Test a call after disposal and a mixed-version deployment in which an old runtime is still present. The runtime’s shared encoder/decoder tests cover reset after partial work; your application tests must additionally prove your own framing, limits, and failure handling.

Keep ownership and resource limits explicit

Each instance holds mutable compression state. The sample’s encoder and decoder are scoped to one sequential batch and disposed at the end. Do not put one instance in a singleton and let concurrent requests call Compress, Decompress, and Reset on it. Use per-request ownership first. If measurement later justifies a pool, lease exclusively, reset at the next operation boundary, and dispose objects that encountered an unexpected error rather than returning a questionable instance to circulation.

Protect the surrounding resource budget independently. Enforce maximum compressed input size, decompressed output size, per-request CPU/time limits where appropriate, and a sensible maximum number of concurrently leased codecs. A small compressed message can expand dramatically. The fixed output-cap example chooses fail-closed behavior, but it allocates the full cap per call; for larger messages, use bounded incremental buffers or a streaming design and keep the same total limit. An array pool can reduce output-buffer churn, provided rented arrays are always returned and their contents are not exposed across tenants.

Constructor choices remain attached to the reusable encoder. If a request needs a different quality, window, or strategy, construct or lease an instance configured for that choice; Reset() is not a reconfiguration API. Keep format-specific pools separate, and never use a GZip decoder for a ZLib member. This is particularly important when a service negotiates a wire format per client.

Measure before pooling

The official .NET 11 compression Reset change makes reuse possible without constructing a replacement codec each time. It does not establish a universal end-to-end throughput gain. Benchmark your representative payload sizes and concurrency with the actual .NET 11 runtime: one codec per operation versus exclusive reuse, including allocation rate, latency distribution, CPU, and memory under contention. Keep input data and compression settings identical, verify output bytes, warm both paths, and separate codec-construction savings from payload-buffer allocations.

Start with the simplest safe ownership model. A pool adds contention, capacity tuning, cleanup on exceptions, and shutdown disposal. If construction is a negligible share of the workload or pooling harms tail latency, keep per-operation instances. If the measured saving is material, add a bounded pool with one borrower at a time and rerun the negative and concurrency tests. Roll back by switching to per-operation construction, not by sharing a single mutable codec across threads.

Adoption checklist

  • Target and deploy a .NET 11 build containing the streamless codec APIs and Reset().
  • Define one message/member boundary; finish and check the prior result before resetting for another payload.
  • Reject incomplete status, unconsumed input, corrupt data, trailing bytes where prohibited, and output beyond a fixed application limit.
  • Use one mutable codec per owner at a time; dispose it on shutdown or after uncertain failure.
  • Run independent, partial-abandon, truncated-input, expansion-limit, and concurrency tests for the formats you actually use.
  • Measure production-like traffic before adding a pool; retain a straightforward per-operation fallback.

References

Found this useful? Support more practical developer content.

Author

Practical .NET, Angular, Azure, Blazor, and AI engineering for real-world development.

Write A Comment

Ads Blocker Image Powered by Code Help Pro

Ads Blocker Detected!!!

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

Powered By
100% Free SEO Tools - Tool Kits PRO