|
Enhanced C#
Loyc library documentation
|
Convenience methods and shared definitions for a pair of ISyncManager implementations (Reader and Writer) that read and write the Protocol Buffers wire format. Call Write{T}(T, SyncObjectFunc{Writer, T}, Options?) or Read{T}(ReadOnlyMemory{byte}, SyncObjectFunc{Reader, T}, Options?) to (de)serialize an object, NewWriter / NewReader(ReadOnlyMemory{byte}, Options?) to obtain a low-level (de)serializer, or WriteSchema{T}(SyncObjectFunc{SchemaWriter, T}, Options?) to generate a .proto schema describing the output.
More...
Convenience methods and shared definitions for a pair of ISyncManager implementations (Reader and Writer) that read and write the Protocol Buffers wire format. Call Write{T}(T, SyncObjectFunc{Writer, T}, Options?) or Read{T}(ReadOnlyMemory{byte}, SyncObjectFunc{Reader, T}, Options?) to (de)serialize an object, NewWriter / NewReader(ReadOnlyMemory{byte}, Options?) to obtain a low-level (de)serializer, or WriteSchema{T}(SyncObjectFunc{SchemaWriter, T}, Options?) to generate a .proto schema describing the output.
The output is standard, valid Protocol Buffers: any Protobuf implementation (such as protoc-generated code or protobuf-net) can parse it using the proto3 schema produced by SyncProtobuf.SchemaWriter, and Reader can parse messages produced by other Protobuf implementations from the same schema.
Unlike SyncBinary, this format identifies every field by an integer ID. Therefore Reader and Writer report ISyncManager.NeedsIntegerIds = true and ISyncManager.SupportsReordering = true: fields may be read in any order, and unknown fields are skipped.
Each call to a Sync method carries a FieldId. If the FieldId specifies an integer ID (i.e. FieldId.Id != int.MinValue, as produced by the (name, id) tuple conversion or by a private Symbol pool), that ID becomes the Protobuf field number. Otherwise the field number is auto-assigned as N + 1, where N is the last field number used in the current message (starting from 0). The auto-numbering advances for every field synchronized (whether or not a value is physically written), so the reader and writer stay in agreement as long as they synchronize the same fields in the same order. Valid field numbers are 1 to 536,870,909; the range 19000-19999 (reserved by Protobuf) and the two highest numbers (reserved by SyncProtobuf, see below) are rejected.
Every field is preceded by a tag: a varint equal to (fieldNumber << 3) | wireType, using the standard Protobuf wire types:
bool, char and all integer types. Signed integers are stored as their 64-bit two's-complement bit pattern (so negative numbers occupy 10 bytes, exactly like Protobuf int32/int64). double (8 bytes, little-endian IEEE 754). float (4 bytes, little-endian IEEE 754). string (UTF-8), byte[] (Protobuf bytes), decimal (16 bytes: the little-endian layout of decimal.GetBits(decimal)), System.Numerics.BigInteger (little-endian two's complement, the format of BigInteger.ToByteArray()), sub-messages, and the list/tuple/deduplication containers described below. Null and absent fields. A field whose value is null (a null nullable scalar, string, byte array, list or sub-object) is simply omitted, and the reader returns null when a requested nullable field is absent — matching Protobuf's "absent means
default" convention. Reading an absent field as a non-nullable primitive returns the type's default value, exactly like Protobuf. To preserve round-trip fidelity, non-null values are always written, even zero (in schema terms, every scalar field is optional, i.e. it has explicit presence).
The root object is written as a bare message body — no envelope — just like a message serialized by any other Protobuf library. Because of this, the root must be an object (not a list or tuple), a null root is encoded as zero bytes, and a non-null root that happens to contain no fields is marked with a reserved boolean field (number 536,870,910, called _present in generated schemas) so that it remains distinguishable from null.
A sub-object is a nested message: a LEN field containing the concatenation of its (tag, value) fields, which can be read in any order.
A tuple is also a nested message; its elements are stored as fields auto-numbered 1, 2, 3, ... (a null element is an omitted field, which stays position-safe because element numbering advances regardless).
A list field is a nested list container message in which all elements are stored in field 1 (in generated schemas this container is a message type like Int32List { repeated int32 items = 1; }). This one level of nesting is what allows SyncLib to distinguish a null list (field omitted) from an empty one (empty container), and to nest lists inside lists. Within the container:
decimal, BigInteger, sub-objects synchronized with ObjectMode.NotNull) are stored as one field-1 entry per element, like an ordinary repeated field. { optional T value = 1; } — the same idea as Protobuf's well-known wrapper types — where an empty wrapper represents a null element. This keeps null distinguishable from default values such as the empty string. byte[] and other byte lists are not stored as list containers at all; they are written as a single Protobuf bytes value.
A field or element synchronized with ObjectMode.Deduplicate is stored as a reference wrapper message { uint64 id = 1; T value = 2; }: the first occurrence of an object stores both its ID and its value, and each later occurrence stores only the ID. This lets SyncProtobuf serialize shared references and cyclic object graphs (see the Jack-and-Jill example in ISyncManager) while remaining valid Protobuf. Deduplication also works for strings and byte arrays.
Caution: because the wrapper is only present when Deduplicate is requested, the writer and reader must agree on which fields use Deduplicate. Unlike SyncBinary (whose optional markers make the flag self-describing), toggling ObjectMode.Deduplicate is a breaking change to the data stream.
ISyncManager.SyncTypeTag(string?) stores the tag as a string field with the reserved number 536,870,911 (_type in generated schemas). Protobuf parsers that don't know about it simply skip it.
Nested classes | |
| class | Options |
| Options that control the behavior of SyncProtobuf.Reader and SyncProtobuf.Writer. More... | |
| struct | Reader |
| The ISyncManager implementation that reads the Protocol Buffers wire format (see SyncProtobuf). Because fields are keyed by integer ID, this reader supports reordering (SupportsReordering) and skips unknown fields. Call Read{T}(ReadOnlyMemory{byte}, SyncObjectFunc{Reader, T}, Options?) to deserialize, or NewReader(ReadOnlyMemory{byte}, Options?) to construct one directly. More... | |
| struct | SchemaWriter |
An implementation of ISyncManager that produces a Protocol Buffers proto3 .proto definition describing the output of SyncProtobuf.Writer. More... | |
| struct | Writer |
| The ISyncManager implementation that writes the Protocol Buffers wire format (see SyncProtobuf for the format and guidelines). Call Write{T}(T, SyncObjectFunc{Writer, T}, Options?) to serialize an object, or NewWriter to construct one of these directly. More... | |
Static Public Member Functions | |
| static SyncProtobuf.Reader | NewReader (ReadOnlyMemory< byte > input, Options?options=null) |
| static SyncProtobuf.Reader | NewReader (byte[] input, Options?options=null) |
| static SyncProtobuf.Reader | NewReader (IScanner< byte > input, Options?options=null) |
| static T | Read< T > (ReadOnlyMemory< byte > input, SyncObjectFunc< Reader, T > sync, Options?options=null) |
| static T | ReadI< T > (ReadOnlyMemory< byte > input, SyncObjectFunc< ISyncManager, T > sync, Options?options=null) |
| static T | Read< T, SyncObject > (ReadOnlyMemory< byte > input, SyncObject sync, Options?options=null) |
| static T | Read< T > (byte[] input, SyncObjectFunc< Reader, T > sync, Options?options=null) |
| static T | ReadI< T > (byte[] input, SyncObjectFunc< ISyncManager, T > sync, Options?options=null) |
| static T | Read< T, SyncObject > (byte[] input, SyncObject sync, Options?options=null) |
| static SchemaWriter | NewSchema (Options?options=null) |
Creates a SyncProtobuf.SchemaWriter, an object that produces a Protocol Buffers .proto (proto3) definition describing the messages that SyncProtobuf.Writer would produce. After using it to "synchronize" one root object, call SchemaWriter.Finish (or ToString) to get the schema document. More... | |
| static ReadOnlyMemory< byte > | WriteSchema< T > (SyncObjectFunc< SchemaWriter, T > sync, Options?options=null) |
Generates a proto3 .proto schema (UTF-8) describing the Protobuf output that SyncProtobuf.Write{T}(T, SyncObjectFunc{Writer, T}, Options?) produces for type T with the same synchronizer. More... | |
| static ReadOnlyMemory< byte > | WriteSchemaI< T > (SyncObjectFunc< ISyncManager, T > sync, Options?options=null) |
| static ReadOnlyMemory< byte > | WriteSchema< T, SyncObject > (SyncObject sync, Options?options=null) |
| static string | WriteSchemaString< T > (SyncObjectFunc< SchemaWriter, T > sync, Options?options=null) |
| static string | WriteSchemaStringI< T > (SyncObjectFunc< ISyncManager, T > sync, Options?options=null) |
| static string | WriteSchemaString< T, SyncObject > (SyncObject sync, Options?options=null) |
| static ReadOnlyMemory< byte > | Write< T > (T value, SyncObjectFunc< Writer, T > sync, Options?options=null) |
| static ReadOnlyMemory< byte > | WriteI< T > (T value, SyncObjectFunc< ISyncManager, T > sync, Options?options=null) |
| static ReadOnlyMemory< byte > | Write< T, SyncObject > (T value, SyncObject sync, Options?options=null) |
| static SyncProtobuf.Writer | NewWriter (IBufferWriter< byte >?output=null, Options?options=null) |
|
static |
Creates a SyncProtobuf.SchemaWriter, an object that produces a Protocol Buffers .proto (proto3) definition describing the messages that SyncProtobuf.Writer would produce. After using it to "synchronize" one root object, call SchemaWriter.Finish (or ToString) to get the schema document.
Referenced by WriteSchema< T >(), WriteSchema< T, SyncObject >(), and WriteSchemaI< T >().
|
inlinestatic |
Generates a proto3 .proto schema (UTF-8) describing the Protobuf output that SyncProtobuf.Write{T}(T, SyncObjectFunc{Writer, T}, Options?) produces for type T with the same synchronizer.
The synchronizer runs once in SyncMode.Schema mode, in which there is no data: it receives a default/null value of T, and the Sync methods return default values. Field numbers in the schema are assigned exactly as the writer assigns them (from FieldId.Id, else auto N+1).
References NewSchema().
1.8.7