Migration guide
Migrating to v28.0.0
Moving to v28.0.0 from v27.2.1. Changelog.
CsvHelper 28 changes ShouldSkipRecord: the function gets the reader row instead of the record's fields, and it is
null by default, so it isn't called at all unless you set it. CsvParser and CsvReader take your own
IParserConfiguration and IReaderConfiguration implementations instead of only CsvConfiguration, so
IParserConfiguration gets the CultureInfo and the Validate method that the parser needs. The parser also caches
the fields it processes, and delimiter detection looks at every line it reads. The target frameworks don't change.
Skipping records
ShouldSkipRecordArgs.Record, the record's fields as a string[], is replaced by ShouldSkipRecordArgs.Row, the
IReaderRow being read. args.Row.Parser.Record has the fields that args.Record had, and you can also read single
fields from the row, such as args.Row[0].
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
ShouldSkipRecord = args => args.Record.All(string.IsNullOrWhiteSpace),
};var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
ShouldSkipRecord = args => args.Row.Parser.Record.All(string.IsNullOrWhiteSpace),
};ConfigurationFunctions.ShouldSkipRecord, the default function that returned false, is removed. Where you assigned
it, to reset the setting, assign null, which is the default now.
Custom configuration classes
A class that implements IParserConfiguration can be passed to new CsvParser(reader, configuration), and one that
implements IReaderConfiguration to new CsvReader(reader, configuration); 27 took only a CsvConfiguration. The
parser needs two more members for that:
| Interface | Change |
|---|---|
IParserConfiguration |
New: CultureInfo CultureInfo { get; }, moved from IReaderConfiguration. |
IParserConfiguration |
New: void Validate(), which the parser calls when it is created. Throw a ConfigurationException for settings that can't work together. |
IReaderConfiguration |
CultureInfo comes from IParserConfiguration now, so a reader configuration only adds Validate. |
IReaderConfiguration |
ShouldSkipRecord can be null, which skips no records. |
public class ParserConfiguration : IParserConfiguration
{
public bool CacheFields => false;
public bool LeaveOpen => false;
public string NewLine => "\r\n";
public bool IsNewLineSet => false;
public CsvMode Mode => CsvMode.RFC4180;
public int BufferSize => 0x1000;
public int ProcessFieldBufferSize => 1024;
public bool CountBytes => false;
public Encoding Encoding => Encoding.UTF8;
public BadDataFound BadDataFound => ConfigurationFunctions.BadDataFound;
public bool LineBreakInQuotedFieldIsBadData => false;
public char Comment => '#';
public bool AllowComments => false;
public bool IgnoreBlankLines => true;
public char Quote => '"';
public string Delimiter => ",";
public bool DetectDelimiter => false;
public string[] DetectDelimiterValues => new[] { ",", ";", "|", "\t" };
public char Escape => '"';
public TrimOptions TrimOptions => TrimOptions.None;
public char[] WhiteSpaceChars => new[] { ' ' };
public bool ExceptionMessagesContainRawData => true;
}public class ParserConfiguration : IParserConfiguration
{
public CultureInfo CultureInfo => CultureInfo.InvariantCulture; // New in 28.
public bool CacheFields => false;
public bool LeaveOpen => false;
public string NewLine => "\r\n";
public bool IsNewLineSet => false;
public CsvMode Mode => CsvMode.RFC4180;
public int BufferSize => 0x1000;
public int ProcessFieldBufferSize => 1024;
public bool CountBytes => false;
public Encoding Encoding => Encoding.UTF8;
public BadDataFound BadDataFound => ConfigurationFunctions.BadDataFound;
public bool LineBreakInQuotedFieldIsBadData => false;
public char Comment => '#';
public bool AllowComments => false;
public bool IgnoreBlankLines => true;
public char Quote => '"';
public string Delimiter => ",";
public bool DetectDelimiter => false;
public string[] DetectDelimiterValues => new[] { ",", ";", "|", "\t" };
public char Escape => '"';
public TrimOptions TrimOptions => TrimOptions.None;
public char[] WhiteSpaceChars => new[] { ' ' };
public bool ExceptionMessagesContainRawData => true;
public void Validate() // New in 28.
{
if (Delimiter == Quote.ToString())
{
throw new ConfigurationException("The delimiter and the quote can't be the same.");
}
}
}
using var parser = new CsvParser(reader, new ParserConfiguration());Behavior changes
IParser.Recordis cached: reading it twice for the same row gives the same array, until the nextRead. If you change that array, copy it first, or the next read ofRecordfor that row returns your changes.BadDataFoundis called once per bad field. 27 called it each time the field was read, for example once forGetFieldand again forRecord.- Delimiter detection (
DetectDelimiter = true) ignores text in quotes (or escaped text inCsvMode.Escape), only considers delimiters that are on every line it reads, and picks the culture's list separator when that is one of them. It can choose a different delimiter than 27 did for the same file. - No header, mapped names: with
HasHeaderRecord = false, a member mapped withNameis read by its index (its position in the map, unless you setIndex). 27 threw aReaderExceptionsaying there is no header record to find the name in. - Nullable annotations in 28.0.0 only: 28.0.0 is built with nullable reference types enabled. In a project that
enables them too, you get warnings for values such as
HeaderRecordandParser.Record, which arestring[]?, and forBadDataFound = null. 28.0.1 builds without the annotations again, as 27 did.
All API changes
The API of v27.2.1 or v28.0.0 could not be read, so there is no list of every change. The v28.0.0 changelog has the commits.