Sep for .NET separated values
Trigger On
- delimited data needs are performance-sensitive and allocation-aware
- project needs explicit control over separator inference, escaping, trimming, and header behavior
- reading/writing large or long-lived file pipelines in ML, ETL, or analytics workloads
- startup/perf tests require AOT/trimming-friendly CSV/TSV processing
Install
- NuGet:
dotnet add package Sep
dotnet add package Sep --version <version>
- XML package reference:
<PackageReference Include="Sep" Version="x.y.z" />
- Verify baseline support by checking the package page:
- Source:
Workflow
flowchart LR
A[Input source: file/text/stream] --> B[Sep.Reader or Sep.New(...).Reader]
B --> C[SepReaderOptions]
C --> D[Rows -> Cols -> Span/Parse]
D --> E[Transform and validate]
E --> F[SepWriter via SepWriterOptions]
F --> G[To file/text output]
- Decide schema shape
- header present or no header
- separator known (
;, ,, tab, custom) or infer from first row
- row/column quoting rules
- Build reader with
Sep.Reader(...) and explicit options only where needed:
Sep.Reader() for inferred separator from header-like first row
Sep.New(',').Reader(...) for explicit separator mode
Sep.Reader(o => o with { HasHeader = false }) if header is absent
- Read rows and map columns as
ReadOnlySpan<char> first, convert only when needed.
- For output, use
reader.Spec.Writer() when you need the same separator/culture as input.
- Control writer behavior with
Sep.Writer(...) and SepWriterOptions (WriteHeader, Escape, DisableColCountCheck).
- Add async only where it brings value and your runtime is C# 13 / .NET 9+ for
await foreach over async reader rows.
- Use
ParallelEnumerate for CPU-heavy transformations only after benchmarking single-threaded baseline.
Install and read patterns
using var reader = Sep.Reader(o => o with
{
HasHeader = true,
Unescape = true,
Trim = SepTrim.Both
}).FromText(data);
foreach (var row in reader)
{
var id = row["Id"].Parse<int>();
var name = row[1].ToString();
// process row
}
Write patterns
using var reader = Sep.Reader().FromFile("input.csv");
using var writer = reader.Spec.Writer().ToFile("output.csv");
foreach (var row in reader)
{
using var writeRow = writer.NewRow(row);
writeRow["Amount"].Format(row["Amount"].Parse<double>() * 1.2);
}
Async reading and writing
var text = "A;B\n1;hello\n";
using var reader = await Sep.Reader().FromTextAsync(text);
await using var writer = reader.Spec.Writer().ToText();
await foreach (var row in reader)
{
await using var writeRow = writer.NewRow(row);
var normalized = row["B"].ToString().ToUpperInvariant();
writeRow["B"].Set(normalized);
}
Common configuration patterns
- Header-driven read
- default
HasHeader = true
- query by name:
row["ColName"]
- Headerless pipelines
HasHeader = false
- use index-based access:
row[0], row[1]
- Round-trip output
- start writer with
reader.Spec.Writer() to preserve inference and formatting contract
- Speed-first processing
- keep default buffer + culture unless profiling proves a need to tune
Best practices
- Parse to primitive types with
Parse<T> in hot paths to avoid extra allocations.
- Keep
ToString/format conversions at the edge (presentational layers), not in inner loops.
- Prefer
Unescape, Trim, and DisableQuotesParsing settings deliberately and test with realistic samples.
- For large transforms, isolate heavy CPU work after enumeration and then apply
ParallelEnumerate where appropriate.
Limitations to check before production
SepReader.Row and SepWriter.Row are ref structs:
- avoid patterns that store rows beyond immediate scope
- materialize if you truly need random async/LINQ-style buffering
SepReader row iteration is row-by-row by design; it is intentionally not the same as a classic collection model.
Deliver
- installation and usage guide that is ready to copy into a .NET repo
- practical reader/writer configuration patterns
- clear notes on defaults, tradeoffs, and constraints
Validate
dotnet add package Sep installs correctly and project compiles
- one file-read sample and one file-write sample execute successfully
- header/no-header and explicit-separator cases are covered
- at least one validation sample for quoting/unescaping or async path exists if required by task
Load References
- references/overview.md - official links and practical decision notes.
1---2name: dotnet-sep3description: Use Sep for high-performance separated-value parsing and writing in .NET, including delimiter inference, explicit parser/writer options, and low-allocation row/column workflows.4---56# Sep for .NET separated values78## Trigger On910- delimited data needs are performance-sensitive and allocation-aware11- project needs explicit control over separator inference, escaping, trimming, and header behavior12- reading/writing large or long-lived file pipelines in ML, ETL, or analytics workloads13- startup/perf tests require AOT/trimming-friendly CSV/TSV processing1415## Install1617- NuGet:18 - `dotnet add package Sep`19 - `dotnet add package Sep --version <version>`20- XML package reference:21 - `<PackageReference Include="Sep" Version="x.y.z" />`22- Verify baseline support by checking the package page:23 - [NuGet: Sep](https://www.nuget.org/packages/Sep/)24- Source:25 - [GitHub: nietras/Sep](https://github.com/nietras/Sep)2627## Workflow2829```mermaid30flowchart LR31 A[Input source: file/text/stream] --> B[Sep.Reader or Sep.New(...).Reader]32 B --> C[SepReaderOptions]33 C --> D[Rows -> Cols -> Span/Parse]34 D --> E[Transform and validate]35 E --> F[SepWriter via SepWriterOptions]36 F --> G[To file/text output]37```38391. Decide schema shape40 - header present or no header41 - separator known (`;`, `,`, tab, custom) or infer from first row42 - row/column quoting rules432. Build reader with `Sep.Reader(...)` and explicit options only where needed:44 - `Sep.Reader()` for inferred separator from header-like first row45 - `Sep.New(',').Reader(...)` for explicit separator mode46 - `Sep.Reader(o => o with { HasHeader = false })` if header is absent473. Read rows and map columns as `ReadOnlySpan<char>` first, convert only when needed.484. For output, use `reader.Spec.Writer()` when you need the same separator/culture as input.495. Control writer behavior with `Sep.Writer(...)` and `SepWriterOptions` (`WriteHeader`, `Escape`, `DisableColCountCheck`).506. Add async only where it brings value and your runtime is C# 13 / .NET 9+ for `await foreach` over async reader rows.517. Use `ParallelEnumerate` for CPU-heavy transformations only after benchmarking single-threaded baseline.5253### Install and read patterns5455```csharp56using var reader = Sep.Reader(o => o with57{58 HasHeader = true,59 Unescape = true,60 Trim = SepTrim.Both61}).FromText(data);6263foreach (var row in reader)64{65 var id = row["Id"].Parse<int>();66 var name = row[1].ToString();67 // process row68}69```7071### Write patterns7273```csharp74using var reader = Sep.Reader().FromFile("input.csv");75using var writer = reader.Spec.Writer().ToFile("output.csv");7677foreach (var row in reader)78{79 using var writeRow = writer.NewRow(row);80 writeRow["Amount"].Format(row["Amount"].Parse<double>() * 1.2);81}82```8384### Async reading and writing8586```csharp87var text = "A;B\n1;hello\n";8889using var reader = await Sep.Reader().FromTextAsync(text);90await using var writer = reader.Spec.Writer().ToText();9192await foreach (var row in reader)93{94 await using var writeRow = writer.NewRow(row);95 var normalized = row["B"].ToString().ToUpperInvariant();96 writeRow["B"].Set(normalized);97}98```99100### Common configuration patterns101102- Header-driven read103 - default `HasHeader = true`104 - query by name: `row["ColName"]`105- Headerless pipelines106 - `HasHeader = false`107 - use index-based access: `row[0]`, `row[1]`108- Round-trip output109 - start writer with `reader.Spec.Writer()` to preserve inference and formatting contract110- Speed-first processing111 - keep default buffer + culture unless profiling proves a need to tune112113## Best practices114115- Parse to primitive types with `Parse<T>` in hot paths to avoid extra allocations.116- Keep `ToString`/format conversions at the edge (presentational layers), not in inner loops.117- Prefer `Unescape`, `Trim`, and `DisableQuotesParsing` settings deliberately and test with realistic samples.118- For large transforms, isolate heavy CPU work after enumeration and then apply `ParallelEnumerate` where appropriate.119120## Limitations to check before production121122- `SepReader.Row` and `SepWriter.Row` are `ref struct`s:123 - avoid patterns that store rows beyond immediate scope124 - materialize if you truly need random async/LINQ-style buffering125- `SepReader` row iteration is row-by-row by design; it is intentionally not the same as a classic collection model.126127## Deliver128129- installation and usage guide that is ready to copy into a .NET repo130- practical reader/writer configuration patterns131- clear notes on defaults, tradeoffs, and constraints132133## Validate134135- `dotnet add package Sep` installs correctly and project compiles136- one file-read sample and one file-write sample execute successfully137- header/no-header and explicit-separator cases are covered138- at least one validation sample for quoting/unescaping or async path exists if required by task139140## Load References141142- [references/overview.md](references/overview.md) - official links and practical decision notes.