Better attributes for configuring object de-/serialization

Created by: RayKoopa

The BinaryMemberAttribute currently handles a lot of different aspects of object member serialization. It should be split up into the following attributes to allow easier and better extensible configuration and also shorten the code required to use the attributes:

  • Rename *DataFormat to just *Coding to further shorten the lengthy enumeration names.
  • Replace OffsetOrigin.Current with Origin.Add and OffsetOrigin.Begin with Origin.Set to clarify purpose and shorten names. Default to Origin.Begin. Allow Origin.Align to use Align() method.
  • Rename ByteOrder.BigEndian to Endian.Big and Endian.LittleEndian to Endian.Little to further shorten the lengthy enumeration names. Apply similar naming to ByteConverter instances.
  • Rename IBinaryConverter to IDataConverter.
  • DataClassAttribute: Represents the previous BinaryObjectAttribute.
  • DataMemberAttribute: Used to explicitly mark a member to be read or written, in case the owning instance is annotated with DataClassAttribute.Explicit.
  • DataBooleanAttribute: Configures BooleanCoding for bool members.
  • DataDateTimeAttribute: Configures DateTimeCoding for DateTime members.
  • DataEnumAttribute: Configures strict parsing of Enum members.
  • DataStringAttribute: Configures StringCoding, Encoding and expected string length for string members.
  • DataConverterAttribute: Configures using an IDataConverter instance instead of the other features.
  • DataArrayAttribute: Configures the number of elements to be read for IEnumerable members. Also allows passing a string to invoke an instance method or use another member of the instance for retrieving the actual size.
  • DataOffsetAttribute: Configures the Origin modification to apply to the current stream position.
  • DataEndianAttribute: Configures the Endian to use, if not the default one.
  • DataArrayAttribute can be combined with DataBooleanAttribute, DataDateTimeAttribute, DataEnumAttribute, DataStringAttribute, DataConverterAttribute, DataOffsetAttribute or DataByteOrderAttribute (the converter is invoked as many times as specified by the array length).
  • DataConverterAttribute cannot be combined with DataBooleanAttribute, DataDateTimeAttribute, DataEnumAttribute, DataStringAttribute or DataByteOrderAttribute. (? Maybe it can, and this information is passed to the interface again).

Additionally for structs and classes:

  • DataOffsetStartAttribute: Apply position modification before starting to read / write the instance.
  • DataOffsetEndAttribute: Apply position modification after reading / writing the instance.

This results in the following breaking changes:

  • Remove BinaryMemberAttribute.
  • Move the de-/serialization classes / interfaces into a new namespace, preferrably Syroot.BinaryData.Serialization. Rename IBinaryConverter to IDataConverter.
  • Clean up the IDataConverter interface accordingly.

Practical example with X11 structures:

using Syroot.BinaryData;
using Syroot.BinaryData.Serialization;

namespace X11
{
	public struct Screen
	{
		public Window Root; // uint
		public Colormap DefaultColorMap; // uint
		public uint WhitePixel;
		public uint BlackPixel;
		[DataEnum(true)] public Event CurrentInputMasks; // enum
		public ushort WidthInPixels;
		public ushort HeightInPixels;
		public ushort WidthInMillimeters;
		public ushort HeightInMillimeters;
		public ushort MinInstalledMaps;
		public ushort MaxInstalledMaps;
		public VisualID RootVisual; // uint
		[DataEnum(true)] public BackingStores BackingStores; // enum
		[DataBoolean(BooleanCoding.Byte)] public bool SaveUnders;
		public byte RootDepth;
		public byte AllowedDepthsLength;
		[DataArray(nameof(AllowedDepthsLength))] public IList<Depth> AllowedDepths;
	}
	
	public struct Depth
	{
		public byte DepthValue;
		[DataOffset(Origin.Add, 1)] // 1 unused byte
		public ushort VisualsLength;
		[DataArray(nameof(VisualsLength))] public IList<VisualType> Visuals;
	}
	
	[DataOffsetEnd(Origin.Add, 4)] // 4 unused bytes at end
	public struct VisualType
	{
		public VisualID VisualID; // uint
		[DataEnum(true)] public VisualTypeClass Class; // enum
		public byte BitsPerRgbValue;
		public ushort ColormapEntries;
		public uint RedMask;
		public uint GreenMask;
		public uint BlueMask;
	}
}

Extreme example with string array read behind unused bytes (nice new line wrapping makes this logical):

public struct Potato
{
	[DataByteOrder(ByteOrder.Big)] public ushort Age;
	[DataOffset(Origin.Align, 4)] // Pad to next 4 byte boundary
	[DataArray(3)] [DataString(StringCoding.Raw, 5, Encoding.ASCII)] public IList<string> ThreeFiveCharLongStrings;
}