CsvHelper 13 makes you choose the culture. Without a configuration, the reader, writer, parser and serializer used the machine's current culture; now their constructors require a CultureInfo, and so does the configuration, which is renamed CsvConfiguration. The culture reads and writes numbers and dates, and gives the default delimiter. 13 also adds GetRecordsAsync and WriteRecordsAsync, a choice of line endings, mapping attributes of your own, and a .NET Standard 2.1 build next to .NET Standard 2.0 and .NET Framework 4.5 and 4.7.

Creating a reader or writer

Pass the culture as the second argument:

Before · v12.3.2
using var csv = new CsvReader(reader);
After · v13.0.0
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);

Which culture? 12 used CultureInfo.CurrentCulture, both for values and for the delimiter, which was the culture's list separator (; in many European cultures). Pass CultureInfo.CurrentCulture to keep that, or CultureInfo.InvariantCulture (, between fields, . in numbers) for files that shouldn't depend on the settings of the machine that reads or writes them.

Before After Notes
new CsvReader(textReader) new CsvReader(textReader, culture)
new CsvReader(textReader, leaveOpen) new CsvReader(textReader, culture, leaveOpen)
new CsvWriter(textWriter) new CsvWriter(textWriter, culture) The culture also chooses the line ending: see Behavior changes.
new CsvWriter(textWriter, leaveOpen) new CsvWriter(textWriter, culture, leaveOpen)
new CsvParser(...), new CsvSerializer(...) The same, with the culture second
factory.CreateParser(textReader), CreateReader, CreateWriter factory.CreateParser(textReader, culture), and so on Also on IFactory.

Configuration

Configuration is renamed CsvConfiguration, so that the class no longer has the name of its namespace, CsvHelper.Configuration. It has no parameterless constructor: pass the culture.

Before · v12.3.2
var config = new CsvHelper.Configuration.Configuration
{
    Delimiter = ";",
    HasHeaderRecord = false,
};
using var csv = new CsvReader(reader, config);
After · v13.0.0
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Delimiter = ";",
    HasHeaderRecord = false,
};
using var csv = new CsvReader(reader, config);

The constructor sets Delimiter to the culture's list separator, as new Configuration(culture) did in 12. Every other place that took a Configuration takes a CsvConfiguration: the reader and writer constructors, ReadingContext, WritingContext, ClassMapCollection, IFactory, and the protected AutoMapMembers and AutoMapConstructorParameters of ClassMap.

Class maps

AutoMap() takes the culture, or a configuration:

Before · v12.3.2
public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        AutoMap();
        Map(m => m.Name).Name("Foo Name");
    }
}
After · v13.0.0
public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        AutoMap(CultureInfo.InvariantCulture);
        Map(m => m.Name).Name("Foo Name");
    }
}

AutoMap(culture) auto maps with a new CsvConfiguration for that culture. When your configuration changes how members are found, such as MemberTypes or IncludePrivateMembers, pass it instead: AutoMap(config).

Mapping attributes

The mapping attributes are applied through two interfaces, IMemberMapper and IMemberReferenceMapper, instead of a fixed list of attributes. Each one configures the MemberMap (or MemberReferenceMap) of the member it is on, so an attribute of your own can configure a member the way a class map does:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class YesNoAttribute : Attribute, IMemberMapper
{
    public void ApplyTo(MemberMap memberMap)
    {
        memberMap.Data.TypeConverterOptions.BooleanTrueValues.Add("yes");
        memberMap.Data.TypeConverterOptions.BooleanFalseValues.Add("no");
    }
}

public class Foo
{
    [YesNo]
    public bool IsActive { get; set; }
}

Attributes are read when a type is auto mapped, the built-in ones as before.

Async records

GetRecordsAsync<T>() reads records as an IAsyncEnumerable<T>, and WriteRecordsAsync writes them with asynchronous calls:

public class Foo
{
    public int Id { get; set; }
}

using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    await foreach (var foo in csv.GetRecordsAsync<Foo>())
    {
        // ...
    }
}

using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
    await csv.WriteRecordsAsync(new List<Foo> { new Foo { Id = 1 } });
}
  • Frameworks: GetRecordsAsync, its overloads and EnumerateRecordsAsync are in the .NET Standard and .NET Framework 4.7 builds, not in the .NET Framework 4.5 build. On .NET Standard 2.0 and .NET Framework 4.7, IAsyncEnumerable<T> comes from the Microsoft.Bcl.AsyncInterfaces package, a new dependency.
  • Your own IReader or IWriter: the interfaces have the new methods, so an implementation needs them too.

Behavior changes

The writer ended records with \r\n in 12 (since 12.1.3). In 13 the line ending is the new NewLine setting, and its default depends on the culture of the configuration: \r\n for CultureInfo.InvariantCulture, and Environment.NewLine for any other culture, which is \n on Linux and macOS. To write \r\n with another culture, set it:

var config = new CsvConfiguration(CultureInfo.CurrentCulture)
{
    NewLine = NewLine.CRLF,
};
using var csv = new CsvWriter(writer, config);

NewLine is CRLF, CR, LF or Environment.

All API changes

Every change to the public API since v12.3.2: 47 added, 41 changed, 13 removed (38 breaking). New target framework: netstandard2.1. Open a type to see its changes.

CsvHelper

CsvFieldReaderclass2 breaking2 changed

CsvFieldReader(TextReader, CsvConfiguration)Breaking

The parameters changed from (TextReader, Configuration) to (TextReader, CsvConfiguration).

Before
public CsvFieldReader(TextReader reader, Configuration configuration)
After
public CsvFieldReader(TextReader reader, CsvConfiguration configuration)

CsvFieldReader(TextReader, CsvConfiguration, bool)Breaking

The parameters changed from (TextReader, Configuration, bool) to (TextReader, CsvConfiguration, bool).

Before
public CsvFieldReader(TextReader reader, Configuration configuration, bool leaveOpen)
After
public CsvFieldReader(TextReader reader, CsvConfiguration configuration, bool leaveOpen)
CsvParserclass4 breaking1 removed3 changed1 added

CsvParser(TextReader, CsvConfiguration)Breaking

The parameters changed from (TextReader, Configuration) to (TextReader, CsvConfiguration).

Before
public CsvParser(TextReader reader, Configuration configuration)
After
public CsvParser(TextReader reader, CsvConfiguration configuration)

CsvParser(TextReader, CsvConfiguration, bool)Breaking

The parameters changed from (TextReader, Configuration, bool) to (TextReader, CsvConfiguration, bool).

Before
public CsvParser(TextReader reader, Configuration configuration, bool leaveOpen)
After
public CsvParser(TextReader reader, CsvConfiguration configuration, bool leaveOpen)

CsvParser(TextReader, CultureInfo)Breaking

The parameters changed from (TextReader, bool) to (TextReader, CultureInfo).

Before
public CsvParser(TextReader reader, bool leaveOpen)
After
public CsvParser(TextReader reader, CultureInfo culture)

Removed

CsvParser(TextReader)

Added

CsvParser(TextReader, CultureInfo, bool)
CsvReaderclass4 breaking1 removed3 changed5 added

CsvReader(TextReader, CsvConfiguration)Breaking

The parameters changed from (TextReader, Configuration) to (TextReader, CsvConfiguration).

Before
public CsvReader(TextReader reader, Configuration configuration)
After
public CsvReader(TextReader reader, CsvConfiguration configuration)

CsvReader(TextReader, CsvConfiguration, bool)Breaking

The parameters changed from (TextReader, Configuration, bool) to (TextReader, CsvConfiguration, bool).

Before
public CsvReader(TextReader reader, Configuration configuration, bool leaveOpen)
After
public CsvReader(TextReader reader, CsvConfiguration configuration, bool leaveOpen)

CsvReader(TextReader, CultureInfo)Breaking

The parameters changed from (TextReader, bool) to (TextReader, CultureInfo).

Before
public CsvReader(TextReader reader, bool leaveOpen)
After
public CsvReader(TextReader reader, CultureInfo culture)

Removed

CsvReader(TextReader)

Added

CsvReader(TextReader, CultureInfo, bool)EnumerateRecordsAsync<T>(T)GetRecordsAsync(Type)GetRecordsAsync<T>()GetRecordsAsync<T>(T)
CsvSerializerclass4 breaking1 removed3 changed1 added

CsvSerializer(TextWriter, CsvConfiguration)Breaking

The parameters changed from (TextWriter, Configuration) to (TextWriter, CsvConfiguration).

Before
public CsvSerializer(TextWriter writer, Configuration configuration)
After
public CsvSerializer(TextWriter writer, CsvConfiguration configuration)

CsvSerializer(TextWriter, CsvConfiguration, bool)Breaking

The parameters changed from (TextWriter, Configuration, bool) to (TextWriter, CsvConfiguration, bool).

Before
public CsvSerializer(TextWriter writer, Configuration configuration, bool leaveOpen)
After
public CsvSerializer(TextWriter writer, CsvConfiguration configuration, bool leaveOpen)

CsvSerializer(TextWriter, CultureInfo)Breaking

The parameters changed from (TextWriter, bool) to (TextWriter, CultureInfo).

Before
public CsvSerializer(TextWriter writer, bool leaveOpen)
After
public CsvSerializer(TextWriter writer, CultureInfo cultureInfo)

Removed

CsvSerializer(TextWriter)

Added

CsvSerializer(TextWriter, CultureInfo, bool)
CsvWriterclass4 breaking1 removed3 changed3 added

CsvWriter(TextWriter, CsvConfiguration)Breaking

The parameters changed from (TextWriter, Configuration) to (TextWriter, CsvConfiguration).

Before
public CsvWriter(TextWriter writer, Configuration configuration)
After
public CsvWriter(TextWriter writer, CsvConfiguration configuration)

CsvWriter(TextWriter, CsvConfiguration, bool)Breaking

The parameters changed from (TextWriter, Configuration, bool) to (TextWriter, CsvConfiguration, bool).

Before
public CsvWriter(TextWriter writer, Configuration configuration, bool leaveOpen)
After
public CsvWriter(TextWriter writer, CsvConfiguration configuration, bool leaveOpen)

CsvWriter(TextWriter, CultureInfo)Breaking

The parameters changed from (TextWriter, bool) to (TextWriter, CultureInfo).

Before
public CsvWriter(TextWriter writer, bool leaveOpen)
After
public CsvWriter(TextWriter writer, CultureInfo cultureInfo)

Removed

CsvWriter(TextWriter)

Added

CsvWriter(TextWriter, CultureInfo, bool)WriteRecordsAsync(IEnumerable)WriteRecordsAsync<T>(IEnumerable<T>)
Factoryclass6 breaking3 removed3 changed3 added

CreateParser(TextReader, CsvConfiguration)Breaking

The parameters changed from (TextReader, Configuration) to (TextReader, CsvConfiguration).

Before
public virtual IParser CreateParser(TextReader reader, Configuration configuration)
After
public virtual IParser CreateParser(TextReader reader, CsvConfiguration configuration)

CreateReader(TextReader, CsvConfiguration)Breaking

The parameters changed from (TextReader, Configuration) to (TextReader, CsvConfiguration).

Before
public virtual IReader CreateReader(TextReader reader, Configuration configuration)
After
public virtual IReader CreateReader(TextReader reader, CsvConfiguration configuration)

CreateWriter(TextWriter, CsvConfiguration)Breaking

The parameters changed from (TextWriter, Configuration) to (TextWriter, CsvConfiguration).

Before
public virtual IWriter CreateWriter(TextWriter writer, Configuration configuration)
After
public virtual IWriter CreateWriter(TextWriter writer, CsvConfiguration configuration)

Removed

CreateParser(TextReader)CreateReader(TextReader)CreateWriter(TextWriter)

Added

CreateParser(TextReader, CultureInfo)CreateReader(TextReader, CultureInfo)CreateWriter(TextWriter, CultureInfo)
IFactoryinterface6 breaking3 removed3 changed3 added

CreateParser(TextReader, CsvConfiguration)Breaking

The parameters changed from (TextReader, Configuration) to (TextReader, CsvConfiguration).

Before
IParser CreateParser(TextReader reader, Configuration configuration)
After
IParser CreateParser(TextReader reader, CsvConfiguration configuration)

CreateReader(TextReader, CsvConfiguration)Breaking

The parameters changed from (TextReader, Configuration) to (TextReader, CsvConfiguration).

Before
IReader CreateReader(TextReader reader, Configuration configuration)
After
IReader CreateReader(TextReader reader, CsvConfiguration configuration)

CreateWriter(TextWriter, CsvConfiguration)Breaking

The parameters changed from (TextWriter, Configuration) to (TextWriter, CsvConfiguration).

Before
IWriter CreateWriter(TextWriter writer, Configuration configuration)
After
IWriter CreateWriter(TextWriter writer, CsvConfiguration configuration)

Removed

CreateParser(TextReader)CreateReader(TextReader)CreateWriter(TextWriter)

Added

CreateParser(TextReader, CultureInfo)CreateReader(TextReader, CultureInfo)CreateWriter(TextWriter, CultureInfo)
IReaderinterface4 added

Added

EnumerateRecordsAsync<T>(T)GetRecordsAsync(Type)GetRecordsAsync<T>()GetRecordsAsync<T>(T)
IWriterinterface2 added

Added

WriteRecordsAsync(IEnumerable)WriteRecordsAsync<T>(IEnumerable<T>)
ReadingContextclass1 breaking1 changed

ReadingContext(TextReader, CsvConfiguration, bool)Breaking

The parameters changed from (TextReader, Configuration, bool) to (TextReader, CsvConfiguration, bool).

Before
public ReadingContext(TextReader reader, Configuration configuration, bool leaveOpen)
After
public ReadingContext(TextReader reader, CsvConfiguration configuration, bool leaveOpen)
WritingContextclass1 breaking1 changed

WritingContext(TextWriter, CsvConfiguration, bool)Breaking

The parameters changed from (TextWriter, Configuration, bool) to (TextWriter, CsvConfiguration, bool).

Before
public WritingContext(TextWriter writer, Configuration configuration, bool leaveOpen)
After
public WritingContext(TextWriter writer, CsvConfiguration configuration, bool leaveOpen)

CsvHelper.Configuration

ClassMapclass4 breaking2 removed2 changed2 added

AutoMapConstructorParameters(ClassMap, CsvConfiguration, LinkedList<Type>, int)Breaking

The parameters changed from (ClassMap, Configuration, LinkedList<Type>, int) to (ClassMap, CsvConfiguration, LinkedList<Type>, int).

Before
protected virtual void AutoMapConstructorParameters(ClassMap map, Configuration configuration, LinkedList<Type> mapParents, int indexStart = 0)
After
protected virtual void AutoMapConstructorParameters(ClassMap map, CsvConfiguration configuration, LinkedList<Type> mapParents, int indexStart = 0)

AutoMapMembers(ClassMap, CsvConfiguration, LinkedList<Type>, int)Breaking

The parameters changed from (ClassMap, Configuration, LinkedList<Type>, int) to (ClassMap, CsvConfiguration, LinkedList<Type>, int).

Before
protected virtual void AutoMapMembers(ClassMap map, Configuration configuration, LinkedList<Type> mapParents, int indexStart = 0)
After
protected virtual void AutoMapMembers(ClassMap map, CsvConfiguration configuration, LinkedList<Type> mapParents, int indexStart = 0)

Removed

AutoMap()AutoMap(Configuration)

Added

AutoMap(CsvConfiguration)AutoMap(CultureInfo)
ClassMapCollectionclass1 breaking1 changed

ClassMapCollection(CsvConfiguration)Breaking

The parameters changed from (Configuration) to (CsvConfiguration).

Before
public ClassMapCollection(Configuration configuration)
After
public ClassMapCollection(CsvConfiguration configuration)
ISerializerConfigurationinterface2 added

Added

NewLineNewLineString

Added types (3)

CsvConfigurationNewLineNewLines

Removed types (1)

Configuration

CsvHelper.Configuration.Attributes

BooleanFalseValuesAttributeclass1 changed1 added

BooleanFalseValuesAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class BooleanFalseValuesAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class BooleanFalseValuesAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
BooleanTrueValuesAttributeclass1 changed1 added

BooleanTrueValuesAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class BooleanTrueValuesAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class BooleanTrueValuesAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
ConstantAttributeclass1 changed1 added

ConstantAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class ConstantAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class ConstantAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
CultureInfoAttributeclass1 changed1 added

CultureInfoAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class CultureInfoAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class CultureInfoAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
DateTimeStylesAttributeclass1 changed1 added

DateTimeStylesAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class DateTimeStylesAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class DateTimeStylesAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
DefaultAttributeclass1 changed1 added

DefaultAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class DefaultAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class DefaultAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
FormatAttributeclass1 changed1 added

FormatAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class FormatAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class FormatAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
HeaderPrefixAttributeclass1 changed1 added

HeaderPrefixAttribute

The type now implements IMemberReferenceMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class HeaderPrefixAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class HeaderPrefixAttribute : Attribute, IMemberReferenceMapper

Added

ApplyTo(MemberReferenceMap)
IgnoreAttributeclass1 changed1 added

IgnoreAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class IgnoreAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class IgnoreAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
IndexAttributeclass1 changed1 added

IndexAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class IndexAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class IndexAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
NameAttributeclass1 changed1 added

NameAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NameAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NameAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
NameIndexAttributeclass1 changed1 added

NameIndexAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NameIndexAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NameIndexAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
NullValuesAttributeclass1 changed1 added

NullValuesAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NullValuesAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NullValuesAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
NumberStylesAttributeclass1 changed1 added

NumberStylesAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NumberStylesAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NumberStylesAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
OptionalAttributeclass1 changed1 added

OptionalAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class OptionalAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class OptionalAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)
TypeConverterAttributeclass1 changed1 added

TypeConverterAttribute

The type now implements IMemberMapper.

Before
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class TypeConverterAttribute : Attribute
After
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class TypeConverterAttribute : Attribute, IMemberMapper

Added

ApplyTo(MemberMap)

Added types (2)

IMemberMapperIMemberReferenceMapper