Upgrading from CsvHelper 33? Read the v34.0.0 migration guide

SIMD CSV for .NET

CsvHelper

A CSV reader and writer for .NET, built for speed.

CsvHelper is a SIMD accelerated, allocation free CSV reader and strict RFC 4180 writer for .NET 10, .NET Standard 2.0 and 2.1 and .NET Framework 4.7.

>dotnet add package CsvHelper

The latest release is v34.0.0.

using CsvHelper;

using var reader = CsvReader.FromFile("orders.csv");    // or Create(stream), Create(byte[]), Create(TextReader), FromText(string)
var price = reader.GetFieldIndex("Unit Price");         // header lookup: exact, then ignoring case and whitespace
while (reader.Read())
{
    string id = reader.GetString(0);                    // by index
    string name = reader.GetString("Name");             // by header name
    decimal unitPrice = reader.Parse<decimal>(price);   // also int, long, double, bool, DateTime, Guid, ...
    ReadOnlySpan<byte> raw = reader.GetSpan(3);         // UTF-8, already unquoted, no allocation
    DateOnly ordered = reader.Parse<DateOnly>(4);       // any ISpanParsable<T> (.NET 8 and later)
}
using CsvHelper;

[CsvRecord]
public partial class Order
{
    public string Id { get; set; } = "";
    [CsvColumn("Unit Price")]
    public decimal Price { get; set; }
    [CsvColumn(Optional = true)]
    public int? Quantity { get; set; }
    [CsvIgnore]
    public decimal Total { get; set; }
}

[CsvRecord]
public partial record Line(string Sku, [CsvColumn(Index = 3)] double Weight, int Count = 1);

using var reader = CsvReader.FromFile("orders.csv");
foreach (Order order in reader.GetRecords<Order>()) { }          // also GetRecordsAsync<T>() and ParallelEnumerate<T>()
while (reader.Read()) { Order order = reader.GetRecord<Order>(); } // the current record

using var writer = CsvWriter.ToFile("copy.csv");
writer.WriteRecords(orders);                                     // the header, then the records
using CsvHelper;

using var writer = CsvWriter.ToFile("out.csv");           // or Create(stream), Create(TextWriter)
writer.WriteHeader("Id", "Name", "Price", "When");
foreach (var item in items)
{
    writer.WriteField(item.Id);                           // numbers format straight to UTF-8
    writer.WriteField(item.Name);                         // quoted only if it contains , " CR or LF
    writer.WriteField(item.Price);
    writer.WriteField(item.Timestamp);                    // DateTime/DateTimeOffset: ISO 8601 ("O") by default
    writer.EndRecord();                                   // or await writer.EndRecordAsync()
}
finding records
8.0 GB/s
reading every field
5.3 GB/s
writing text records
1.9 GB/s

Why CsvHelper

SIMD parsing Parses UTF-8 directly with AVX-512, AVX2 or 128-bit vectors, finds quoted regions branch-free and exposes fields as zero-copy spans. Allocation free Fields are spans into the reader's buffer and strings are created only when you ask for one, so reading a file field by field allocates a few kilobytes however large it is. The writer allocates a few hundred bytes. Every core ParallelEnumerate maps records on several threads and returns them in input order. ParallelWriteRecords formats records on every core and writes exactly what WriteRecords would. Source-generated records A source generator in the package writes the code that reads and writes [CsvRecord] types at compile time: no reflection, as fast as hand-written code, and mapping mistakes are compile errors. Native AOT and trimming On .NET 10 both packages are marked AOT compatible and trimmable, and the analyzers check them on every build. Only C#'s dynamic and LINQ queries need code generated at run time. Async throughout ReadAsync, await foreach, GetRecordsAsync, and WriteRecordsAsync for IAsyncEnumerable. Reads wait only when the buffer needs refilling and otherwise complete without allocating. Strict RFC 4180 output By default: CRLF, minimal quoting, doubled quotes and consistent field counts, with numbers formatted straight into a UTF-8 buffer. More than RFC 4180 Also reads and writes escaped files (the old Linux mode), unescaped files and fixed-width files. Adapts to messy files Detects the separator and encoding, recovers from stray quotes and has explicit options for malformed data. LINQ CsvHelper.Linq queries CSV with any LINQ operator, reads only the columns and rows a query uses, and joins with Entity Framework Core by sending it only the keys it needs. Fast dynamic records No record type? Dynamic records hold their fields' bytes rather than a dictionary of strings, and bind each member to its column once: 16 to 22x faster than ExpandoObject. Highly configurable Attributes and record maps fit columns to your types, even types you can't change, and the reader and writer options cover separators, quoting, headers, cultures and more. Fast, exact numbers Floats parse with a port of fast_float and format with a port of Dragonbox: bit-identical to .NET's own results, checked on all 2^32 floats, and 3 to 4.5x faster. Safe with untrusted files Escapes spreadsheet formulas such as =1+2, caps record size so an unclosed quote cannot fill memory, and can keep field values out of exception messages. Every .NET .NET 10 with SIMD, plus .NET Standard 2.0 and 2.1 and .NET Framework 4.7, which read and write exactly the same CSV. Fuzz tested The reader is fuzzed against an independent reference parser at every vector width, from every input source and with tiny buffers. Tests run on .NET 10, .NET 8 and .NET Framework 4.8. Measured Benchmarked against Sep and Sylvan.Data.Csv with BenchmarkDotNet, with every library returning the same results. Open source Free for commercial use and dual licensed under MS-PL and Apache 2.0. Many contributors have helped make CsvHelper what it is.

At a glance

A CSV reader and writer for .NET, built for speed. It targets .NET 10 and .NET 8, and also runs on .NET Framework 4.7+ and .NET Standard 2.0 and 2.1 (see Target frameworks):

  • Reader: parses UTF-8 directly with SIMD (AVX-512, AVX2 or 128-bit vectors), finds quoted regions branch-free with a carry-less multiply, and exposes fields as zero-copy spans. Flexible: it adapts to most real-world files and has explicit options for malformed ones. Besides RFC 4180 it reads escaped (a\,b), unescaped and fixed-width files (Parsing modes).
  • Writer: strict RFC 4180 output by default (CRLF, minimal quoting, doubled quotes, consistent field counts), formatting numbers straight into a UTF-8 buffer; also escaped, unescaped and fixed-width output.
  • LINQ: the CsvHelper.Linq package queries CSV with any LINQ operator, reading only the columns and rows a query uses, joins with Entity Framework Core and other providers by sending them only the keys it needs, and writes query results as CSV.

Benchmarks against Sep and Sylvan.Data.Csv are in bench/; results are in Benchmarks below.

For another cross-library view, see the .NET Matrix CSV Processing comparison.

Start reading

Get help

Sponsors

Contributors

Want to contribute? Great! Here are a few guidelines.

  1. If you want to do a feature, post an issue about the feature first. Some features are intentionally left out, some features may already be in the works, or I may have some advice on how I think it should be done. I would feel bad if time was spent on some code that won't be used.
  2. If you want to do a bug fix, it might not be a bad idea to post about it too. I've had the same bug fixed by multiple people at the same time before.
  3. All code should have a unit test. If you make a feature, there should be significant tests around the feature. If you do a bug fix, there should be a test specific to that bug so it doesn't happen again.
  4. Pull requests should have a single commit. If you have multiple commits, squash them into a single commit before requesting a pull.
  5. When in Rome: Try and follow the code styling already in place. I use default Visual Studio Text Editor settings but change tabs to Keep tabs.
The avatars of CsvHelper's contributors