High-performance RFC 4180-compliant CSV library for .NET 8+ with trimming/AOT support
- Usage
- Straightforward API with minimal fiddling unless needed
- Supports reading both
char
andbyte
(UTF-8) - Read from
TextReader
,Stream
,PipeReader
,ReadOnlySequence
,string
, etc. - Write to
StringBuilder
,TextWriter
,Stream
, file, etc. - Converter API similar to System.Text.Json to customize parsing
- When needed, ccess to low level types used to parse CSV
- Data binding
- Supports both binding to classes/structs and reading records and individual fields manually
- Supports binding to CSV headers, or column indexes
- Supports complex object initialization, e.g. a combination of properties and constructor params
- Configuration
- Supports both RFC4180/Excel and Unix/escaped CSV styles
- Automatic newline detection between
\n
and\r\n
when reading text or UTF-8
- Performance
- Built with performance in mind from the ground up
- Minimal allocations when reading records asynchronously
- Near-zero allocations when reading records synchronously
- Near-zero allocations when enumerating CSV (e.g. peeking fields)
- Source generator
- NativeAOT / trimming compatible
- Same feature list and API as reflection based binding
string data = "id,name,lastlogin\n1,Bob,2010-01-01\n2,Alice,2024-05-22";
foreach (var user in CsvReader.Read<User>(data))
{
Console.WriteLine(user);
}
record User(int Id, string Name, DateTime LastLogin, int? Age = null);
string data = "1,Bob,2010-01-01\n2,Alice,2024-05-22";
var options = new CsvTextOptions { HasHeader = false };
foreach (var user in CsvReader.Read<User>(data, options))
{
Console.WriteLine(user);
}
class User
{
[CsvIndex(0)] public int Id { get; set; }
[CsvIndex(1)] public string? Name { get; set; }
[CsvIndex(2)] public DateTime LastLogin { get; set; }
}
var options = new CsvUtf8Options { /* configure here */ };
await foreach (var user in CsvReader.ReadAsync<User>(File.OpenRead(@"C:\test.csv"), options))
{
Console.WriteLine(user);
}
foreach (var user in CsvReader.Read<User>(data, UserTypeMap.Instance))
{
Console.WriteLine(user);
}
record User(int Id, string Name, DateTime LastLogin, int? Age = null);
[CsvTypeMap<char, User>]
partial class UserTypeMap;
string data = "id,name,lastlogin,age\n1,Bob,2010-01-01,42\n2,Alice,2024-05-22,\n";
// case insensitive header names (enabled by default)
var options = new CsvTextOptions { Comparer = StringComparer.OrdinalIgnoreCase };
foreach (CsvValueRecord<char> record in CsvReader.Enumerate(data, options))
{
// get fields by column index of header name
var u1 = new User(
Id: record.GetField<int>(0),
Name: record.GetField<string>(1),
LastLogin: record.GetField<DateTime>(2),
Age: record.GetFieldCount() >= 3 ? record.GetField<int?>(3) : null);
var u2 = new User(
Id: record.GetField<int>("Id"),
Name: record.GetField<string>("Name"),
LastLogin: record.GetField<DateTime>("LastLogin"),
Age: record.GetFieldCount() >= 3 ? record.GetField<int?>("Age") : null);
}
The CsvValueRecord<T>
struct wraps around buffers from the CSV data source directly. To access the records safely outside a foreach
, use AsEnumerable()
or manually call new CsvRecord<T>(csvValueRecord)
.
string data = "id,name,lastlogin,age\n1,Bob,2010-01-01,42\n2,Alice,2024-05-22,\n";
// CsvRecord reference type copies the CSV fields for later use
List<CsvRecord<char>> records = [.. CsvReader.Enumerate(data).AsEnumerable()];
Console.WriteLine("First name: " + records[0].GetField(1));
User[] data =
[
new User(1, "Bob", DateTime.UnixEpoch, 42),
new User(2, "Alice", DateTime.UnixEpoch, null),
];
StringBuilder result = CsvWriter.WriteToString(data);
Console.WriteLine(result);
record User(int Id, string Name, DateTime LastLogin, int? Age = null);
var output = new MemoryStream();
await using (var writer = CsvWriter.Create(output))
{
writer.WriteRaw("id,name,lastlogin"u8);
writer.NextRecord();
writer.WriteField(1);
writer.WriteField("Bob");
writer.WriteField(DateTime.UnixEpoch);
writer.WriteField(42);
writer.NextRecord();
writer.WriteField(2);
writer.WriteField("Alice");
writer.WriteField(DateTime.UnixEpoch);
writer.WriteField(ReadOnlySpan<char>.Empty, skipEscaping: true);
writer.NextRecord();
}
Console.WriteLine(Encoding.UTF8.GetString(output.ToArray()));
record User(int Id, string Name, DateTime LastLogin, int? Age = null);
var output = new StringWriter();
using var writer = CsvWriter.Create(output);
User[] data =
[
new User(1, "Bob", DateTime.UnixEpoch, 42),
new User(2, "Alice", DateTime.UnixEpoch, null),
];
writer.WriteHeader<User>();
foreach (User item in data)
{
writer.WriteRecord<User>(item);
}
writer.Flush();
Console.WriteLine(output.ToString());
record User(int Id, string Name, DateTime LastLogin, int? Age = null);
Note: CsvHelper was chosen because it is the most popular CSV library for C#. This library isn't meant to be a replacement for CsvHelper.
Example CSV file used in th benchmarks is found in the TestData
folder in the tests-project.
--