How layouts are prepared and reused
Creating a CStruct prepares the source for later operations. The documentation sometimes calls this compiling the
layout. It prepares binary-layout metadata; it does not run a C compiler.
Constructor-time work
The constructor:
- parses the supported source syntax;
- resolves declarations, names, aliases, pointers, and expressions;
- checks enum, array, bitfield, recursion, and finite-storage rules; and
- records field order, codecs, alignment, fixed or runtime offset/size rules, array counts and strides, and bit slices in an immutable internal model.
A codec is the read/write rule for one primitive or enum backing type. A stride is the number of bytes from one array element to the next, including any required tail padding.
If preparation fails, the constructor throws CStructLayoutException and no usable layout is returned. Parsed
declaration dictionaries exposed for diagnostics are read-only views; parse, size, address, write, and update
operations use the prepared model.
Reuse a completed CStruct for every record that follows the same format options. Reconstructing it per record
repeats parsing and name/layout work.
Reuse and cached plans
Keep the prepared object when possible. CStruct.GetOrCompile also offers bounded shared reuse when your
application repeatedly starts from definition text; see managed caching.
Compilation prepares layout metadata, not native C machine code. The .NET runtime can separately compile
implementation methods and object-access delegates as part of executing the library.
Fixed layouts can use cached read and write plans with known offsets. Eligible typed reads fill C# objects directly. Every other layout runs a program: a flat list of read or write steps built once per struct from the prepared model, on first use, and cached with the layout. Where a position, count, or condition depends on data, the step evaluates it from values read earlier in the same operation. These choices preserve layout rules; they are not extra syntax the application needs to select.
Per-operation work
Each read-like call creates private state for:
- copied runtime variables and options;
- the stream or memory region;
- the total physical-read count;
- current struct and pointer depth;
- active pointer targets used for cycle detection;
- bitfield position; and
- optional debug ranges.
Locating a selected path and then reading its value uses the same state, so the bytes read while locating it count toward the same limits. A runtime-sized nested struct consumes its actual data extent and final alignment rather than an invented fixed size.
Struct reads at a root, nested field, array element, or pointer target run the same program for that struct. Union members share one overlapping storage region; a struct view inside a union begins at that union address and then advances through its own fields.
Concurrency and ownership
The completed CStruct is immutable and supports concurrent operations without one global instance lock.
Concurrency does not extend to mutable objects supplied by your application. Each operation must have exclusive use
of its:
- stream;
- writable span or
IBufferWriter<byte>; - mutable dictionary while CStructSharp is copying it;
- dynamic object, mapped-class instance, collection, or enumerable being written; and
- returned mutable dynamic or debug result.
Two tasks may share one CStruct and use separate streams. They must not seek or read the same stream at the same
time unless the application holds a lock for the complete CStructSharp call. Locking only an individual stream read
is not enough, because one operation may seek, read, and revisit several ranges.
Variable dictionaries are copied when an operation starts and must not change during that copy. Returned dynamic and debug objects belong to one operation and remain mutable application data. Init-only option objects can be shared after construction.
A common mistake is placing both the layout and one MemoryStream in a singleton service. Keep the reusable layout
in the service; create or obtain an independent stream for each request.
The contributor architecture page maps this flow to internal classes. Those names are maintenance details, not public API promises.