Skip to content

.NET Formatters & Types

Core types and CBOR serialization primitives provided by the ion.runtime package.

IonFormatter

IonFormatter is a static class providing low-level CBOR serialization for all primitives:

// Signed integers
IonFormatter.WriteSByte(writer, value);   // i1
IonFormatter.WriteInt16(writer, value);   // i2
IonFormatter.WriteInt32(writer, value);   // i4
IonFormatter.WriteInt64(writer, value);   // i8
IonFormatter.WriteInt128(writer, value);  // i16

// Unsigned integers
IonFormatter.WriteByte(writer, value);    // u1
IonFormatter.WriteUInt16(writer, value);  // u2
IonFormatter.WriteUInt32(writer, value);  // u4
IonFormatter.WriteUInt64(writer, value);  // u8
IonFormatter.WriteUInt128(writer, value); // u16

// Floats
IonFormatter.WriteHalf(writer, value);    // f2
IonFormatter.WriteFloat(writer, value);   // f4
IonFormatter.WriteDouble(writer, value);  // f8

// Special types
IonFormatter.WriteString(writer, value);
IonFormatter.WriteGuid(writer, value);
IonFormatter.WriteDateTime(writer, value);
IonFormatter.WriteBool(writer, value);

IonMaybe<T>

A Rust-style Option monad for nullable values. Generated for T? fields:

// Implicit conversions
IonMaybe<string> email = "user@example.com";
IonMaybe<string> empty = default;

// Pattern matching
if (email.HasValue)
    Console.WriteLine(email.Value);

// LINQ-style operations
var upper = email.Map(e => e.ToUpper());
var fallback = empty.Or("default@example.com");

// Wire format: CBOR null for None, CBOR value for Some

IonPartial<T>

Tracks field modifications for sparse (PATCH-like) updates. Only modified fields are serialized:

var patch = new IonPartial<User>();

// Set specific fields
patch.Set(u => u.name, "New Name");
patch.Set(u => u.email, "new@example.com");

// Check if a field was modified
bool modified = patch.IsSet(u => u.name);  // true
bool untouched = patch.IsSet(u => u.id);  // false

// On the wire: only 'name' and 'email' are serialized
await userService.UpdateUser(42, patch);

IonBytes

Zero-copy byte buffer type for efficient binary data transfer:

// From byte array
IonBytes data = new byte[] { 0x01, 0x02, 0x03 };

// From ReadOnlyMemory
IonBytes data2 = new IonBytes(memory);

// Access the underlying data
ReadOnlySpan<byte> span = data.Span;

// Wire format: CBOR byte string

IIonUnion<T>

Marker interface for discriminated union types. Each union case is a sealed class implementing this interface:

// Generated from: union Shape { Circle(r: f4), Rectangle(w: f4, h: f4) }
public interface Shape : IIonUnion<Shape> { }

// Pattern match with switch
Shape shape = GetShape();
var area = shape switch
{
    Shape.Circle c    => Math.PI * c.r * c.r,
    Shape.Rectangle r => r.w * r.h,
    _ => throw new NotSupportedException()
};

IonFormatterStorage<T>

Type-safe formatter registry. Generated code registers formatters here at module initialization:

// Generated in moduleInit.cs
IonFormatterStorage<User>.Serialize = (writer, value) =>
{
    writer.WriteArrayHeader(3);
    IonFormatter.WriteUInt32(writer, value.id);
    IonFormatter.WriteString(writer, value.name);
    IonFormatter.WriteString(writer, value.email);
};

IonFormatterStorage<User>.Deserialize = (reader) =>
{
    reader.ReadArrayHeader();
    return new User
    {
        id = IonFormatter.ReadUInt32(reader),
        name = IonFormatter.ReadString(reader),
        email = IonFormatter.ReadString(reader)
    };
};