9.7 KiB
AcBinary Configuration
Configuration options, presets, and option interactions for AcBinarySerializerOptions. For wire format see BINARY_FORMAT.md. For features (interning, ref tracking, property ordering) see BINARY_FEATURES.md.
WireMode
| Value | Integers | Strings | Output size | Speed |
|---|---|---|---|---|
Compact (default) |
VarInt/VarUInt (1–5 bytes) | UTF-8 with speculative ASCII fast path | Smaller | Slightly slower |
Fast |
Fixed-width raw bytes (4/8 bytes) | UTF-16 memcpy (charCount * 2 bytes) |
Larger | Fastest encode/decode |
Format difference for strings:
- Compact:
[VarUInt byteLength] [UTF-8 bytes]— speculative ASCII (1 pass if all ASCII, rewind+UTF-8 fallback otherwise) - Fast:
[VarUInt charCount] [raw UTF-16 bytes]— zero-encoding memcpy
Code branch: context.FastWire flag set at context.Reset(). Checked in WriteStringUtf8() and integer write methods. FixStr optimization is skipped in Fast mode (UTF-8 specific).
ReferenceHandling
| Value | Tracked objects | Scan pass | Header flags | Wire markers |
|---|---|---|---|---|
None |
Nothing | Skipped | 0x00 |
Standard object markers only |
OnlyId |
IId objects only (by ID value) |
Partial | 0x02 |
ObjectRefFirst(70) + ObjectRef(65) |
All (default) |
All reference types | Full graph walk | 0x06 |
ObjectRefFirst(70) + ObjectRef(65) |
Format impact: When enabled, multi-referenced objects are written once with ObjectRefFirst(70) + VarUInt(refCacheIndex) on first encounter, then replaced by ObjectRef(65) + VarUInt(refCacheIndex) on subsequent encounters. Header HasCacheCount flag is set and cache count written.
Interaction with ThrowOnCircularReference (default: true):
true+ ref handling enabled: all objects tracked for cycle detection, throwsInvalidOperationExceptionon circular referencefalse+ ref handling enabled: only IId types tracked for deduplication, non-IId circular refs silently truncated atMaxDepth
UseMetadata
| Value | Wire markers | Property matching | Overhead |
|---|---|---|---|
false (default) |
FixObj/Object |
Positional index only — types must match | None |
true |
ObjectWithMetadata(69) / ObjectWithMetadataRefFirst(71) |
FNV-1a property name hashes | 4 bytes per property per type |
Format impact: When enabled, each type's first occurrence writes [VarUInt hashCount] [FNV-1a hash × N] before properties. Deserializer uses hashes to build source→destination index mapping, enabling cross-type deserialization (different property sets/ordering).
Code branch: context.UseMetadata controls whether ObjectWithMetadata(69) or plain Object(64) markers are used. When false, IsDirectObjectWrite=true allows source-generated writers to bypass WriteObject entirely and inline property writes.
Related: CheckDuplicatePropName (default: true) — throws if FNV-1a hash collision detected between property names of the same type. Disable in production for performance.
UseStringInterning
| Value | Eligible strings | Scan overhead | Wire markers |
|---|---|---|---|
None |
Nothing | None | String(91) / FixStr only |
Attribute (default) |
Properties with [AcStringIntern(true)] |
Scans marked properties | StringInternFirst(94) + StringInterned(92) |
All |
All strings within length limits | Scans all strings | StringInternFirst(94) + StringInterned(92) |
Length limits: MinStringInternLength (default: 4) and MaxStringInternLength (default: 64, 0=unlimited). Strings outside this range are always written inline.
Format impact: Interned strings on first occurrence: [StringInternFirst(94)] [VarUInt cacheIndex] [string data]. Subsequent: [StringInterned(92)] [VarUInt cacheIndex] (1–2 bytes vs full string). Single-occurrence strings are never interned — no overhead for unique strings.
Code branch: context.StringInternEligible flag set per-property before WriteString. Scan pass builds a WriteDuplicateEntry[] plan; write pass consumes it via cursor.
MaxDepth
| Value | Behavior |
|---|---|
255 (default) |
Effectively unlimited nesting |
0 |
Root level only — nested objects/collections written as Null(76) |
N |
Objects deeper than N levels written as Null(76) |
Format impact: Depth-exceeded values appear as Null(76) in the stream — indistinguishable from actual null values. No special marker.
Code branch: Checked at entry of every object/collection write: if (depth > MaxDepth) { WriteByte(Null); return; }.
UseCompression
| Value | Method | Granularity | Memory |
|---|---|---|---|
None (default) |
No compression | — | — |
Block |
LZ4 single block | Entire payload | Full buffer in memory |
BlockArray |
LZ4 chunked | 64KB chunks | Streaming-friendly, lower peak memory |
Format impact: Compression is applied post-serialization as a transparent wrapper — the inner wire format is unchanged. Both modes are pure managed C# (WASM-compatible, no native dependencies).
Code branch: Applied in AcBinarySerializer.Serialize() after the serialization context produces the raw buffer: if (UseCompression != None) Lz4.Compress(buffer, mode). Decompression is automatic on deserialize.
PropertyFilter
Optional delegate BinaryPropertyFilter? (default: null). When set, invoked for each property to decide inclusion.
delegate bool BinaryPropertyFilter(in BinaryPropertyFilterContext context);
BinaryPropertyFilterContext fields: DeclaringType, PropertyName, PropertyType, Instance (null during metadata phase), IsMetadataPhase, GetValue() (lazy).
Format impact: Excluded properties are completely absent from the stream — no marker, no placeholder. The deserializer must use UseMetadata=true or identical filter to correctly match property indices.
Code branch: context.HasPropertyFilter checked in ShouldSerializeProperty(). Called twice: once during metadata registration (Instance=null), once during write phase.
PropertyMapper
Optional delegate PropertyMapperDelegate? (default: null) for cross-type deserialization property remapping.
delegate PropertyInfo? PropertyMapperDelegate(PropertyInfo sourceProperty, Type destinationType);
Purpose: Maps properties between different class hierarchies (renamed properties, external DTOs). Result is cached — zero overhead on same-type operations (Deserialize<T>).
WASM Options
| Option | Default | Purpose |
|---|---|---|
IsWasm |
OperatingSystem.IsBrowser() |
Auto-detect WASM environment |
UseStringCaching |
follows IsWasm |
Cache short strings during deserialization to reduce GC pressure |
MaxCachedStringLength |
64 | Max string length to cache |
Format impact: None — these are deserialization-only optimizations. When UseStringCaching=true, the deserializer maintains an intern cache for strings ≤ MaxCachedStringLength chars. Disabled automatically when StringInternFirst marker is encountered (interning takes precedence).
Other Options
| Option | Type | Default | Purpose |
|---|---|---|---|
UseGeneratedCode |
bool | true |
Use source-generated writers/readers when available |
InitialBufferCapacity |
int | 4096 | Starting buffer size (bytes) for serialization output |
RemoveOrphanedItems |
bool | false |
During PopulateMerge: remove destination collection items with no matching source ID |
UseAsync |
bool | false |
Async context pool return via ThreadPool. Auto-disabled in WASM and when ReferenceHandling=None |
MaxContextPoolSize |
int | 8 | Max serialization contexts kept in pool |
Presets
| Preset | WireMode | Metadata | StringInterning | RefHandling | MaxDepth | Compression | Other |
|---|---|---|---|---|---|---|---|
Default |
Compact | false | Attribute | All | 255 | None | — |
FastMode |
Compact | false | None | None | 255 | None | No scan pass |
ShallowCopy |
Compact | false | None | None | 0 | None | Root level only |
WasmOptimized |
Compact | false | Attribute | All | 255 | None | +StringCaching |
WithoutReferenceHandling |
Compact | false | Attribute | None | 255 | None | No scan pass |
WithoutMetadata |
Compact | false | Attribute | All | 255 | None | — |
Performance implication of presets:
Default/WasmOptimized— two-phase (scan + serialize) due toReferenceHandling=AllFastMode/ShallowCopy— single-phase (no scan pass) since both interning and refs are disabled- The scan pass adds ~20-30% overhead; disable it when the object graph is a simple tree
Option Interactions
Key interdependencies that affect which code branches execute:
| Combination | Effect |
|---|---|
ReferenceHandling=None + UseStringInterning=None |
No scan pass — fastest path, single-phase serialization |
ReferenceHandling=All + UseMetadata=true |
Uses ObjectWithMetadataRefFirst(71) marker — combined ref + metadata |
UseMetadata=false + UseGeneratedCode=true |
IsDirectObjectWrite=true — generated code inlines property writes, bypasses WriteObject |
UseMetadata=true + PropertyFilter set |
Filter invoked twice (metadata phase + write phase); filter results must be stable |
WireMode=Fast + UseStringInterning!=None |
Interned strings still use the fast string path (UTF-16 for first occurrence, VarUInt index for subsequent) |
UseCompression!=None + any other option |
Compression is orthogonal — applied post-serialization, inner format unchanged |