BinarySerializer
A declarative serialization framework for controlling formatting of data at the byte and bit level using field bindings, converters, and code.
Install / Use
/learn @jefffhaynes/BinarySerializerREADME
BinarySerializer
A .NET declarative serialization framework for controlling formatting of data at the byte and bit level, BinarySerializer is designed to make working with formats and protocols fast and simple using types, bindings, converters, and code.
Field Ordering
In order to ensure the correct order of serialization, FieldOrder attributes are required on all classes with more than one field or property. By convention, base classes are serialized first followed by any derived classes. For example, the fields in DerivedClass will serialize in order A, B, C.
public class BaseClass
{
public int A { get; set; }
}
public class DerivedClass : BaseClass
{
[FieldOrder(0)]
public int B { get; set; }
[FieldOrder(1)]
public int C;
}
var stream = new MemoryStream();
var serializer = new BinarySerializer();
var derivedClass = new DerivedClass();
await serializer.SerializeAsync(stream, derivedClass);
Note that properties and fields are used interchangeably as they are treated as equivalent by the serializer.
Binding
The most powerful feature of BinarySerializer is the ability to bind attributes to other fields in the object graph. Using the available attributes this approach can support complex formats and protocols. One of the simplest examples of binding is field length binding.
public class Person
{
[FieldOrder(0)]
public byte NameLength { get; set; }
[FieldOrder(1)]
[FieldLength(nameof(NameLength))]
public string Name { get; set; }
}
var person = new Person { Name = "Alice" };
await serializer.SerializeAsync(stream, person);
<p align="center">
<img src="https://raw.githubusercontent.com/jefffhaynes/BinarySerializer/master/BinarySerializer.Docs/LengthBinding.png" />
</p>
Note that it is not necessary that NameLength contain the length of the Name field as that value will be computed during serialization and updated in the serialized graph. During deserialization the NameLength value will be used to correctly deserialize the Name field.
Length can also be specified for complex objects. See the FieldLength section for more examples.
Attributes
There are a number of attributes that can be used to control the serialization of fields.
- Ignore
- IgnoreMember
- FieldOrder
- FieldLength
- FieldBitLength
- FieldBitOrder
- FieldCount
- FieldAlignment
- FieldScale
- FieldEndianness
- FieldEncoding
- FieldValue
- FieldChecksum
- FieldCrc16
- FieldCrc32
- FieldOffset
- Subtype
- SubtypeFactory
- SubtypeDefault
- SerializeAs
- SerializeAsEnum
- SerializeWhen
- SerializeWhenNot
- SerializeUntil
- ItemLength
- ItemSubtype
- ItemSubtypeFactory
- ItemSubtypeDefault
- ItemSerializeUntil
IgnoreAttribute
Any field or property with an Ignore attribute will not be included in serialization or deserialization. These fields can still be used in bindings, however properties will be treated as normal fields. If some calculation on a binding source is required, this can be accomplished with a binding converter.
IgnoreMemberAttribute
Similar to IgnoreAttribute but can be used on containing type definitions to reference members by name.
FieldOrderAttribute
This attribute is required on any field or property in a class with more than one field or property. Only the relative order value matters; for example, field ordering can be zero-based, one-based, prime numbers only, etc. In the case of a class inheriting from a base, base fields are serialized before derived values irrespective of field order numbers. In the following example the field A will be serialized first, followed by B and then C. Note that the base class does not need to specify field ordering as there is only one field.
public class BaseClass
{
public int A { get; set; }
}
public class DerivedClass : BaseClass
{
[FieldOrder(0)]
public int B { get; set; }
[FieldOrder(1)]
public int C { get; set; }
}
FieldLengthAttribute
FieldLength can be used to specify either a bound or constant field length as measured in bytes. Field lengths can apply to anything that is sizable including strings, arrays, lists, streams, and even objects.
For constant length fields, the serialized field length will always result in the specified length, either by limiting the serialization operation or padding out the result with zeros. For bound length fields, the source will be updated with the serialized length. Typically source fields are value types such as integers but value converters may also be used to update other types.
public class Person
{
[FieldLength(32)]
public string Name { get; set; }
}
Alternatively, the length of the Name field could be bound to a NameLength field:
public class Person
{
[FieldOrder(0)]
public byte NameLength { get; set; }
[FieldOrder(1)]
[FieldLength(nameof(NameLength))]
public string Name { get; set; }
}
<p align="center">
<img src="https://raw.githubusercontent.com/jefffhaynes/BinarySerializer/master/BinarySerializer.Docs/LengthBinding.png" />
</p>
In some cases it may be desirable to limit a collection of items by the total serialized length. Note that we are not restricting the number of items in the collection here, but the serialized length in bytes. To restrict the number of items in a collection use the FieldCount attribute.
public class Directory
{
[FieldOrder(0)]
public byte NamesLength { get; set; }
[FieldOrder(1)]
[FieldLength(nameof(NamesLength))]
public List<string> Names { get; set; }
}
<p align="center">
<img src="https://raw.githubusercontent.com/jefffhaynes/BinarySerializer/master/BinarySerializer.Docs/CollectionLengthBinding.png" />
</p>
To enforce the size of an entire object, write:
public class Person
{
[FieldOrder(0)]
public string FirstName { get; set; }
[FieldOrder(1)]
public string LastName { get; set; }
}
public class PersonContainer
{
[FieldLength(24)]
public Person Person { get; set; }
}
Note that if the field length is constant Person will always be 24 bytes long and will be padded out if the serialized length of Person is less than 24. However, if the length is bound to a field such as "PersonLength" then the actual length of Person will take precedence and PersonLength will be updated accordingly during serialization.
public class PersonContainer
{
[FieldOrder(0)]
public int PersonLength { get; set; } // set to the length of Person during serialization.
[FieldOrder(1)]
[FieldLength(nameof(PersonLength))]
public Person Person { get; set; }
}
Some formats and protocols will define a set of fields of specified size, with "optional" trailing fields. In the following example, EntryLength will either be 32 or 36, depending on whether or not Age is specified.
public class Person
{
[FieldOrder(0)]
[FieldLength(32)]
public string Name { get; set; }
[FieldOrder(1)]
public int? Age { get; set; }
}
public class PersonEntry
{
[FieldOrder(0)]
public int EntryLength { get; set; }
[FieldOrder(1)]
[FieldLength(nameof(EntryLength))]
public Person Person { get; set; }
}
If age is null during serialization, the framework will update EntryLength to be 32, or 36 if Age is present. If EntryLength is 32 during deserialization, the framework will return a null value for Age. If EntryLength is 36, the framework will deserialize the Age value.
FieldBitLengthAttribute
The FieldBitLength attribute is similar to the length attribute but can be used to specify field lengths in terms of bits. Note that if the bit values do not add to a byte-aligned length, remaining bits will be dropped from the final serialized stream. There are also some limitations on non-byte-aligned field lengths when used in combination with other attributes.
WARNING: There are known issues when using bit fields in big endian mode. Results are undefined.
public class Header
{
[FieldOrder(0)]
[FieldBitLength(3)]
public HeaderType Type { get; set; }
[FieldOrder(1)]
[FieldBitLength(5)]
public int Length { get; set; }
}
FieldBitOrderAttribute
The FieldBitOrder attribute is used alongside the FieldBitLength attribute, or bitwise data members. It determines the order (within the byte) at which the bits are allocated for the field. NOTE: It does not change the significance of the bits within the field value, just where in the raw data stream they are allocated to/from.
WARNING: There are known issues when using bit fields in big endian mode. Results are undefined. **WARNING: Do NOT mix BitOrder.MsbFirst and BitOrder.LsbFirst within the same byte. Results are u
Related Skills
node-connect
344.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
99.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
