Skip to content

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:

Terminal window
dotnet build implementations/csharp/DotReg.Sample.csproj -c Release
dotnet implementations/csharp/bin/Release/net8.0/DotReg.Sample.dll settings.reg
dotnet implementations/csharp/bin/Release/net8.0/DotReg.Sample.dll --mode raw settings.reg
dotnet implementations/csharp/bin/Release/net8.0/DotReg.Sample.dll --mode strings < settings.reg

An 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.

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.

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.

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.

The native tests require no test framework or NuGet packages:

Terminal window
dotnet run --project implementations/csharp/tests/DotReg.Tests.csproj -c Release

They 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.