Recursive AI

LZ4 compression and decompression in C#

OneArchive is a free, open-source archive library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

LZ4 is a fast, lossless compression format. The codec ships in the RecursiveAI.OneCompress package (part of OneArchive). A frame is a complete file wrapper: compressed bytes with a header and end marker.

Terminal
dotnet add package RecursiveAI.OneCompress

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using RecursiveAI.OneCompress.Lz4;
 
byte[] frame = Lz4.Compress("hello LZ4"u8);
File.WriteAllBytes("message.lz4", frame);
byte[] restored = Lz4.Decompress(
    File.ReadAllBytes("message.lz4"));
Console.WriteLine(
    System.Text.Encoding.UTF8.GetString(restored));

Writes message.lz4 and prints:

Text
hello LZ4

Unzip the example, then dotnet run.

The full program

No input files are needed. The program builds 100 copies of hello LZ4 followed by a newline, compresses those UTF-8 bytes, reads the saved frame, and writes the decoded bytes to restored.txt.

C#
using System.Text;
using RecursiveAI.OneCompress.Lz4;
 
byte[] source = Encoding.UTF8.GetBytes(
    string.Concat(Enumerable.Repeat("hello LZ4\n", 100)));
byte[] frame = Lz4.Compress(source);
File.WriteAllBytes("message.lz4", frame);
byte[] restored = Lz4.Decompress(
    File.ReadAllBytes("message.lz4"));
File.WriteAllBytes("restored.txt", restored);
Console.WriteLine($"Input: {source.Length} bytes");
Console.WriteLine($"Frame: {frame.Length} bytes");
Console.WriteLine($"Restored: {restored.Length} bytes");
bool equal = source.AsSpan().SequenceEqual(restored);
Console.WriteLine($"Equal: {equal}");
Text
Input: 1000 bytes
Frame: 50 bytes
Restored: 1000 bytes
Equal: True

A truncated frame throws CompressionFormatException; because the program writes restored.txt only after decompression returns, that failure does not write a partial restored file.

Variations

Choose high compression and block checksums. This complete program uses level 9. Content checksums are already enabled by default; the extra option adds a checksum for each block.

C#
using RecursiveAI.OneCompress.Lz4;
 
byte[] source = System.Text.Encoding.UTF8.GetBytes(
    string.Concat(Enumerable.Repeat("hello LZ4\n", 100)));
var options = new Lz4CompressionOptions
{
    Level = 9,
    BlockChecksum = true
};
byte[] frame = Lz4.Compress(source, options);
byte[] restored = Lz4.Decompress(frame);
Console.WriteLine(source.AsSpan().SequenceEqual(restored));
Text
True

Catch truncated input. This deliberately removes the final byte of a frame.

C#
using RecursiveAI.OneCompress;
using RecursiveAI.OneCompress.Lz4;
 
byte[] frame = Lz4.Compress("hello LZ4"u8);
try
{
    Lz4.Decompress(frame.AsSpan(0, frame.Length - 1));
}
catch (CompressionFormatException error)
{
    Console.WriteLine(error.GetType().Name);
}
Text
CompressionFormatException

The short answer

For LZ4 compression in C#, install RecursiveAI.OneCompress, import RecursiveAI.OneCompress.Lz4, and call Lz4.Compress with your bytes. It returns a complete frame. Save those bytes directly as an .lz4 file. Read the file and pass its bytes to Lz4.Decompress to recover the original content.

The compression codecs are part of OneArchive but ship in the separate OneCompress package. This task does not require the archive package or an archive entry API. The example uses the released .NET 10 package and operates on byte arrays, not filenames inside a container.

What is LZ4 compression?

LZ4 is lossless compression: decoding restores the input bytes rather than an approximation. Its format documentation distinguishes the raw block format from the frame format used to organize blocks into files and streams. That distinction determines which API you should call.

In OneCompress, Lz4.Compress and Lz4.Decompress are the frame pair. Lz4Block.Compress and Lz4Block.Decompress handle raw blocks instead. A raw block needs its compressed length and an output bound supplied outside the block. A frame carries a descriptor, block boundaries and an end marker. It can also carry content size and checksums. See the decoder documentation for the accepted frame variants.

Do not send a raw block to the frame decoder or strip off a frame header and assume the remainder is one raw block. A frame may contain several blocks, block-size fields, checksums and an end marker. Match the API to the format your producer actually writes, not just a filename extension.

How much does LZ4 compress?

There is no fixed ratio. The complete program’s repeated text becomes a 50-byte frame from 1,000 input bytes; that is one small, deliberately repetitive example, not an estimate for photos, encrypted bytes or your application’s records. Compare the frame length with the original byte length on representative inputs. Include the framing overhead when deciding whether to store the compressed representation.

The encoder documentation defines level 1 as the default and levels 2 through 12 as high compression. The level controls how the encoder searches for matches; it does not change the need for a matching frame decoder. The level-9 variation shows where that option goes without assuming it will shrink every input. A small or already-compressed payload can cost more bytes to frame than it saves.

Is LZ4 compression good?

It fits a byte round-trip when both ends agree on the LZ4 format. For a portable .lz4 payload, choose frames rather than an application-specific raw-block wrapper. For several named files, an archive is a separate requirement: this example stores one byte sequence, not a directory, filenames or timestamps.

The one-call APIs allocate byte arrays for their complete results. OneCompress also exposes Lz4Stream for stream-based use and Lz4Encoder and Lz4Decoder for incremental buffers. Choose those when retaining both complete arrays is not appropriate. The decoder’s options default to a 1 GiB maximum total output and checksum verification enabled; a limit is not a promise that an allocation of that size is appropriate for your application.

For malformed input, catch the format exception as shown above. Keep the write after successful decoding if the destination must not receive partial output. The example’s final equality check compares the restored bytes with the original input, independently of how the text happens to display in a console.

All onearchive recipes