CsvHelper 23 changes every callback to take one argument: a struct with a property for each value the callback used to get as a parameter. New values can then be added without breaking your code, and BadDataFound already gets one, the bad field. The properties of CsvConfiguration have setters again (they were init from 20 to 22) so that Visual Basic can set them, and the constructor that took every setting as an optional argument is removed. The target frameworks don't change.

Configuration

The constructor with every setting as an optional argument, added in 21.0.5 for Visual Basic, is removed. The properties have setters now, so set them in an object initializer, or one at a time.

Before · v22.1.2
var config = new CsvConfiguration(CultureInfo.InvariantCulture, delimiter: ";", hasHeaderRecord: false);
After · v23.0.0
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Delimiter = ";",
    HasHeaderRecord = false,
};

Code that sets the properties in an object initializer doesn't change. Set them before you create the reader or writer: the reader, the parser and the writer copy most settings when they are created.

Callbacks

Each callback gets an args struct named after it (BadDataFoundArgs, PrepareHeaderForMatchArgs and so on), with a property for each former parameter.

Before · v22.1.2
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    PrepareHeaderForMatch = (header, fieldIndex) => header.ToLower(),
    ShouldSkipRecord = record => record.All(string.IsNullOrWhiteSpace),
    MissingFieldFound = (headerNames, index, context) => Console.WriteLine($"Missing field {index}"),
    BadDataFound = context => Console.WriteLine($"Bad data: {context.Parser.RawRecord}"),
    ReadingExceptionOccurred = exception =>
    {
        Console.WriteLine(exception.Message);
        return false;
    },
    GetDynamicPropertyName = (context, fieldIndex) => $"Field{fieldIndex}",
};
After · v23.0.0
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    PrepareHeaderForMatch = args => args.Header.ToLower(),
    ShouldSkipRecord = args => args.Record.All(string.IsNullOrWhiteSpace),
    MissingFieldFound = args => Console.WriteLine($"Missing field {args.Index}"),
    BadDataFound = args => Console.WriteLine($"Bad data: {args.Context.Parser.RawRecord}"),
    ReadingExceptionOccurred = args =>
    {
        Console.WriteLine(args.Exception.Message);
        return false;
    },
    GetDynamicPropertyName = args => $"Field{args.FieldIndex}",
};
Callback CsvHelper 22 parameters CsvHelper 23 args properties
BadDataFound context Field (new: the bad field), Context
GetConstructor classType ClassType
GetDynamicPropertyName context, fieldIndex Context, FieldIndex
HeaderValidated invalidHeaders, context InvalidHeaders, Context
MissingFieldFound headerNames, index, context HeaderNames, Index, Context
PrepareHeaderForMatch header, fieldIndex Header, FieldIndex
ReadingExceptionOccurred exception Exception
ReferenceHeaderPrefix memberType, memberName MemberType, MemberName
ShouldQuote field, fieldType, row Field, FieldType, Row
ShouldSkipRecord record Record
ShouldUseConstructorParameters parameterType ParameterType
Convert when reading row Row (ConvertFromStringArgs)
Convert when writing value Value (ConvertToStringArgs<TClass>)
Validate field Field

Setting a callback to null, such as MissingFieldFound = null, doesn't change.

Mapping

Convert and Validate on a member map take the args too.

Before · v22.1.2
public class Order
{
    public int Id { get; set; }
    public decimal Total { get; set; }
}

public sealed class OrderMap : ClassMap<Order>
{
    public OrderMap()
    {
        Map(m => m.Id).Validate(field => !string.IsNullOrEmpty(field));
        Map(m => m.Total)
            .Convert(row => row.GetField<decimal>("Net") + row.GetField<decimal>("Tax"))
            .Convert(order => order.Total.ToString("0.00"));
    }
}
After · v23.0.0
public class Order
{
    public int Id { get; set; }
    public decimal Total { get; set; }
}

public sealed class OrderMap : ClassMap<Order>
{
    public OrderMap()
    {
        Map(m => m.Id).Validate(args => !string.IsNullOrEmpty(args.Field));
        Map(m => m.Total)
            .Convert(args => args.Row.GetField<decimal>("Net") + args.Row.GetField<decimal>("Tax"))
            .Convert(args => args.Value.Total.ToString("0.00"));
    }
}

Calling a callback

The default functions in ConfigurationFunctions take the args too, so a callback that calls the default passes its args on.

Before · v22.1.2
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    // Quote what CsvHelper quotes, and numbers with leading zeros.
    ShouldQuote = (field, fieldType, row) => ConfigurationFunctions.ShouldQuote(field, fieldType, row) || (field != null && field.StartsWith("0")),
};
After · v23.0.0
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    // Quote what CsvHelper quotes, and numbers with leading zeros.
    ShouldQuote = args => ConfigurationFunctions.ShouldQuote(args) || (args.Field != null && args.Field.StartsWith("0")),
};

To call a callback yourself, in a test for example, create its args with their constructor.

Before · v22.1.2
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    PrepareHeaderForMatch = (header, fieldIndex) => header.Trim().ToLower(),
};
var name = config.PrepareHeaderForMatch(" Name ", 0);
After · v23.0.0
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    PrepareHeaderForMatch = args => args.Header.Trim().ToLower(),
};
var name = config.PrepareHeaderForMatch(new PrepareHeaderForMatchArgs(" Name ", 0));

Behavior changes

  • Flush flushes the TextWriter. CsvWriter.Flush() and FlushAsync() write the writer's buffer to the TextWriter and then flush the TextWriter too. NextRecord() and NextRecordAsync() write the buffer to the TextWriter without flushing it, as before, but no longer call Flush() and FlushAsync(): if your subclass overrides them to run after each record, override the new protected FlushBuffer() and FlushBufferAsync() instead. A subclass can also write text to the buffer with the new protected WriteToBuffer(string).

All API changes

Every change to the public API since v22.1.2: 17 added, 63 changed, 1 removed (50 breaking). Open a type to see its changes.

CsvHelper

BadDataFounddelegate1 changed

BadDataFound

The declaration changed.

Before
public delegate void BadDataFound(CsvContext context)
After
public delegate void BadDataFound(BadDataFoundArgs args)
ConvertFromString<TMember>delegate1 changed

ConvertFromString<TMember>

The declaration changed.

Before
public delegate TMember ConvertFromString<TMember>(IReaderRow row)
After
public delegate TMember ConvertFromString<TMember>(ConvertFromStringArgs args)
ConvertToString<TClass>delegate1 changed

ConvertToString<TClass>

The declaration changed.

Before
public delegate string ConvertToString<TClass>(TClass value)
After
public delegate string ConvertToString<TClass>(ConvertToStringArgs<TClass> args)
CsvContextclass2 breaking2 changed

TypeConverterCacheBreaking

The init accessor was removed.

Before
public virtual TypeConverterCache TypeConverterCache { get; init; }
After
public virtual TypeConverterCache TypeConverterCache { get; set; }

TypeConverterOptionsCacheBreaking

The init accessor was removed.

Before
public virtual TypeConverterOptionsCache TypeConverterOptionsCache { get; init; }
After
public virtual TypeConverterOptionsCache TypeConverterOptionsCache { get; set; }
CsvWriterclass3 added

Added

FlushBuffer()FlushBufferAsync()WriteToBuffer(string)
GetConstructordelegate1 changed

GetConstructor

The declaration changed.

Before
public delegate ConstructorInfo GetConstructor(Type classType)
After
public delegate ConstructorInfo GetConstructor(GetConstructorArgs args)
GetDynamicPropertyNamedelegate1 changed

GetDynamicPropertyName

The declaration changed.

Before
public delegate string GetDynamicPropertyName(CsvContext context, int fieldIndex)
After
public delegate string GetDynamicPropertyName(GetDynamicPropertyNameArgs args)
HeaderValidateddelegate1 changed

HeaderValidated

The declaration changed.

Before
public delegate void HeaderValidated(InvalidHeader[] invalidHeaders, CsvContext context)
After
public delegate void HeaderValidated(HeaderValidatedArgs args)
MissingFieldFounddelegate1 changed

MissingFieldFound

The declaration changed.

Before
public delegate void MissingFieldFound(string[] headerNames, int index, CsvContext context)
After
public delegate void MissingFieldFound(MissingFieldFoundArgs args)
PrepareHeaderForMatchdelegate1 changed

PrepareHeaderForMatch

The declaration changed.

Before
public delegate string PrepareHeaderForMatch(string header, int fieldIndex)
After
public delegate string PrepareHeaderForMatch(PrepareHeaderForMatchArgs args)
ReadingExceptionOccurreddelegate1 changed

ReadingExceptionOccurred

The declaration changed.

Before
public delegate bool ReadingExceptionOccurred(CsvHelperException exception)
After
public delegate bool ReadingExceptionOccurred(ReadingExceptionOccurredArgs args)
ReferenceHeaderPrefixdelegate1 changed

ReferenceHeaderPrefix

The declaration changed.

Before
public delegate string ReferenceHeaderPrefix(Type memberType, string memberName)
After
public delegate string ReferenceHeaderPrefix(ReferenceHeaderPrefixArgs args)
ShouldQuotedelegate1 changed

ShouldQuote

The declaration changed.

Before
public delegate bool ShouldQuote(string field, Type fieldType, IWriterRow row)
After
public delegate bool ShouldQuote(ShouldQuoteArgs args)
ShouldSkipRecorddelegate1 changed

ShouldSkipRecord

The declaration changed.

Before
public delegate bool ShouldSkipRecord(string[] record)
After
public delegate bool ShouldSkipRecord(ShouldSkipRecordArgs args)
ShouldUseConstructorParametersdelegate1 changed

ShouldUseConstructorParameters

The declaration changed.

Before
public delegate bool ShouldUseConstructorParameters(Type parameterType)
After
public delegate bool ShouldUseConstructorParameters(ShouldUseConstructorParametersArgs args)
Validatedelegate1 changed

Validate

The declaration changed.

Before
public delegate bool Validate(string field)
After
public delegate bool Validate(ValidateArgs args)

Added types (14)

BadDataFoundArgsConvertFromStringArgsConvertToStringArgs<TClass>GetConstructorArgsGetDynamicPropertyNameArgsHeaderValidatedArgsMissingFieldFoundArgsPrepareHeaderForMatchArgsReadingExceptionOccurredArgsReferenceHeaderPrefixArgsShouldQuoteArgsShouldSkipRecordArgsShouldUseConstructorParametersArgsValidateArgs

CsvHelper.Configuration

ConfigurationFunctionsstatic class10 breaking10 changed

BadDataFound(BadDataFoundArgs)Breaking

The parameters changed from (CsvContext) to (BadDataFoundArgs).

Before
public static void BadDataFound(CsvContext context)
After
public static void BadDataFound(BadDataFoundArgs args)

GetConstructor(GetConstructorArgs)Breaking

The parameters changed from (Type) to (GetConstructorArgs).

Before
public static ConstructorInfo GetConstructor(Type type)
After
public static ConstructorInfo GetConstructor(GetConstructorArgs args)

GetDynamicPropertyName(GetDynamicPropertyNameArgs)Breaking

The parameters changed from (CsvContext, int) to (GetDynamicPropertyNameArgs).

Before
public static string GetDynamicPropertyName(CsvContext context, int fieldIndex)
After
public static string GetDynamicPropertyName(GetDynamicPropertyNameArgs args)

HeaderValidated(HeaderValidatedArgs)Breaking

The parameters changed from (InvalidHeader[], CsvContext) to (HeaderValidatedArgs).

Before
public static void HeaderValidated(InvalidHeader[] invalidHeaders, CsvContext context)
After
public static void HeaderValidated(HeaderValidatedArgs args)

MissingFieldFound(MissingFieldFoundArgs)Breaking

The parameters changed from (string[], int, CsvContext) to (MissingFieldFoundArgs).

Before
public static void MissingFieldFound(string[] headerNames, int index, CsvContext context)
After
public static void MissingFieldFound(MissingFieldFoundArgs args)

PrepareHeaderForMatch(PrepareHeaderForMatchArgs)Breaking

The parameters changed from (string, int) to (PrepareHeaderForMatchArgs).

Before
public static string PrepareHeaderForMatch(string header, int index)
After
public static string PrepareHeaderForMatch(PrepareHeaderForMatchArgs args)

ReadingExceptionOccurred(ReadingExceptionOccurredArgs)Breaking

The parameters changed from (CsvHelperException) to (ReadingExceptionOccurredArgs).

Before
public static bool ReadingExceptionOccurred(CsvHelperException exception)
After
public static bool ReadingExceptionOccurred(ReadingExceptionOccurredArgs args)

ShouldQuote(ShouldQuoteArgs)Breaking

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

Before
public static bool ShouldQuote(string field, Type fieldType, IWriterRow row)
After
public static bool ShouldQuote(ShouldQuoteArgs args)

ShouldSkipRecord(ShouldSkipRecordArgs)Breaking

The parameters changed from (string[]) to (ShouldSkipRecordArgs).

Before
public static bool ShouldSkipRecord(string[] record)
After
public static bool ShouldSkipRecord(ShouldSkipRecordArgs args)

ShouldUseConstructorParameters(ShouldUseConstructorParametersArgs)Breaking

The parameters changed from (Type) to (ShouldUseConstructorParametersArgs).

Before
public static bool ShouldUseConstructorParameters(Type type)
After
public static bool ShouldUseConstructorParameters(ShouldUseConstructorParametersArgs args)
CsvConfigurationrecord38 breaking1 removed37 changed

AllowCommentsBreaking

The init accessor was removed.

Before
public virtual bool AllowComments { get; init; }
After
public virtual bool AllowComments { get; set; }

BadDataFoundBreaking

The init accessor was removed.

Before
public virtual BadDataFound BadDataFound { get; init; }
After
public virtual BadDataFound BadDataFound { get; set; }

BufferSizeBreaking

The init accessor was removed.

Before
public virtual int BufferSize { get; init; }
After
public virtual int BufferSize { get; set; }

CacheFieldsBreaking

The init accessor was removed.

Before
public virtual bool CacheFields { get; init; }
After
public virtual bool CacheFields { get; set; }

CommentBreaking

The init accessor was removed.

Before
public virtual char Comment { get; init; }
After
public virtual char Comment { get; set; }

CountBytesBreaking

The init accessor was removed.

Before
public virtual bool CountBytes { get; init; }
After
public virtual bool CountBytes { get; set; }

DelimiterBreaking

The init accessor was removed.

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

DetectColumnCountChangesBreaking

The init accessor was removed.

Before
public virtual bool DetectColumnCountChanges { get; init; }
After
public virtual bool DetectColumnCountChanges { get; set; }

DynamicPropertySortBreaking

The init accessor was removed.

Before
public virtual IComparer<string> DynamicPropertySort { get; init; }
After
public virtual IComparer<string> DynamicPropertySort { get; set; }

EncodingBreaking

The init accessor was removed.

Before
public virtual Encoding Encoding { get; init; }
After
public virtual Encoding Encoding { get; set; }

EscapeBreaking

The init accessor was removed.

Before
public virtual char Escape { get; init; }
After
public virtual char Escape { get; set; }

GetConstructorBreaking

The init accessor was removed.

Before
public virtual GetConstructor GetConstructor { get; init; }
After
public virtual GetConstructor GetConstructor { get; set; }

GetDynamicPropertyNameBreaking

The init accessor was removed.

Before
public virtual GetDynamicPropertyName GetDynamicPropertyName { get; init; }
After
public virtual GetDynamicPropertyName GetDynamicPropertyName { get; set; }

HasHeaderRecordBreaking

The init accessor was removed.

Before
public virtual bool HasHeaderRecord { get; init; }
After
public virtual bool HasHeaderRecord { get; set; }

HeaderValidatedBreaking

The init accessor was removed.

Before
public virtual HeaderValidated HeaderValidated { get; init; }
After
public virtual HeaderValidated HeaderValidated { get; set; }

IgnoreBlankLinesBreaking

The init accessor was removed.

Before
public virtual bool IgnoreBlankLines { get; init; }
After
public virtual bool IgnoreBlankLines { get; set; }

IgnoreReferencesBreaking

The init accessor was removed.

Before
public virtual bool IgnoreReferences { get; init; }
After
public virtual bool IgnoreReferences { get; set; }

IncludePrivateMembersBreaking

The init accessor was removed.

Before
public virtual bool IncludePrivateMembers { get; init; }
After
public virtual bool IncludePrivateMembers { get; set; }

InjectionCharactersBreaking

The init accessor was removed.

Before
public virtual char[] InjectionCharacters { get; init; }
After
public virtual char[] InjectionCharacters { get; set; }

InjectionEscapeCharacterBreaking

The init accessor was removed.

Before
public virtual char InjectionEscapeCharacter { get; init; }
After
public virtual char InjectionEscapeCharacter { get; set; }

LeaveOpenBreaking

The init accessor was removed.

Before
public virtual bool LeaveOpen { get; init; }
After
public virtual bool LeaveOpen { get; set; }

LineBreakInQuotedFieldIsBadDataBreaking

The init accessor was removed.

Before
public virtual bool LineBreakInQuotedFieldIsBadData { get; init; }
After
public virtual bool LineBreakInQuotedFieldIsBadData { get; set; }

MemberTypesBreaking

The init accessor was removed.

Before
public virtual MemberTypes MemberTypes { get; init; }
After
public virtual MemberTypes MemberTypes { get; set; }

MissingFieldFoundBreaking

The init accessor was removed.

Before
public virtual MissingFieldFound MissingFieldFound { get; init; }
After
public virtual MissingFieldFound MissingFieldFound { get; set; }

ModeBreaking

The init accessor was removed.

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

PrepareHeaderForMatchBreaking

The init accessor was removed.

Before
public virtual PrepareHeaderForMatch PrepareHeaderForMatch { get; init; }
After
public virtual PrepareHeaderForMatch PrepareHeaderForMatch { get; set; }

ProcessFieldBufferSizeBreaking

The init accessor was removed.

Before
public virtual int ProcessFieldBufferSize { get; init; }
After
public virtual int ProcessFieldBufferSize { get; set; }

QuoteBreaking

The init accessor was removed.

Before
public virtual char Quote { get; init; }
After
public virtual char Quote { get; set; }

ReadingExceptionOccurredBreaking

The init accessor was removed.

Before
public virtual ReadingExceptionOccurred ReadingExceptionOccurred { get; init; }
After
public virtual ReadingExceptionOccurred ReadingExceptionOccurred { get; set; }

ReferenceHeaderPrefixBreaking

The init accessor was removed.

Before
public virtual ReferenceHeaderPrefix ReferenceHeaderPrefix { get; init; }
After
public virtual ReferenceHeaderPrefix ReferenceHeaderPrefix { get; set; }

SanitizeForInjectionBreaking

The init accessor was removed.

Before
public virtual bool SanitizeForInjection { get; init; }
After
public virtual bool SanitizeForInjection { get; set; }

ShouldQuoteBreaking

The init accessor was removed.

Before
public ShouldQuote ShouldQuote { get; init; }
After
public ShouldQuote ShouldQuote { get; set; }

ShouldSkipRecordBreaking

The init accessor was removed.

Before
public virtual ShouldSkipRecord ShouldSkipRecord { get; init; }
After
public virtual ShouldSkipRecord ShouldSkipRecord { get; set; }

ShouldUseConstructorParametersBreaking

The init accessor was removed.

Before
public virtual ShouldUseConstructorParameters ShouldUseConstructorParameters { get; init; }
After
public virtual ShouldUseConstructorParameters ShouldUseConstructorParameters { get; set; }

TrimOptionsBreaking

The init accessor was removed.

Before
public virtual TrimOptions TrimOptions { get; init; }
After
public virtual TrimOptions TrimOptions { get; set; }

UseNewObjectForNullReferenceMembersBreaking

The init accessor was removed.

Before
public virtual bool UseNewObjectForNullReferenceMembers { get; init; }
After
public virtual bool UseNewObjectForNullReferenceMembers { get; set; }

WhiteSpaceCharsBreaking

The init accessor was removed.

Before
public virtual char[] WhiteSpaceChars { get; init; }
After
public virtual char[] WhiteSpaceChars { get; set; }

Removed

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[])