Choose an output model

Choose the row shape and lifetime you need before choosing an optimization. Most readers expose shared output APIs, but CSV’s span-based object mapper and its ToAsync<T> API differ from data-reader mappers on extension readers.

Compare outputs

API Contents Streaming Ownership
ToDataReader Current row, typed/raw getters Yes Dispose the reader; it owns the input unless explicitly constructed otherwise
ToDbDataReader Standard ADO.NET adapter Yes, including async reads Dispose the adapter
To<T> Constructor-mapped or custom-mapped objects Yes See format-specific mapping and stream rules
CSV ToAsync<T> Async mapped objects Yes File closes; caller-provided stream remains open
ToArrayString string?[] in current field order Yes Enumerator disposes reader and input
ToObjectArrays object?[], parsed values Yes Enumerator disposes reader and input
ToDictionaries Read-only string-keyed parsed values Yes Enumerator disposes reader and input
ToDynamic ExpandoObject with parsed members Yes Enumerator disposes reader and input
ToDataTable Complete in-memory table No Reads/disposes input during the call

ToDataTable currently stores ordinary columns as strings; a generated CSV row-number column is an integer. It is not a typed equivalent of ToObjectArrays. See stream lifetimes for lazy versus eager creation and early exits.

Parsed rows

using System.Text;
using PocketCsvReader.Configuration;

var csv = new CsvReaderBuilder()
    .WithDialect(d => d.WithHeader().WithLineTerminator("\n"))
    .WithSchema(s => s.Named().WithField<int>("Id").WithField<string>("Name"))
    .Build();
using var stream = new MemoryStream(Encoding.UTF8.GetBytes("Id,Name\n1,Ada\n"));
foreach (var row in csv.ToDictionaries(stream))
    Console.WriteLine($"{row["Id"].GetType().Name}: {row["Name"]}");
Int32: Ada

Use ToObjectArrays for the same parsed values by position, ToArrayString for strings, or ToDynamic for member access. Fields without an explicit type remain text. Parsed arrays/dictionaries/dynamic rows normalize database nulls to C# null; empty strings remain empty unless the format’s configured value handling changes them.

Names, shapes, and duplicate fields

Dictionaries use ordinal, case-sensitive keys. Duplicate names throw InvalidDataException; dynamic rows throw DuplicateNameException. Positional arrays preserve duplicate columns. Tables reject duplicate column names through their column collection. These distinctions avoid silently overwriting values.

For labeled formats, each dictionary or array reflects that current record’s field count and order. Do not cache ordinals across changing shapes. Tabular consumers require a consistent shape; ToDataTable establishes columns from the first record. Incomplete and extra CSV fields retain the underlying reader’s width behavior; materializers do not universally pad missing values.

Returned row arrays, strings, dictionaries, and dynamic objects are independently owned. Borrowed parser views expire on the next read; see buffering. All materializers can throw during enumeration when conversion fails. Use foreach or dispose enumerators explicitly on early exit.

For typed objects use object mapping; for asynchronous consumption use async reading; for columnar analytics use Arrow batches.

Reader filters apply before output rows are materialized, including objects, arrays, dictionaries, dynamic records, and tables. Default CSV constructor projection can reuse values already parsed by filters; a custom span mapper controls its own parsing.


Read compressed input
Read fields with a DataReader