ComputerCodeBlue.Csv 1.3.0

dotnet add package ComputerCodeBlue.Csv --version 1.3.0
                    
NuGet\Install-Package ComputerCodeBlue.Csv -Version 1.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ComputerCodeBlue.Csv" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ComputerCodeBlue.Csv" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="ComputerCodeBlue.Csv" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ComputerCodeBlue.Csv --version 1.3.0
                    
#r "nuget: ComputerCodeBlue.Csv, 1.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ComputerCodeBlue.Csv@1.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ComputerCodeBlue.Csv&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=ComputerCodeBlue.Csv&version=1.3.0
                    
Install as a Cake Tool

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 library
  • tests/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-owned Stream
  • 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 as Dictionary<string, string>, keyed by header name, raw field values.
    • CsvFile.WriteDynamic(filePath, headers, items) / CsvStream.WriteDynamic(stream, headers, items) (plus async variants) — items are IDictionary<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.
  • DataTable support

    • dataTable.LoadCsv(filePath) / dataTable.LoadCsv(stream) (plus async variants) — load a CSV straight into a System.Data.DataTable via CsvHelper's CsvDataReader and DataTable.Load(IDataReader). An empty DataTable gets all-string columns from the CSV header; a DataTable that already has typed columns gets its values converted to those types.
  • CsvStream never closes the Stream you pass it — you own its lifetime.

  • Built on CsvHelper with sensible defaults (CultureInfo.InvariantCulture).

  • Optional CsvOptions parameter 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.0 123 8/2/2026
1.1.0 433 11/19/2025
1.0.4 146 10/12/2025
1.0.3 184 10/12/2025 1.0.3 is deprecated because it has critical bugs.
1.0.2 171 10/12/2025 1.0.2 is deprecated because it has critical bugs.
1.0.1 175 10/12/2025 1.0.1 is deprecated because it has critical bugs.
1.0.0 174 10/12/2025 1.0.0 is deprecated because it has critical bugs.