Table of Contents

Inspect byte ranges and addresses

Normal parsing tells you what the data means. Diagnostic parsing also tells you which bytes produced each value. This is useful for hex viewers, format inspectors, and error reports.

Use ParseWithDebug when you need a struct's values and ranges together; it returns a ParseResult whose Value is the same StructValue that Parse returns and whose Debug list holds one DebugData record per value read. ReadValueWithDebug does the same for any selection (a union, an array, a scalar) and returns a ReadResult. Use ResolveAddress when you need only the absolute stream position of one path. DebugData lives in the CStructSharp.Diagnostics namespace.

Capture value ranges

The executable example uses a three-byte packed structure:

struct sample {
    uint8 tag;
    uint16 value;
};
private static void InspectRanges()
{
    var layout = new CStruct("struct sample { uint8 tag; uint16 value; };");
    using var stream = new MemoryStream([0xA1, 0x34, 0x12]);
    (StructValue result, IReadOnlyList<DebugData> ranges) = layout.ParseWithDebug(stream, "sample");
    Equal((byte)0xA1, result.Get<byte>("tag"));
    True(ranges.Any(item => item.Start == 1 && item.End == 3), "Value range was not reported.");

    stream.Position = 0;
    Equal(1L, layout.ResolveAddress(stream, "sample.value"));
    Equal(0L, stream.Position);
}

With input A1 34 12, sample.value occupies the half-open range [1, 3): it starts at position 1 and ends just before position 3. Half-open ranges make the byte count easy to calculate: End - Start, or 3 - 1 = 2; the record's Length property does this for you.

A typedef root keeps the name you selected. For typedef sample packet;, reading packet reports packet.value, not sample.value or packet.packet.value. Inline and chained aliases follow the same rule.

Each DebugData record is an immutable value with the item's Path (sample.value), Start and End positions, TypeName, and decoded Value. Select the bytes from your own input with Start..End; only a union captured as raw storage carries its bytes in Bytes. Treat records as diagnostic output. They expose exact input values, so filter them before sending them outside your application's trusted logs or diagnostic tools.

Resolve a position without returning the value

long address = layout.ResolveAddress(stream, "sample.value");

The example receives address 1. ResolveAddress restores the caller's original stream position after the lookup, so it can be used to annotate a stream without consuming the selected field.

Pointer paths need careful wording: root.pointer.address resolves the byte position where the pointer itself is stored, while root.pointer.value follows one level and resolves the target position.

Cost and common mistakes

Debug capture does more work and allocates diagnostic records, so use ordinary reads in hot paths that do not need byte ranges. Both debug parsing and address resolution require a readable, seekable stream because traversal may revisit positions.

If a range looks wrong, verify the stream's position at operation entry, packed versus aligned placement, the root path, array indices, and whether a pointer accessor followed a target. All returned stream positions are absolute; they include any nonzero root starting position.

See Paths and selection for coordinate rules and DebugData for the generated member reference.