Table of Contents

Configuration

Overview

Aside from attributes, configuration is mainly done through the CsvOptions<T> class. Similar to System.Text.Json, options instances should be configured once and reused for the application lifetime. Creating a new options instance for every operation is not catastrophic, but will have a slight performance impact.

After an options instance is used to read or write CSV, it cannot be modified (see IsReadOnly). The options instances are thread-safe to use, but not to configure. You can call CsvOptions<T>.MakeReadOnly() to ensure thread safety by making the options instance immutable.

For convenience, a copy-constructor CsvOptions(CsvOptions<T>) is available, for example if you need slightly different configuration for reading and writing. This copies over all the configurable properties.

Default Options

The static CsvOptions<T>.Default property provides access to default configuration. This is used when null options are passed to Csv. The default options are read-only and have identical configuration to a new instance created with new().

Default options are available for byte and char.

Dialect

Delimiter

The field separator is configured with CsvOptions<T>.Delimiter. The default value is , (comma). Other common values include \t and ;.

Quote

The string delimiter is configured with CsvOptions<T>.Quote. The default value is " (double-quote). CSV fields wrapped in quotes (also referred to as strings) can contain otherwise special characters such as delimiters. A quote inside a string is escaped with another quote, e.g. "James ""007"" Bond". If you know your data does not contain quoted fields, you can improve performance by setting the value to null (this also requires that CsvOptions<T>.FieldQuoting is disabled).

Newline

The newline type (record separator) is configured with CsvOptions<T>.Newline. The default value is \r\n, which will also accept lone \n or \r when reading. The configured value is used as-is while writing. You can also configure it to be platform-specific. If you know your data only contains \n, you can improve performance by configuring the dialect accordingly. Otherwise, the safe choice is to leave it as the default.

Trimming

The CsvOptions<T>.Trimming property is used to configure whether spaces are trimmed from fields when reading. The default value is CsvFieldTrimming.None. The flags-enum supports trimming leading and trailing spaces, or both. For compliance with other CSV libraries, whitespace characters other than ASCII-space (0x20) are not trimmed.

This property is only used when reading CSV, and has no effect when writing, see CsvOptions<T>.FieldQuoting for writing.

Warning

For performance reasons, all the dialect characters must be non-alphanumeric ASCII (numeric value 1..127 inclusive).

Example

CsvOptions<byte> options = new()
{
    Delimiter = '\t',
    Quote = '"',
    Newline = CsvNewline.LF,
    Trimming = CsvFieldTrimming.Both,
    FieldQuoting = CsvFieldQuoting.Auto | CsvFieldQuoting.LeadingOrTrailingSpaces,
};

The CsvOptions<T>.HasHeader property is true by default, which expects a header record on the first line/record. The comparison is case-insensitive by default (configured via IgnoreHeaderCase). A delegate NormalizeHeader can be used to process header names before they are compared (note that this disables the header string value pooling).

const string csv = "id,name\n1,Bob\n2,Alice\n";

List<User> users = Csv.From(csv).Read<User>(new CsvOptions<char> { HasHeader = true });

Parsing and formatting fields

See Converters for an overview on converter configuration, implementation, and what converters are supported by default.

Quoting fields when writing

The CsvFieldQuoting enumeration and CsvOptions<T>.FieldQuoting property are used to configure the behavior when writing CSV. The default, CsvFieldQuoting.Auto only quotes fields if they contain special characters or whitespace.

// quote all fields, e.g., for noncompliant 3rd party libraries
return new CsvOptions<char>() { FieldQuoting = CsvFieldQuoting.Always };

If you are 100% sure your data does not contain any special characters, you can set it to CsvFieldQuoting.Never to squeeze out a little bit of performance by omitting the check if each written field needs to be quoted. If Quote is set to null, an exception is thrown if this setting is not CsvFieldQuoting.Never.

Skipping records or resetting headers

The CsvOptions<T>.RecordCallback property is used to configure a custom callback. The argument contains metadata about the current record, and can be used to skip records or reset the header record. This can be used to read multiple different "documents" out of the same data stream.

Below is an example of a callback that resets the headers and bindings on empty lines, and skips records that start with #.

CsvOptions<char> options = new()
{
    RecordCallback = (ref readonly CsvRecordCallbackArgs<char> args) =>
    {
        if (args.IsEmpty)
        {
            // reset the current headers and bindings on empty lines
            args.HeaderRead = false;
        }
        else if (args.Record[0] == '#')
        {
            // skip records that start with #
            args.SkipRecord = true;
        }
    }
};
Warning

Comment lines are not supported! Even if you configure the callback to skip rows that start with #, the rows are still parsed and expected to be properly structured CSV (e.g., no unbalanced quotes).

Advanced topics

NativeAOT

Since any implementation of CsvConverterFactory<T> (including built-in nullable and enum factories) can potentially require unreferenced types or dynamic code, the default CsvOptions<T>.GetConverter<TResult>() method is not AOT-compatible.

Use CsvOptions<T>.Aot to retrieve a wrapper around the configured converters, which provides convenience methods to safely retrieve converters for types known at runtime. See the documentation on methods of CsvOptions<T>.AotSafeConverters for more info. This property is used by the source generator.

// aot-safe default nullable and enum converters if not configured by user
CsvConverter<char, int?> c1 = options.Aot.GetOrCreateNullable(static o => o.Aot.GetConverter<int>());
CsvConverter<char, DayOfWeek> c2 = options.Aot.GetOrCreateEnum<DayOfWeek>();

Buffer sizes and memory pooling

You can configure the I/O options with CsvIOOptions when using streamed reading/writing, including buffer sizes (defaulting to 16 kB, and 32 kB for file I/O) and whether to close the Stream/TextWriter passed.

You can configure how memory is allocated by assigning a custom IBufferPool. This memory is used to handle escaping, unescaping, and buffers to read data from streaming sources. The default value uses the shared array pool.

The library also maintains a small pool of string-instances of previously encountered headers, so unless your data is exceptionally varied, the allocation cost is paid only once.

Custom binding

If you don't want to use the built-in CsvReflectionBinder<T> (attribute configuration), set CsvOptions<T>.TypeBinder property to your custom implementation implementing ICsvTypeBinder<T>.