C# sample
This independent, standard-library-only .NET 8 implementation reads dotReg version
1. It resolves headings and quoted key elements, retains every occurrence in
source order, and preserves each multiline body’s physical LF or CRLF endings.
Type annotations remain opaque text: this sample performs common string decoding
and does not introduce a type profile or implicit conversions.
The format specification is ../../docs/config-format.md. These public types and JSON fields are sample API choices rather than a standard retrieval API.
Build once, then invoke the compiled programme to avoid a build on every read:
dotnet build implementations/csharp/DotReg.Sample.csproj -c Releasedotnet implementations/csharp/bin/Release/net8.0/DotReg.Sample.dll settings.regdotnet implementations/csharp/bin/Release/net8.0/DotReg.Sample.dll --mode raw settings.regdotnet implementations/csharp/bin/Release/net8.0/DotReg.Sample.dll --mode strings < settings.regAn omitted filename, or -, reads UTF-8 bytes from standard input. The default
strings mode adds decoded to each value; raw returns syntax results without
common value decoding. Both modes return a single JSON array. On failure the
programme exits non-zero, leaves stdout empty for input and decoding failures,
and prints a diagnostic which excludes values, filenames and underlying I/O
exception strings. A late stdout-device failure can of course leave partial
output at that device.
Native API
Section titled “Native API”using DotReg;
using var source = File.OpenRead("settings.reg");Parser.ParseEach(source, entry =>{ if (entry.Kind == "value") { Text text = Parser.DecodeString(entry); // text.Value is a string; text.Unset retains the unset distinction. }});ParseEach(Stream, Action<Entry>) visits each complete heading or value before
parsing the next entry. It propagates a recipient’s exception as the same
exception object. A possible pipe boundary can require one unvalidated physical
line of lookahead, and the input buffer may read bytes ahead. An invalid
non-pipe lookahead line is validated only after the preceding recipient returns.
Multiline values are collected before notification. Recipient side effects are
not rolled back. The stream stays open; a null stream or recipient is rejected.
Parse(Stream) returns all entries only on success and throws on failure without
returning partial results. DecodeString(Entry) applies ordinary quoted and
triple-quoted escapes, keeps pipe bodies literal, and reports invalid strings.
Raw syntax parsing intentionally retains an undecodable quoted value until this
decoding step. Entry.Path is a separate array on each notification, so mutating
it cannot alter subsequent heading resolution. Compare complete element sequences
to identify keys; joining elements with dots loses their boundaries.
Updating source text
Section titled “Updating source text”Writer.AppendValue, Writer.SetText and Writer.SetTextAt edit a valid source string while preserving unrelated text. Appending follows the deepest existing matching heading with two more spaces and a relative key. If no heading path is a prefix of the target, it uses a fully qualified key at end of file. SetText replaces a unique occurrence or appends when absent; SetTextAt selects a zero-based repeated occurrence. Values are written as escaped, quoted single-line strings. Type conversion remains the application’s responsibility.
Memory and work
Section titled “Memory and work”The parser scans the input once using a 32 KiB byte buffer. It validates UTF-8 strictly and does not normalise Unicode or line endings. Persistent parent/suffix nodes share heading prefixes rather than retaining a full expanded path at every scope. Path expansion is iterative, so deep headings do not consume call-stack depth. Parsing and common decoding do not use regular expressions, an object tree, a repeated-key index, or third-party packages.
Streaming retention consists of active heading suffixes, the current physical line or multiline value, one possible lookahead line, the input buffer and the largest line scratch buffer seen. Each recipient receives a fresh expanded path; creating that path costs work proportional to its element count, as required by this API. Triple-quoted margins are known only at the closing line, so that block’s source lines remain until its closing marker. Collection and the CLI also retain all results, and CLI serialisation temporarily retains its complete JSON output to prevent input failures from producing partial JSON.
The sample imposes no fixed format size or depth cap; available process memory and .NET’s string/array limits still bound an individual parse.
Validation
Section titled “Validation”The native tests require no test framework or NuGet packages:
dotnet run --project implementations/csharp/tests/DotReg.Tests.csproj -c ReleaseThey cover declarations, key identity, repeated occurrences, indentation, opaque annotations, common Unicode decoding, raw decoding failures, multiline margins and mixed endings, strict source encoding, short reads, recipient exception identity, deferred lookahead errors, mutation isolation, safe I/O diagnostics, fresh parse state, long lines and deep headings. The repository’s shared conformance runner additionally compares the CLI with the other samples.