Getting started
Install
Add the package. Its one library dependency, the shared Bodu.Text.Serialization package (the attribute family, naming policies, and callback interfaces), is restored transitively - there is nothing else to add.
dotnet add package Bodu.Text.Bencode
It targets net8.0.
A first Bencode round trip
using Bodu.Text.Bencode;
public sealed class FileEntry
{
public string Name { get; set; } = "";
public long Length { get; set; }
}
byte[] payload = BencodeSerializer.Serialize(new FileEntry { Name = "ubuntu.iso", Length = 1024 });
// d6:Lengthi1024e4:Name10:ubuntu.isoe (dictionary keys in canonical order)
FileEntry entry = BencodeSerializer.Deserialize<FileEntry>(payload);
Rename members
using Bodu.Text.Bencode;
using Bodu.Text.Serialization;
var options = new BencodeSerializerOptions
{
PropertyNamingPolicy = NamingPolicy.SnakeCaseLower,
};
// "Name" is written as "name", "Length" as "length".
byte[] payload = BencodeSerializer.Serialize(entry, options);
Or pin a single member's name with [PropertyName], which always wins over the policy.
Edit a document without a model
When you need to change a value but do not want a POCO, parse to the mutable DOM:
using Bodu.Text.Bencode.Nodes;
BencodeNode node = BencodeNode.Parse(payload)!;
node["Length"] = 2048;
byte[] back = node.ToByteArray();
Read a document without a model
For inspection only, the read-only DOM is the lighter choice - a low-allocation view over the parsed buffer, walked through RootElement:
using Bodu.Text.Bencode.Document;
using BencodeDocument doc = BencodeDocument.Parse(payload);
BencodeElement info = doc.RootElement.GetProperty("info");
string name = info.GetProperty("name").GetString(); // "ubuntu.iso"
long length = info.GetProperty("piece length").GetInt64(); // 262144
BencodeDocument is disposable - wrap it in using and copy out any values that must outlive it, since disposal returns its pooled buffer.
Bridge a model to a DOM without re-encoding
When you have a model but want to inspect or edit its shape before writing bytes, the serializer projects it straight into either DOM - no intermediate byte[] round trip - and binds back from a node tree:
using Bodu.Text.Bencode;
using Bodu.Text.Bencode.Nodes;
BencodeNode? node = BencodeSerializer.SerializeToNode(entry); // model → mutable tree
node!["Length"] = 4096; // edit in place
FileEntry edited = BencodeSerializer.Deserialize<FileEntry>(node!); // node → model
SerializeToDocument produces the read-only BencodeDocument for inspection instead; like any deserialized document it is caller-owned and must be disposed.
Round-trip through a Stream
The serializer reads and writes Stream directly, with async variants, so a payload never has to materialize as a byte[] first:
using Bodu.Text.Bencode;
await using (FileStream stream = File.Create("ubuntu.torrent"))
{
await BencodeSerializer.SerializeAsync(stream, torrent);
}
await using (FileStream stream = File.OpenRead("ubuntu.torrent"))
{
Torrent loaded = await BencodeSerializer.DeserializeAsync<Torrent>(stream);
}
The synchronous Serialize(Stream, …) / Deserialize<T>(Stream, …) overloads have the same shape without the token.
When something goes wrong
Failures split into two exception types, so you can tell bad input apart from wrong type:
- A malformed document - bytes the grammar rejects - raises BencodeFormatException, which carries the byte
Offsetwhere parsing failed. - A document that parses but cannot bind to your type - a type mismatch, a missing required member, a value the format cannot represent - raises BencodeSerializationException.
try
{
FileEntry loaded = BencodeSerializer.Deserialize<FileEntry>(payload);
}
catch (BencodeFormatException ex)
{
Console.Error.WriteLine($"Malformed Bencode at byte {ex.Offset}: {ex.Message}");
}
catch (BencodeSerializationException ex)
{
Console.Error.WriteLine($"Document does not match FileEntry: {ex.Message}");
}
Where to go next
- Bodu.Text.Bencode introduction - what is specific to Bencode: byte strings, canonical output, the kinds it cannot represent.
- Core concepts - the serializer, converter model, both DOMs, and the reader/writer seam.
- Using Bencode - byte strings, canonical ordering, the DOMs, and unsupported kinds.
- Writing converters - custom shapes with
BencodeConverter<T>. - Text & Serialization topic overview - where the serializers sit among the codecs and document formats.