CsvHelper 15.0.5 Reference: Reading, Writing, Mapping, and Configuration
CsvHelper is a .NET library for high-performance CSV parsing and generation, targeting .NET Stanadrd 2.0.
Modules
- CsvHelper: Core API for parsing and writing CSV.
- CsvHelper.Configuration: Runtime configuration surface.
- CsvHelper.Configuration.Attributes: Attribute-based model configuration.
- CsvHelper.Expressions: Internals for generated expression trees.
- CsvHelper.TypeConversion: Built-in and extensible type converters.
Reading
Sample model
public class Foo
{
public int ID { get; set; }
public string Name { get; set; }
}
Sample CSV
ID,Name
1,Tom
2,Jerry
Read all records (streamed)
using var sr = new StreamReader("foo.csv");
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture);
IEnumerable<Foo> rows = csv.GetRecords<Foo>();
// Enumerate to process; not materialized until iterated.
foreach (var r in rows)
{
// handle r
}
Blank lines are skipped by default; lines containing only whitespace are not considered blank and can trigger errors. CSV exported by spreadsheet tools may produce lines containing only delimiters; treat those as bad data or adjust configuration if needed.
Read record-by-record
using var sr = new StreamReader("foo.csv");
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture);
while (csv.Read())
{
var item = csv.GetRecord<Foo>();
// use item
}
GetRecords<T>() already streams via yield; explicit Read/GetRecord is only needed when you want fine-grained control per row.
Read individual fields
using var sr = new StreamReader("foo.csv");
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture);
csv.Read(); // advance to header row
csv.ReadHeader(); // cache header names
while (csv.Read())
{
int id = csv.GetField<int>(0); // by index
string name = csv.GetField<string>("Name"); // by header
}
- With out ReadHeader, lookups by header name will fail.
- TryGetField avoids exceptions when data is malformed or missing.
if (csv.TryGetField(0, out int id))
{
// id available
}
Writing
Write all records
var items = new List<Foo>
{
new Foo { ID = 1, Name = "Tom" },
new Foo { ID = 2, Name = "Jerry" },
};
using var sw = new StreamWriter("foo.csv");
using var csv = new CsvWriter(sw, CultureInfo.InvariantCulture);
csv.WriteRecords(items); // writes header + all rows
Write one record at a time
using var sw = new StreamWriter("foo.csv");
using var csv = new CsvWriter(sw, CultureInfo.InvariantCulture);
// Optionally write a header first
csv.WriteHeader<Foo>();
csv.NextRecord();
foreach (var row in items)
{
csv.WriteRecord(row);
csv.NextRecord(); // commit line break per record
}
Write field-by-field
using var sw = new StreamWriter("foo.csv");
using var csv = new CsvWriter(sw, CultureInfo.InvariantCulture);
csv.WriteHeader<Foo>();
csv.NextRecord();
foreach (var row in items)
{
csv.WriteField(row.ID);
csv.WriteField(row.Name);
csv.NextRecord();
}
Attributes (Annotations)
Index
Control column order and support headerless files.
public class Foo
{
[Index(0)]
public int ID { get; set; }
[Index(1)]
public string Name { get; set; }
}
using var sr = new StreamReader("foo.csv");
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture)
{
Configuration = { HasHeaderRecord = false }
};
var list = csv.GetRecords<Foo>().ToList();
- Set HasHeaderRecord = false to avoid skipping the first line when no header exists.
- When writing, columns follow Index order. Disable header if you don’t want one emitted.
Name
Bind a property to a differently named header.
public class Foo
{
[Name("id")]
public int ID { get; set; }
[Name("name")]
public string Name { get; set; }
}
NameIndex
Disambiguate duplicate header names.
public class Person
{
[Name("Name"), NameIndex(0)]
public string FirstName { get; set; }
[Name("Name"), NameIndex(1)]
public string LastName { get; set; }
}
Ignore
Exclude a property from read/write.
public class WithInternal
{
public string Visible { get; set; }
[Ignore]
public string Hidden { get; set; }
}
Optional
Skip a property when no matching column exists.
public class MaybeExtended
{
public string Core { get; set; }
[Optional]
public string Remarks { get; set; }
}
Default
Provide a default value when the incoming field is empty.
public class Defaults
{
[Default("N/A")] // applied on read when field is empty
public string Note { get; set; }
}
Default affects reads only; it does not replace nulls on write.
NullValues
Map specific literal values to null during reads.
public class Nullables
{
[NullValues("None", "none", "Null", "null")]
public string AliasOfNull { get; set; }
}
Empty CSV fields read as "" (empty string) by default. With NullValues, matching tokens are converted to null. If combined with Default, the null mapping takes precedence and the Default may not apply.
NullValues does not affect writes.
Constant
Force a fixed value for a property regardless of CSV content during both read and write.
public class FixedValue
{
[Constant("Always")]
public string Tag { get; set; }
}
Format
Specify formatting/parsing patterns for conversion.
public class Formats
{
[Format("0.00")]
public decimal Amount { get; set; }
[Format("yyyy-MM-dd HH:mm:ss")]
public DateTime Timestamp { get; set; }
}
BooleanTrueValues and BooleanFalseValues
Define custom boolean literals.
public class Flags
{
[BooleanTrueValues("yes")]
[BooleanFalseValues("no")]
public bool Vip { get; set; }
}
NumberStyles
Control numeric parsing styles; combine with Format for write-side formatting.
public class HexData
{
[Format("X2")]
[NumberStyles(NumberStyles.HexNumber)]
public int Data { get; set; }
}
NumberStyles (e.g., HexNumber/AllowHexSpecifier) applies on read. Use Format to control write representation.
Mapping (ClassMap)
Use ClassMap when attributes aren’t possible or desired.
public class Foo2
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
public DateTime JoinTime { get; set; }
public string Msg { get; set; }
public string Msg2 { get; set; }
public bool Vip { get; set; }
public string Remarks { get; set; }
public string None { get; set; }
public int Data { get; set; }
}
public class Foo2Map : ClassMap<Foo2>
{
public Foo2Map()
{
Map(x => x.ID).Index(0).Name("id");
Map(x => x.Name).Index(1).Name("name");
Map(x => x.Amount).TypeConverterOption.Format("0.00");
Map(x => x.JoinTime).TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
Map(x => x.Msg).Default("Hello");
Map(x => x.Msg2).Ignore();
Map(x => x.Vip)
.TypeConverterOption.BooleanValues(true, true, new[] { "yes" })
.TypeConverterOption.BooleanValues(false, true, new[] { "no" });
Map(x => x.Remarks).Optional();
Map(x => x.None).TypeConverterOption.NullValues("None", "none", "Null", "null");
Map(x => x.Data)
.TypeConverterOption.NumberStyles(NumberStyles.HexNumber)
.TypeConverterOption.Format("X2");
}
}
Register the map before reading/writing.
csv.Configuration.RegisterClassMap<Foo2Map>();
ConvertUsing
Apply custom conversion logic.
// constant value
Map(m => m.Amount).ConvertUsing(row => 3.00m);
// compose full name from multiple columns
Map(m => m.Name).ConvertUsing(row =>
{
var first = row.GetField<string>("FirstName");
var last = row.GetField<string>("LastName");
return $"{first} {last}";
});
// map to a collection
Map(m => m.Msg2).ConvertUsing(row => new List<string> { row.GetField<string>("Msg") }.First());
Configuration
Delimiter
csv.Configuration.Delimiter = ",";
HasHeaderRecord
Use or skip the first row as header.
csv.Configuration.HasHeaderRecord = false;
IgnoreBlankLines
Control whether to skip empty lines (true by default).
csv.Configuration.IgnoreBlankLines = false;
Whitespace-only row are not considered blank.
AllowComments
Enable comment parsing.
csv.Configuration.AllowComments = true;
Commment
Choose the comment prefix character (default '#').
csv.Configuration.Comment = '/';
BadDataFound
Hook for reporting malformed data.
csv.Configuration.BadDataFound = context =>
{
// context.RawRecord, context.Field, context.RawRow, etc.
// log or collect diagnostics
};
IgnoreQuotes
Treat quotes as ordinary characters when parsing.
csv.Configuration.IgnoreQuotes = true;
- Default is false: quotes must be escaped by doubling (e.g., """ for a literal ").
- When true, quote characters are not treated specially during parsing.
- CsvWriter does not expose IgnoreQuotes; it will escape quotes as needed on write.
TrimOptions
Trim whitespace.
csv.Configuration.TrimOptions = TrimOptions.Trim;
PrepareHeaderForMatch
Normalize headers and property names before matching.
csv.Configuration.PrepareHeaderForMatch = (header, index) => header.Replace(" ", string.Empty).ToLowerInvariant();