ComputerCodeBlue.Csv
1.3.0
dotnet add package ComputerCodeBlue.Csv --version 1.3.0
NuGet\Install-Package ComputerCodeBlue.Csv -Version 1.3.0
<PackageReference Include="ComputerCodeBlue.Csv" Version="1.3.0" />
<PackageVersion Include="ComputerCodeBlue.Csv" Version="1.3.0" />
<PackageReference Include="ComputerCodeBlue.Csv" />
paket add ComputerCodeBlue.Csv --version 1.3.0
#r "nuget: ComputerCodeBlue.Csv, 1.3.0"
#:package ComputerCodeBlue.Csv@1.3.0
#addin nuget:?package=ComputerCodeBlue.Csv&version=1.3.0
#tool nuget:?package=ComputerCodeBlue.Csv&version=1.3.0
ComputerCodeBlue.Csv
Lightweight extension methods around CsvHelper that make it easy to read and write CSV files with synchronous or asynchronous APIs.
This package is intended as a small utility library you can reuse across projects, instead of rewriting boilerplate around CsvHelper.
Why This Exists
Reading and writing CSV files is surprisingly hard. There are many edge cases that will break any quick-and-dirty parser you write yourself. Thankfully, there's CsvHelper, which I've used for many years. To use CsvHelper, I was creating the same boilerplate methods in order to get an API like System.IO.File where I can just use CsvFile.Read() or CsvFile.Write(). This project aims to put all of that into a reusable package.
Repository layout
src/ComputerCodeBlue.Csv/— the librarytests/ComputerCodeBlue.Csv.Tests/— unit tests (xUnit)
Features
Read CSV (sync/async)
CsvFile.Read<T>(filePath)→IEnumerable<T>CsvFile.ReadAsync<T>(filePath)→IAsyncEnumerable<T>CsvStream.Read<T>(stream)/CsvStream.ReadAsync<T>(stream)— same, for a caller-ownedStream
Write CSV (sync/async)
CsvFile.Write<T>(filePath, items)CsvFile.WriteAsync<T>(filePath, items)CsvStream.Write<T>(stream, items)/CsvStream.WriteAsync<T>(stream, items)
Anonymous types
CsvFile.ReadAnonymous(filePath, template)/CsvStream.ReadAnonymous(stream, template)(plus async variants) — read into an anonymous type by passing a throwaway instance of the desired shape.
Dynamic/loosely-typed rows (no fixed record type at all — e.g. template/merge-field substitution)
CsvFile.ReadDynamic(filePath)/CsvStream.ReadDynamic(stream)(plus async variants) — each row asDictionary<string, string>, keyed by header name, raw field values.CsvFile.WriteDynamic(filePath, headers, items)/CsvStream.WriteDynamic(stream, headers, items)(plus async variants) —itemsareIDictionary<string, object?>. Exists because CsvHelper's own dynamic write support writes no header at all for an empty sequence, and matches fields by enumeration order rather than by name (two same-shaped records added in a different key order silently land in the wrong columns). This always writes the given headers and looks each value up by name.
DataTablesupportdataTable.LoadCsv(filePath)/dataTable.LoadCsv(stream)(plus async variants) — load a CSV straight into aSystem.Data.DataTablevia CsvHelper'sCsvDataReaderandDataTable.Load(IDataReader). An emptyDataTablegets all-stringcolumns from the CSV header; aDataTablethat already has typed columns gets its values converted to those types.
CsvStreamnever closes theStreamyou pass it — you own its lifetime.Built on CsvHelper with sensible defaults (
CultureInfo.InvariantCulture).Optional
CsvOptionsparameter for full control.
Installation
dotnet add package ComputerCodeBlue.Csv
Or reference the project directly in your solution.
Usage
Define a model
public class Person
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
public int Age { get; set; }
}
Reading a file (synchronous)
using ComputerCodeBlue.Csv;
var people = CsvFile.Read<Person>("people.csv");
foreach (var person in people)
{
Console.WriteLine($"{person.FirstName} {person.LastName} ({person.Age})");
}
Reading a file (asynchronous streaming)
using ComputerCodeBlue.Csv;
await foreach (var person in CsvFile.ReadAsync<Person>("people.csv"))
{
Console.WriteLine($"{person.FirstName} {person.LastName} ({person.Age})");
}
Writing a file (synchronous)
using ComputerCodeBlue.Csv;
var people = new List<Person>
{
new() { FirstName = "Alice", LastName = "Smith", Age = 30 },
new() { FirstName = "Bob", LastName = "Johnson", Age = 42 }
};
CsvFile.Write("people.csv", people);
Writing a file (asynchronous)
using ComputerCodeBlue.Csv;
await CsvFile.WriteAsync("people.csv", people);
Reading/writing a stream
CsvStream mirrors CsvFile for a caller-supplied Stream (an HTTP response
body, a MemoryStream, a network stream, ...). It never closes the stream —
you retain ownership and are responsible for disposing it.
using ComputerCodeBlue.Csv;
using var stream = new MemoryStream();
CsvStream.Write(stream, people);
stream.Position = 0;
var peopleFromStream = CsvStream.Read<Person>(stream);
Anonymous types
Pass a throwaway instance of the desired shape as a template; its values are ignored, only its property names/types are used to match CSV headers:
using ComputerCodeBlue.Csv;
var rows = CsvFile.ReadAnonymous("people.csv", new { FirstName = "", LastName = "", Age = 0 });
Dynamic/loosely-typed rows
For cases with no fixed record type at all - e.g. reading an arbitrary CSV
for template/merge-field substitution, where the columns aren't known until
runtime. ReadDynamic returns each row as Dictionary<string, string>
keyed by the file's actual header names, with raw (unconverted) field
values - a zip code like "07030" stays a string, not 7030:
using ComputerCodeBlue.Csv;
foreach (var row in CsvFile.ReadDynamic("people.csv"))
{
Console.WriteLine($"{row["FirstName"]} {row["LastName"]}");
}
WriteDynamic takes an explicit column list plus items shaped as
IDictionary<string, object?>. It exists because CsvHelper's own dynamic
write support has two sharp edges: an empty IEnumerable<object>/
IEnumerable<dynamic> writes no header at all, and even where it does
write, fields are matched by enumeration order rather than by name - two
records with the same keys added in a different order silently land in the
wrong columns. WriteDynamic always writes the given headers and looks
each item's value up by column name, so neither failure mode can happen:
using ComputerCodeBlue.Csv;
var headers = new[] { "FirstName", "LastName", "Age" };
var rows = new List<IDictionary<string, object?>>
{
new Dictionary<string, object?> { ["FirstName"] = "Alice", ["LastName"] = "Smith", ["Age"] = 30 },
};
CsvFile.WriteDynamic("people.csv", headers, rows);
A row missing a declared column writes a blank cell, or throws if
CsvOptions.MissingField is CsvMissingFieldBehavior.Throw.
DataTable support
LoadCsv/LoadCsvAsync are extension methods on System.Data.DataTable, mirroring the BCL's own
DataTable.Load(IDataReader). Loading into an empty table creates columns (typed string) from
the CSV header:
using System.Data;
using ComputerCodeBlue.Csv;
var table = new DataTable().LoadCsv("people.csv");
Loading into a DataTable that already has typed columns (e.g. from a typed DataSet designer, or
built by hand) converts each value to the existing column's type instead:
var table = new DataTable();
table.Columns.Add("FirstName", typeof(string));
table.Columns.Add("Age", typeof(int));
table.LoadCsv("people.csv"); // Age column ends up as int, not string
LoadCsvAsync offloads the load via Task.Run, since neither CsvHelper's CsvDataReader nor
DataTable.Load has a true async path — it keeps a calling UI thread responsive but offers no
server-side throughput benefit, and a CancellationToken only prevents the load from starting, not
cancelling one already in progress.
API Reference
CsvFile
IEnumerable<T> Read<T>(string filePath, CsvOptions? options = null);
IAsyncEnumerable<T> ReadAsync<T>(
string filePath,
CsvOptions? options = null,
CancellationToken ct = default);
IEnumerable<T> ReadAnonymous<T>(string filePath, T anonymousTypeTemplate, CsvOptions? options = null);
IAsyncEnumerable<T> ReadAnonymousAsync<T>(
string filePath,
T anonymousTypeTemplate,
CsvOptions? options = null,
CancellationToken ct = default);
IEnumerable<IDictionary<string, string>> ReadDynamic(string filePath, CsvOptions? options = null);
IAsyncEnumerable<IDictionary<string, string>> ReadDynamicAsync(
string filePath,
CsvOptions? options = null,
CancellationToken ct = default);
void Write<T>(
string filePath,
IEnumerable<T> items,
CsvOptions? options = null);
Task WriteAsync<T>(
string filePath,
IEnumerable<T> items,
CsvOptions? options = null,
CancellationToken ct = default);
void WriteDynamic(
string filePath,
IEnumerable<string> headers,
IEnumerable<IDictionary<string, object?>> items,
CsvOptions? options = null);
Task WriteDynamicAsync(
string filePath,
IEnumerable<string> headers,
IEnumerable<IDictionary<string, object?>> items,
CsvOptions? options = null,
CancellationToken ct = default);
CsvStream
Same members as CsvFile, with Stream stream in place of string filePath.
None of them close or dispose the stream you pass in.
DataTableExtensions
DataTable LoadCsv(this DataTable table, string filePath, CsvOptions? options = null);
DataTable LoadCsv(this DataTable table, Stream stream, CsvOptions? options = null);
Task<DataTable> LoadCsvAsync(
this DataTable table,
string filePath,
CsvOptions? options = null,
CancellationToken ct = default);
Task<DataTable> LoadCsvAsync(
this DataTable table,
Stream stream,
CsvOptions? options = null,
CancellationToken ct = default);
All four return the same table instance passed in. The Stream overloads never close or dispose
the stream you pass in.
License
MIT © Computer Code Blue LLC
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- CsvHelper (>= 33.1.0)
- System.Linq.Async (>= 7.0.1)
-
net10.0
- CsvHelper (>= 33.1.0)
- System.Linq.Async (>= 7.0.1)
-
net6.0
- CsvHelper (>= 33.1.0)
- System.Linq.Async (>= 7.0.1)
-
net8.0
- CsvHelper (>= 33.1.0)
- System.Linq.Async (>= 7.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.