CsvHelper 22 renames ParserMode to CsvMode, because the writer now uses the mode too. ShouldQuote gets the type of the field, so you can decide by type what to quote. Enums are read case-sensitively unless you set the new EnumIgnoreCase option. The target frameworks don't change.

CsvMode

ParserMode is renamed CsvMode, with the same members: RFC4180, Escape and NoEscape.

Before · v21.3.1
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Mode = ParserMode.Escape,
    Escape = '\\',
};
After · v22.0.0
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Mode = CsvMode.Escape,
    Escape = '\\',
};

The writer uses the mode now, so writing with Escape or NoEscape gives different output: see Behavior changes.

Quoting

ShouldQuote gets the type of the field as its second parameter, before the writer. The default function, ConfigurationFunctions.ShouldQuote, takes it too.

Before · v21.3.1
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    ShouldQuote = (field, row) => true,
};
After · v22.0.0
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    ShouldQuote = (field, fieldType, row) => true,
};

With the type, you can quote text and leave numbers and dates alone:

var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    ShouldQuote = (field, fieldType, row) => fieldType == typeof(string),
};

When you write records, fieldType is string. WriteRecord and WriteRecords pass string for every field, except for dynamic records; WriteField<T> passes the type of the value. CsvHelper 24 passes the type of each member (see Migrating from 23 to 24).

Enums

Enum names, and the names you give enum members with [Name], are matched case-sensitively when reading. In 21, the names were matched ignoring case. To keep that, set EnumIgnoreCase on the member map.

Before · v21.3.1
public enum Status
{
    Active,
    Closed,
}

public class Account
{
    public int Id { get; set; }
    public Status Status { get; set; }
}

public sealed class AccountMap : ClassMap<Account>
{
    public AccountMap()
    {
        Map(m => m.Id);
        Map(m => m.Status);
    }
}
After · v22.0.0
public enum Status
{
    Active,
    Closed,
}

public class Account
{
    public int Id { get; set; }
    public Status Status { get; set; }
}

public sealed class AccountMap : ClassMap<Account>
{
    public AccountMap()
    {
        Map(m => m.Id);
        Map(m => m.Status).TypeConverterOption.EnumIgnoreCase();
    }
}

To ignore case for every member of type Status, set it on the type's options instead: csv.Context.TypeConverterOptionsCache.GetOptions<Status>().EnumIgnoreCase = true.

Use 22.1.0 or later. In 22.0.0, the option doesn't reach the converter when reading, so it has no effect. 22.1.0 fixes this and adds the [EnumIgnoreCase] attribute, for when you map with attributes.

Configuration

QuoteString and DoubleQuoteString are removed from CsvConfiguration. Build them from Quote and Escape.

Before · v21.3.1
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
var quote = config.QuoteString;
var escapedQuote = config.DoubleQuoteString;
After · v22.0.0
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
var quote = config.Quote.ToString();
var escapedQuote = $"{config.Escape}{config.Quote}";

If you implement the configuration interfaces, add ProcessFieldBufferSize to IParserConfiguration and Mode to IWriterConfiguration.

Behavior changes

  • The writer uses Mode. In 21 it ignored the mode and always quoted fields as RFC 4180 does. In 22, CsvMode.Escape puts Escape before each quote and before each character of a delimiter or NewLine in a field instead of quoting it (an Escape character in the field isn't escaped), and CsvMode.NoEscape writes fields as they are. In these two modes, the result of ShouldQuote isn't used.
  • The configuration is checked when it is used. Delimiter, Quote and Escape checked their values when you set them, and threw a ConfigurationException. In 22, CsvConfiguration.Validate() checks them, with NewLine, when CsvParser or CsvWriter is created (and so CsvReader). Until 26.0.1, the exception has an empty message. Call Validate() yourself to check a configuration earlier.

All API changes

Every change to the public API since v21.3.1: 6 added, 6 changed, 3 removed (8 breaking). Open a type to see its changes.

CsvHelper

ShouldQuotedelegate1 changed

ShouldQuote

The declaration changed.

Before
public delegate bool ShouldQuote(string field, IWriterRow row)
After
public delegate bool ShouldQuote(string field, Type fieldType, IWriterRow row)

Added types (1)

Removed types (1)

ParserMode

CsvHelper.Configuration

ConfigurationFunctionsstatic class1 breaking1 changed

ShouldQuote(string, Type, IWriterRow)Breaking

The parameters changed from (string, IWriterRow) to (string, Type, IWriterRow).

Before
public static bool ShouldQuote(string field, IWriterRow row)
After
public static bool ShouldQuote(string field, Type fieldType, IWriterRow row)
CsvConfigurationrecord5 breaking2 removed3 changed1 added

CsvConfiguration(CultureInfo, bool?, BadDataFound, int?, bool?, char?, bool?, string, bool?, IComparer<string>, Encoding, char?, GetConstructor, GetDynamicPropertyName, bool?, HeaderValidated, bool?, bool?, bool?, char[], char?, bool?, bool?, MemberTypes?, MissingFieldFound, CsvMode?, string, PrepareHeaderForMatch, int?, char?, ReadingExceptionOccurred, ReferenceHeaderPrefix, bool?, ShouldQuote, ShouldSkipRecord, ShouldUseConstructorParameters, TrimOptions?, bool?, char[])Breaking

The parameters changed from (CultureInfo, bool?, BadDataFound, int?, bool?, char?, bool?, string, bool?, IComparer<string>, Encoding, char?, GetConstructor, GetDynamicPropertyName, bool?, HeaderValidated, bool?, bool?, bool?, char[], char?, bool?, bool?, MemberTypes?, MissingFieldFound, ParserMode?, string, PrepareHeaderForMatch, int?, char?, ReadingExceptionOccurred, ReferenceHeaderPrefix, bool?, ShouldQuote, ShouldSkipRecord, ShouldUseConstructorParameters, TrimOptions?, bool?, char[]) to (CultureInfo, bool?, BadDataFound, int?, bool?, char?, bool?, string, bool?, IComparer<string>, Encoding, char?, GetConstructor, GetDynamicPropertyName, bool?, HeaderValidated, bool?, bool?, bool?, char[], char?, bool?, bool?, MemberTypes?, MissingFieldFound, CsvMode?, string, PrepareHeaderForMatch, int?, char?, ReadingExceptionOccurred, ReferenceHeaderPrefix, bool?, ShouldQuote, ShouldSkipRecord, ShouldUseConstructorParameters, TrimOptions?, bool?, char[]).

Before
public CsvConfiguration(CultureInfo cultureInfo, bool? allowComments = null, BadDataFound badDataFound = null, int? bufferSize = null, bool? cacheFields = null, char? comment = null, bool? countBytes = null, string delimiter = null, bool? detectColumnCountChanges = null, IComparer<string> dynamicPropertySort = null, Encoding encoding = null, char? escape = null, GetConstructor getConstructor = null, GetDynamicPropertyName getDynamicPropertyName = null, bool? hasHeaderRecord = null, HeaderValidated headerValidated = null, bool? ignoreBlankLines = null, bool? ignoreReferences = null, bool? includePrivateMembers = null, char[] injectionCharacters = null, char? injectionEscapeCharacter = null, bool? leaveOpen = null, bool? lineBreakInQuotedFieldIsBadData = null, MemberTypes? memberTypes = null, MissingFieldFound missingFieldFound = null, ParserMode? mode = null, string newLine = null, PrepareHeaderForMatch prepareHeaderForMatch = null, int? processFieldBufferSize = null, char? quote = null, ReadingExceptionOccurred readingExceptionOccurred = null, ReferenceHeaderPrefix referenceHeaderPrefix = null, bool? sanitizeForInjection = null, ShouldQuote shouldQuote = null, ShouldSkipRecord shouldSkipRecord = null, ShouldUseConstructorParameters shouldUseConstructorParameters = null, TrimOptions? trimOptions = null, bool? useNewObjectForNullReferenceMembers = null, char[] whiteSpaceChars = null)
After
public CsvConfiguration(CultureInfo cultureInfo, bool? allowComments = null, BadDataFound badDataFound = null, int? bufferSize = null, bool? cacheFields = null, char? comment = null, bool? countBytes = null, string delimiter = null, bool? detectColumnCountChanges = null, IComparer<string> dynamicPropertySort = null, Encoding encoding = null, char? escape = null, GetConstructor getConstructor = null, GetDynamicPropertyName getDynamicPropertyName = null, bool? hasHeaderRecord = null, HeaderValidated headerValidated = null, bool? ignoreBlankLines = null, bool? ignoreReferences = null, bool? includePrivateMembers = null, char[] injectionCharacters = null, char? injectionEscapeCharacter = null, bool? leaveOpen = null, bool? lineBreakInQuotedFieldIsBadData = null, MemberTypes? memberTypes = null, MissingFieldFound missingFieldFound = null, CsvMode? mode = null, string newLine = null, PrepareHeaderForMatch prepareHeaderForMatch = null, int? processFieldBufferSize = null, char? quote = null, ReadingExceptionOccurred readingExceptionOccurred = null, ReferenceHeaderPrefix referenceHeaderPrefix = null, bool? sanitizeForInjection = null, ShouldQuote shouldQuote = null, ShouldSkipRecord shouldSkipRecord = null, ShouldUseConstructorParameters shouldUseConstructorParameters = null, TrimOptions? trimOptions = null, bool? useNewObjectForNullReferenceMembers = null, char[] whiteSpaceChars = null)

ModeBreaking

The type changed from ParserMode to CsvMode.

Before
public virtual ParserMode Mode { get; init; }
After
public virtual CsvMode Mode { get; init; }

NewLineBreaking

The init accessor was removed.

Before
public virtual string NewLine { get; init; }
After
public virtual string NewLine { get; set; }

Removed

DoubleQuoteStringQuoteString

Added

Validate()
IParserConfigurationinterface1 breaking1 changed1 added

ModeBreaking

The type changed from ParserMode to CsvMode.

Before
ParserMode Mode { get; }
After
CsvMode Mode { get; }

Added

ProcessFieldBufferSize
IWriterConfigurationinterface1 added

Added

Mode
MemberMapTypeConverterOptionclass1 added

Added

EnumIgnoreCase(bool)

CsvHelper.TypeConversion

TypeConverterOptionsclass1 added

Added

EnumIgnoreCase