Skip to content
github-actions[bot] edited this page Nov 10, 2026 · 4 revisions

Parser Options

IniParserOptions is a configuration class that controls how IniFileParser interprets INI file content. All options default to the most common / lenient behaviour so that existing code continues to work unchanged.

Use the fluent IniConfigBuilder methods to apply options when loading via the registry, or pass an IniParserOptions instance directly to IniFileParser.Parse when using the low-level API.


Quick-start: fluent builder

using var config = IniConfigRegistry.ForFile("app.ini")
    .AddSearchPath(AppContext.BaseDirectory)
    .EnableEscapeSequences()          // decode \n, \t, \\, \xHH, …
    .EnableQuotedValues()             // strip surrounding "…" / '…'
    .EnableLineContinuation()         // join lines ending with \
    .AssignmentDelimiters("=:")       // accept '=' and ':'
    .CaseSensitiveKeys()              // AppName ≠ appname
    .WithDuplicateKeyHandling(DuplicateKeyHandling.FirstWins)
    .RegisterSection<IAppSettings>(new AppSettingsImpl())
    .Build();

Or supply a pre-built IniParserOptions object:

var opts = new IniParserOptions
{
    AssignmentDelimiters = "=:",
    EscapeSequences = true,
    QuotedValues    = true,
};

using var config = IniConfigRegistry.ForFile("app.ini")
    .AddSearchPath(AppContext.BaseDirectory)
    .WithParserOptions(opts)
    .RegisterSection<IAppSettings>(new AppSettingsImpl())
    .Build();

Rules that always apply

  • A repeated [Section] header continues the existing section instead of replacing it, so keys above the second header are kept and DuplicateKeyHandling applies across both parts.
  • A leading byte order mark (\uFEFF) in the string passed to IniFileParser.Parse(string) is ignored.

Options reference

AssignmentDelimiters

Controls which characters are treated as key/value assignment delimiters. Default is =: (both equals and colon are accepted).

[Database]
Host = server
Port: 5432
var opts = new IniParserOptions { AssignmentDelimiters = "=:" };
var file = IniFileParser.Parse(content, opts);

Builder shorthand:

.AssignmentDelimiters("=:")

DuplicateKeyHandling

Controls what happens when the same key appears more than once inside one section.

Value Behaviour
LastWins (default) Each occurrence overwrites the previous one; the last value wins
FirstWins The first occurrence is kept; later duplicates are silently ignored
ThrowError An InvalidOperationException is thrown on the first duplicate
[Database]
Host = server1
Host = server2      ; duplicate!
// LastWins (default) → Host = "server2"
var file = IniFileParser.Parse(content);

// FirstWins → Host = "server1"
var file = IniFileParser.Parse(content,
    new IniParserOptions { DuplicateKeyHandling = DuplicateKeyHandling.FirstWins });

// ThrowError → throws InvalidOperationException
var file = IniFileParser.Parse(content,
    new IniParserOptions { DuplicateKeyHandling = DuplicateKeyHandling.ThrowError });

Builder shorthand:

.WithDuplicateKeyHandling(DuplicateKeyHandling.FirstWins)

QuotedValues

When true, values enclosed in matching double-quotes "…" or single-quotes '…' have their surrounding quote characters stripped. Interior whitespace is preserved.

QuotedValues INI line Parsed value
false (default) key = "hello world" "hello world"
true key = "hello world" hello world
true key = ' spaces ' spaces
true key = plain value plain value (unchanged — no quotes)

When QuotedValues is on and EscapeSequences is off, the parser also undoes the quote escaping the writer applies to quoted values (\" inside "…"), so values written with QuoteValuesOnWrite() round-trip. Hand-written values such as "C:\Temp\" are kept as written.

var opts = new IniParserOptions { QuotedValues = true };
var file = IniFileParser.Parse(content, opts);

Builder shorthand:

.EnableQuotedValues()

LineContinuation

When true, a backslash (\) at the very end of a value line causes the parser to append the trimmed content of the following line, forming a single value. The chain continues as long as each continuation line also ends with \.

[Message]
Text = Hello, \
       World!
LineContinuation Parsed value
false (default) Hello, \
true Hello, World!

Multi-line example:

[Script]
Command = first \
          second \
          third

Parsed value with LineContinuation = true: first second third

Details:

  • Only an odd number of trailing backslashes continues the line: abc\ continues, C:\Temp\\ (an escaped backslash) does not.
  • A section header is never swallowed: continuation stops before a [Section] line. When the next line is a section header, a blank line or the end of the file, the value is kept as written, including its trailing backslash (e.g. Dir = C:\Temp\ followed by [Next]).
  • An empty continuation line ends the value.
var opts = new IniParserOptions { LineContinuation = true };
var file = IniFileParser.Parse(content, opts);

Builder shorthand:

.EnableLineContinuation()

EscapeSequences

When true, standard C-style escape sequences in values are decoded.

Sequence Decoded character
\\ Literal backslash \
\n Newline (LF, U+000A)
\r Carriage return (CR, U+000D)
\t Horizontal tab (U+0009)
\0 Null character (U+0000)
\" Double quote "
\' Single quote '
\a Bell / alert (U+0007)
\b Backspace (U+0008)
\xHH Character with hex code HH (two hex digits)

Unrecognised sequences (e.g. \q) are left unchanged — the backslash is preserved.

[Paths]
DataDir = C:\\ProgramData\\MyApp
Greeting = Hello\nWorld
Tab = col1\tcol2
Bullet = \x2022 item

With EscapeSequences = true:

Key Value
DataDir C:\ProgramData\MyApp
Greeting Hello + newline + World
Tab col1 + tab + col2
Bullet • item
var opts = new IniParserOptions { EscapeSequences = true };
var file = IniFileParser.Parse(content, opts);

Builder shorthand:

.EnableEscapeSequences()

CaseSensitiveKeys

By default (false) key names within a section are compared case-insensitively — AppName, appname, and APPNAME all refer to the same entry.

When set to true, key comparisons use ordinal case-sensitive equality, so AppName and appname are treated as different keys.

[S]
AppName = Pascal
appname = lower
CaseSensitiveKeys GetValue("AppName") GetValue("appname") Entry count
false (default) lower (LastWins) lower 1
true Pascal lower 2
var opts = new IniParserOptions { CaseSensitiveKeys = true };
var file = IniFileParser.Parse(content, opts);

Builder shorthand:

.CaseSensitiveKeys()

PreserveTrivia

When true, blank lines, comment lines (exactly as written) and lines that cannot be parsed are kept on the parsed IniFile (IniSection.LeadingTrivia, IniEntry.LeadingTrivia, IniFile.TrailingTrivia), and IniFileWriter writes them back unchanged. Default false. IniConfigBuilder.PreserveFormatting() turns it on (see Saving).

CaseSensitiveSections

By default (false) section names are compared case-insensitively — [General], [GENERAL], and [general] all refer to the same section.

When set to true, section-name comparisons use ordinal case-sensitive equality, so [General] and [GENERAL] are independent sections.

[General]
key = v1

[GENERAL]
key = v2
CaseSensitiveSections GetSection("General") GetSection("GENERAL") Section count
false (default) v2 (LastWins) v2 1
true v1 v2 2
var opts = new IniParserOptions { CaseSensitiveSections = true };
var file = IniFileParser.Parse(content, opts);

Builder shorthand:

.CaseSensitiveSections()

Combining options

All options can be combined freely. Use the fluent builder methods to compose the exact set you need:

using var config = IniConfigRegistry.ForFile("app.ini")
    .AddSearchPath(AppContext.BaseDirectory)
    .EnableEscapeSequences()
    .EnableQuotedValues()
    .EnableLineContinuation()
    .CaseSensitiveKeys()
    .CaseSensitiveSections()
    .WithDuplicateKeyHandling(DuplicateKeyHandling.ThrowError)
    .RegisterSection<IAppSettings>(new AppSettingsImpl())
    .Build();

Or build the options object once and reuse it across multiple configs:

var strictOpts = new IniParserOptions
{
    EscapeSequences      = true,
    QuotedValues         = true,
    CaseSensitiveKeys    = true,
    DuplicateKeyHandling = DuplicateKeyHandling.ThrowError,
};

using var configA = IniConfigRegistry.ForFile("a.ini")
    .WithParserOptions(strictOpts)
    .RegisterSection<IA>(new AImpl())
    .Build();

using var configB = IniConfigRegistry.ForFile("b.ini")
    .WithParserOptions(strictOpts)
    .RegisterSection<IB>(new BImpl())
    .Build();

Low-level API

If you use IniFileParser directly (without IniConfig / IniConfigBuilder), pass IniParserOptions as the second argument:

var opts = new IniParserOptions
{
    EscapeSequences = true,
    QuotedValues    = true,
};

// From a string
var iniFile = IniFileParser.Parse(rawText, opts);

// From disk (sync)
var iniFile = IniFileParser.ParseFile("/path/to/file.ini", Encoding.UTF8, opts);

// From disk (async)
var iniFile = await IniFileParser.ParseFileAsync("/path/to/file.ini",
    Encoding.UTF8, opts, cancellationToken);

// From any TextReader (e.g. an embedded resource); the reader is not disposed
using var reader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("MyProduct.defaults.ini")!);
var iniFile = IniFileParser.Parse(reader, opts);

Files and readers are parsed through one small pooled buffer: a file that fits into it (up to 16 KB, most configuration files) is parsed as a whole, a larger one line by line while it is read. No string or buffer of the whole content is created, so large files do not end up on the large object heap. Only the section names, keys, values and comments of the result are allocated.


See also

Clone this wiki locally