ZstdDotnet 1.5.7.2
dotnet add package ZstdDotnet --version 1.5.7.2
NuGet\Install-Package ZstdDotnet -Version 1.5.7.2
<PackageReference Include="ZstdDotnet" Version="1.5.7.2" />
<PackageVersion Include="ZstdDotnet" Version="1.5.7.2" />
<PackageReference Include="ZstdDotnet" />
paket add ZstdDotnet --version 1.5.7.2
#r "nuget: ZstdDotnet, 1.5.7.2"
#:package ZstdDotnet@1.5.7.2
#addin nuget:?package=ZstdDotnet&version=1.5.7.2
#tool nuget:?package=ZstdDotnet&version=1.5.7.2
ZstdDotnet
ZstdDotnet is a high-performance, streaming-friendly .NET wrapper for the Zstandard (ZSTD) compression library. It builds on the official native libzstd implementation and exposes modern .NET APIs that work seamlessly with Span<byte> and Memory<byte>.
The managed
ZstdDotnetpackage delivers the .NET API surface, whileZstdDotnet.NativeAssetsships the cross-platform native binaries. This README covers both.
Table of contents
- ZstdDotnet
Packages
| Package | Version source | Notes |
|---|---|---|
ZstdDotnet |
<PackageVersion> in src/ZstdDotnet/ZstdDotnet.csproj (must be four-part, e.g. 1.5.7.0) |
Managed compression/decompression API that consumes the native package |
ZstdDotnet.NativeAssets |
<PackageVersion> in src/Zstdotnet.NativeAssets/ZstdDotnet.NativeAssets.csproj |
Bundles libzstd (dll / so) for runtime consumption |
CI workflows read the version directly from the respective project files. Keep the managed package version in four-part
Major.Minor.Patch.Revisionformat.
Features
- Powered by the official C implementation of Zstandard, matching native compression quality and performance.
- Full streaming support: chunked writes/reads and transparent multi-frame decoding.
- True async APIs (
ReadAsync,WriteAsync,FlushAsync,DisposeAsync) with no sync blocking shims. Span<byte>/Memory<byte>overloads to minimize allocations and copies.- Configurable compression level via precise integers or the built-in
CompressionLevelenum (default 5, rangeZstdProperties.MinCompressionLevel..ZstdProperties.MaxCompressionLevel). - Concurrency guards: prevents simultaneous read/write/flush/dispose on the same instance.
- Frame tooling:
ZstdFrameDecoderandZstdFrameInspectorexpose incremental frame metadata and async iteration. - Hardened with 60+ unit tests covering edge cases, fuzz inputs, concurrency, cancellation, pooling, and huge frames.
- Requires native libzstd >= 1.5.0 (the library now exclusively uses the unified
ZSTD_compressStream2()API; legacycompress/flush/endtrio removed internally). - Decoder uses DCtx (modern context); optional
SetMaxWindow(log)to cap memory usage. - Reusable decoder instances via
ZstdDecoderPoolreduce allocation and native context churn. - Optional raw content prefix via
ZstdEncoder.SetPrefix(memory)to boost ratio when many frames share an initial header-like segment.
Installation
<ItemGroup>
<PackageReference Include="ZstdDotnet" Version="1.5.7.1" />
</ItemGroup>
Referencing ZstdDotnet automatically pulls in ZstdDotnet.NativeAssets. At runtime the appropriate native binary is loaded based on the current RID. If you only need the native library, reference ZstdDotnet.NativeAssets directly.
Quick start
// Compress
var data = File.ReadAllBytes("input.bin");
using var outStream = new MemoryStream();
using (var zs = new ZstdStream(outStream, CompressionMode.Compress, leaveOpen: true))
{
int offset = 0;
while (offset < data.Length)
{
int chunk = Math.Min(8192, data.Length - offset);
zs.Write(data, offset, chunk); // or zs.Write(data.AsSpan(offset, chunk));
offset += chunk;
}
}
File.WriteAllBytes("output.zst", outStream.ToArray());
// Decompress
using var compressed = File.OpenRead("output.zst");
using var zsDec = new ZstdStream(compressed, CompressionMode.Decompress);
using var restored = new MemoryStream();
var buffer = new byte[8192];
int read;
while ((read = zsDec.Read(buffer, 0, buffer.Length)) > 0)
{
restored.Write(buffer, 0, read);
}
Asynchronous usage
byte[] payload = GetLargeBuffer();
await using var stream = new MemoryStream();
await using (var encoder = new ZstdStream(stream, CompressionMode.Compress, leaveOpen: true))
{
int offset = 0;
var random = new Random();
while (offset < payload.Length)
{
int chunk = Math.Min(random.Next(1024, 16_384), payload.Length - offset);
await encoder.WriteAsync(payload.AsMemory(offset, chunk));
offset += chunk;
}
await encoder.FlushAsync();
}
stream.Position = 0;
await using (var decoder = new ZstdStream(stream, CompressionMode.Decompress, leaveOpen: true))
{
var scratch = new byte[4096];
int n;
while ((n = await decoder.ReadAsync(scratch)) > 0)
{
// consume bytes
}
}
Span/Memory helpers
- Sync:
Write(ReadOnlySpan<byte>),Read(Span<byte>) - Async:
WriteAsync(ReadOnlyMemory<byte>, CancellationToken),ReadAsync(Memory<byte>, CancellationToken)
See docs/LowLevel.md for incrementally streaming the low-level encoder/decoder, and docs/Advanced.md for advanced tuning guidance.
API at a glance
| Member | Description |
|---|---|
ZstdStream(Stream inner, CompressionMode mode, bool leaveOpen = false) |
Create a compression or decompression stream |
CompressionLevel |
Sets compression level when writing |
Flush() / FlushAsync() |
Drain pending output without finalizing a frame |
Flush(Span<byte>, out int) |
Low-level flush returning an OperationStatus |
FlushFrame() |
Terminates the current frame and starts a new one |
Dispose() / DisposeAsync() |
Finalizes the frame and releases resources |
Reset() |
Reset decoder state and continue with subsequent frames |
ZstdFrameInspector.EnumerateFrames(...) |
Inspect frame metadata without decompressing |
ZstdFrameDecoder.DecodeFramesAsync(...) |
Async frame iterator returning content + metadata |
ZstdProperties.LibraryVersion |
Reports the native library version |
ZstdProperties.ZstdVersion / ZstdProperties.ZstdVersionString |
Alternate strongly typed / string version forms |
ZstdProperties.MaxCompressionLevel |
Reports the maximum available level |
Flush API cheat sheet
| Method | Writes frame terminator? | Continue same frame? | Starts new frame? | Primary use |
|---|---|---|---|---|
Flush() / FlushAsync() |
No | Yes | No | Drain the encoder buffer to lower latency |
Flush(Span<byte>, out int) |
No | Yes | No | Manual buffer control and DestinationTooSmall loops |
FlushFrame() |
Yes | No | Yes | Logical segmentation / multi-frame output |
Dispose() / DisposeAsync() |
Yes | No | No | Finalize the stream |
Span<byte> scratch = stackalloc byte[1024];
while (true)
{
var status = zs.Flush(scratch, out int written);
if (written > 0)
downstream.Write(scratch[..written]);
if (status == OperationStatus.Done)
break;
// status == DestinationTooSmall -> loop again
}
Design & performance notes
- Partial layout:
Streams/ZstdStream.csprovides shared state,enc/anddec/contain compression/decompression logic, andStreams/ZstdStream.Async.*.csadds async entry points. - Buffer management: relies on
ArrayPool<byte>.Sharedto limit GC pressure. - Compression pipeline: repeatedly call
encoder.Compress, using empty input to trigger flushes. - Concurrency guard: CAS on
activeOperationprevents simultaneous operations on a single instance. - Unsafe confinement:
unsafeusage is isolated to interop; public APIs remain safe. - Performance guidance: prefer chunk sizes above 8 KB and avoid sharing a single stream instance across threads.
Example compression level adjustment:
using var zs = new ZstdStream(output, CompressionMode.Compress)
{
CompressionLevel = 10
};
Higher levels cost more CPU—pick the right trade-off for your workload.
Building & testing
pwsh> dotnet test
The suite currently covers flush semantics, async streaming, multi-frame behavior, edge cases, fuzz input, concurrency guards, and frame inspection (49 tests total).
Native assets package
Goals & layout
ZstdDotnet.NativeAssets publishes cross-platform libzstd binaries for consumption by the managed package.
root
├─ src/ZstdDotnet.NativeAssets/ # Packing project
├─ bin/ # Drop native artifacts here
│ ├─ libzstd.dll (win-x64)
│ └─ libzstd.so (linux-x64)
└─ licenses/ # MIT + upstream licenses
Pack the binaries into the NuGet package with:
<None Include="../../bin/libzstd.dll" Pack="true" PackagePath="runtimes/win-x64/native" />
<None Include="../../bin/libzstd.so" Pack="true" PackagePath="runtimes/linux-x64/native" />
Building libzstd
- Clone the upstream source:
git clone https://github.com/facebook/zstd.git cd zstd git checkout v$(xmlstarlet sel -t -v '/Project/PropertyGroup/PackageVersion' ..\src\ZstdDotnet.NativeAssets\ZstdDotnet.NativeAssets.csproj) - Build with CMake (recommended):
Copy the resultingmkdir build cd build cmake -DZSTD_BUILD_SHARED=ON -DZSTD_BUILD_STATIC=OFF -DCMAKE_BUILD_TYPE=Release .. cmake --build . --config Release --target zstdzstd.dll/libzstd.sointo the repositorybin/directory. - On Linux you may also run
make -C build/cmake installto producelibzstd.so.
Optional validation:
dumpbin /exports bin\libzstd.dll | Select-String ZSTD_version
nm -D bin/libzstd.so | grep ZSTD_version
Packing & publishing
dotnet pack src/ZstdDotnet.NativeAssets/ZstdDotnet.NativeAssets.csproj -c Release -o out
dotnet nuget push out/ZstdDotnet.NativeAssets.<version>.nupkg -k <API_KEY> -s https://api.nuget.org/v3/index.json
Workflow Build Native Package:
- Reads
<PackageVersion>fromsrc/ZstdDotnet.NativeAssets/ZstdDotnet.NativeAssets.csproj. - Downloads the matching upstream release, compiles/extracts the native binaries.
- Runs
dotnet packto produce the NuGet package. - Pushes the package to NuGet using the
NUGET_DEPLOY_KEYsecret (skipping duplicates).
Managed package publishing is handled by Publish ZstdDotnet Package:
- Reads
<PackageVersion>fromsrc/ZstdDotnet/ZstdDotnet.csproj(must already be four-part). - Synchronizes the native dependency version inside the same project file.
- Optionally runs tests (toggle via the
skip-testsworkflow input). - Runs
dotnet packand pushes to NuGet with--skip-duplicate.
Upgrade checklist
- Update
<PackageVersion>in bothsrc/ZstdDotnet/ZstdDotnet.csprojandsrc/ZstdDotnet.NativeAssets/ZstdDotnet.NativeAssets.csproj. - Trigger the native and/or managed publishing workflows as appropriate.
- Refresh version numbers in this README and docs if needed.
- Verify the newly published packages on NuGet.org.
Benchmarks
Run the BenchmarkDotNet project in benchmark/ZstdDotnet.Benchmark:
dotnet run -c Release --project benchmark/ZstdDotnet.Benchmark -- --filter *LibraryCompressionBench*
Filter specific cases:
dotnet run -c Release --project benchmark/ZstdDotnet.Benchmark -- --filter *Zstd_Compress_LibCompare_Async*
Find results under BenchmarkDotNet.Artifacts/results/. Use --job Dry for a quick sanity pass.
Additional documentation
- docs/LowLevel.md: Low-level encoder/decoder and static helper examples
- docs/Advanced.md: Advanced parameters, dictionary roadmap, tuning hints
- docs/FAQ.md: Frequently asked questions
- docs/Benchmark.md: Extended benchmarking guide
- docs/TestMatrix.md: Test trait reference
License
Distributed under the MIT License. Upstream Zstandard library code used here (only the lib directory, built as a dynamically linked native library) is under the 3-clause BSD license. See licenses/THIRD-PARTY-NOTICES.txt for architectural usage notes and licenses/zstdlicense.txt for the full upstream BSD text.
Acknowledgements
- Facebook/Meta Zstandard maintainers for the reference implementation
- The .NET team and community for Span/Stream innovations
Questions or ideas? Open an issue or pull request.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net6.0
- ZstdDotnet.NativeAssets (>= 1.5.7.2)
-
net8.0
- ZstdDotnet.NativeAssets (>= 1.5.7.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.