CsvHelper 33 annotates its API for nullable reference types: the parameters, return values and properties that can be null are marked with ?. In a project that doesn't enable nullable reference types, the compiler ignores the annotations and the upgrade needs no changes, unless you derive from MemberMap. In a project that does (<Nullable>enable</Nullable>), the compiler now checks your code against them and warns where it assumes a value is never null; that is where your changes are. Use 33.0.1 or later: it marks some values nullable that 33.0.0 missed, and unmarks some that 33.0.0 marked by mistake.

Nullable reference types

CsvHelper 32 had no annotations, so the compiler knew nothing about what could be null and didn't warn. 33 tells it, and code that compiled cleanly can get these warnings:

Warning Means
CS8600 A value that may be null is assigned to a variable that isn't nullable.
CS8602 A value that may be null is dereferenced.
CS8604 A value that may be null is passed to a parameter that isn't nullable.
CS8765, CS8767 An override or interface implementation says a parameter can't be null, where the base says it can.

Fix them as you would in your own code: declare the variable nullable, check for null, or give a fallback with ??. They are warnings, not errors, unless your project treats warnings as errors. Value types don't change: GetField<int> still returns an int.

Reading fields

The field getters return string?: a field can be missing, and HeaderRecord is null until the header is read.

Before · v32.0.3
#nullable enable
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
csv.Read();
csv.ReadHeader();
var columns = csv.HeaderRecord.Length;  // 33: warning CS8602, HeaderRecord is string[]?
while (csv.Read())
{
    string name = csv.GetField("Name");  // 33: warning CS8600, GetField returns string?
    var id = csv.GetField<int>("Id");    // No warning: an int is still an int.
    Console.WriteLine(name.Trim());
}
After · v33.0.0
#nullable enable
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
csv.Read();
csv.ReadHeader();
var columns = csv.HeaderRecord?.Length ?? 0;
while (csv.Read())
{
    var name = csv.GetField("Name") ?? "";
    var id = csv.GetField<int>("Id");
    Console.WriteLine(name.Trim());
}
Member In 33
GetField(index), GetField(name), csv[index], csv[name] string?
GetField<T>(...), TryGetField<T>(..., out T? field) T?: nullable for reference types such as string, unchanged for value types
GetField(Type, ...), TryGetField(Type, ..., out object? field) object?
CsvReader.HeaderRecord, Parser.Record string[]?
Context.Maps[type], Context.Maps.Find<T>() Nullable: null when there is no map for the type
CsvContext.Reader, .Writer, .Parser, CsvHelperException.Context Nullable

The writing methods take nullable values (WriteField(string? field), WriteField<T>(T? field), WriteRecord<T>(T? record)), which changes nothing for callers.

Type converters

ITypeConverter and DefaultTypeConverter take string? text in ConvertFromString and object? value in ConvertToString, and return object? and string?. TypeConverter<T> is the same with T?. A converter that overrides them with string text or object value gets CS8765 (CS8767 for an ITypeConverter implementation): the base says null can come in, so handle it. The return types can stay non-nullable.

Before · v32.0.3
#nullable enable
public class TrimConverter : DefaultTypeConverter
{
    // 33: warning CS8765 on text and value, which are nullable in DefaultTypeConverter.
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) =>
        text.Trim();

    public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) =>
        ((string)value).Trim();
}
After · v33.0.0
#nullable enable
public class TrimConverter : DefaultTypeConverter
{
    public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) =>
        text?.Trim();

    public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) =>
        ((string?)value)?.Trim();
}

Configuration

The configuration functions that are null by default are nullable: ShouldSkipRecord, ReferenceHeaderPrefix and DynamicPropertySort. So are some values that the functions get, such as ShouldQuoteArgs.Field and MissingFieldFoundArgs.HeaderNames, and the row's Parser.Record.

Before · v32.0.3
#nullable enable
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    // 33: warning CS8604, Parser.Record is string[]?
    ShouldSkipRecord = args => args.Row.Parser.Record.All(string.IsNullOrWhiteSpace),
};
After · v33.0.0
#nullable enable
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    ShouldSkipRecord = args => args.Row.Parser.Record?.All(string.IsNullOrWhiteSpace) ?? false,
};

33.0.0 and 33.0.1

33.0.0 is too strict in a few places, and 33.0.1 fixes them. Upgrade to 33.0.1 or later instead of working around these warnings:

  • BadDataFound, MissingFieldFound, HeaderValidated and ReadingExceptionOccurred aren't nullable in 33.0.0, so the usual BadDataFound = null or MissingFieldFound = null warns (CS8625). 33.0.1 makes them nullable.
  • PrepareHeaderForMatchArgs.Header is string? in 33.0.0, so args => args.Header.ToLower() warns. In 33.0.1 it's string again.
  • GetRecord<T>() and GetRecord(Type) return T? and object? in 33.0.0. In 33.0.1 they return T and object again (see Behavior changes).
  • 33.0.1 also accepts null in MemberMap.Constant and the Default(string) overload, accepts Convert functions that return null, and accepts class map lambdas of Map and References that return a nullable member.

Member maps

MemberMap.TypeConverterOption is an abstract property with only a getter now, and MemberMap<TClass, TMember> implements it. This matters only if you derive from these classes: a class derived from MemberMap must implement the property, and one derived from MemberMap<TClass, TMember> that set it in its constructor overrides it instead.

Before · v32.0.3
using System.Reflection;

public class CustomTypeConverterOption : MemberMapTypeConverterOption
{
    public CustomTypeConverterOption(MemberMap memberMap) : base(memberMap) { }
}

public class CustomMemberMap<TClass, TMember> : MemberMap<TClass, TMember>
{
    public CustomMemberMap(MemberInfo member) : base(member)
    {
        TypeConverterOption = new CustomTypeConverterOption(this);
    }
}
After · v33.0.0
using System.Reflection;

public class CustomTypeConverterOption : MemberMapTypeConverterOption
{
    public CustomTypeConverterOption(MemberMap memberMap) : base(memberMap) { }
}

public class CustomMemberMap<TClass, TMember> : MemberMap<TClass, TMember>
{
    private readonly CustomTypeConverterOption typeConverterOption;

    public CustomMemberMap(MemberInfo member) : base(member)
    {
        typeConverterOption = new CustomTypeConverterOption(this);
    }

    public override MemberMapTypeConverterOption TypeConverterOption => typeConverterOption;
}

Dependencies

On .NET Framework (4.6.2, 4.7, 4.8) and .NET Standard 2.0, CsvHelper 33 depends on Microsoft.Bcl.AsyncInterfaces 8.0.0 instead of 7.0.0. If your project references an older version of that package itself, raise it to 8.0.0 or later, or NuGet reports a package downgrade. The target frameworks are the same as in 32, until 33.1.0 replaces the .NET 6 and 7 builds with a .NET 9 build.

Behavior changes

  • GetRecord with no records: from 33.0.1, when GetRecord<T>() or GetRecord(Type) reads the header itself (you didn't call ReadHeader) and no record follows it, it throws a ReaderException ("There are no records."). 32 and 33.0.0 returned default, null for a class.

All API changes

Every change to the public API since v32.0.3: 1 added, 210 changed (1 breaking). Open a type to see its changes.

CsvHelper

CsvContextclass3 changed

Only nullable annotations changed.

ParserReaderWriter
CsvDataReaderclass3 changed
CsvHelperExceptionclass1 changed

Only nullable annotations changed.

Context
CsvParserclass1 changed

Only nullable annotations changed.

Record
CsvReaderclass40 changed

Only nullable annotations changed.

GetField(Type, int)GetField(Type, int, ITypeConverter)GetField(Type, string)GetField(Type, string, ITypeConverter)GetField(Type, string, int)GetField(Type, string, int, ITypeConverter)GetField(int)GetField(string)GetField(string, int)GetField<T, TConverter>(int)GetField<T, TConverter>(string)GetField<T, TConverter>(string, int)GetField<T>(int)GetField<T>(int, ITypeConverter)GetField<T>(string)GetField<T>(string, ITypeConverter)GetField<T>(string, int)GetField<T>(string, int, ITypeConverter)GetRecord(Type)GetRecord<T>()GetRecord<T>(T)HeaderRecordTryGetField(Type, int, ITypeConverter, out object)TryGetField(Type, int, out object)TryGetField(Type, string, ITypeConverter, out object)TryGetField(Type, string, int, ITypeConverter, out object)TryGetField(Type, string, int, out object)TryGetField(Type, string, out object)TryGetField<T, TConverter>(int, out T)TryGetField<T, TConverter>(string, int, out T)TryGetField<T, TConverter>(string, out T)TryGetField<T>(int, ITypeConverter, out T)TryGetField<T>(int, out T)TryGetField<T>(string, ITypeConverter, out T)TryGetField<T>(string, int, ITypeConverter, out T)TryGetField<T>(string, int, out T)TryGetField<T>(string, out T)this[int]this[string, int]this[string]
CsvWriterclass13 changed

Only nullable annotations changed.

GetTypeInfoForRecord<T>(T)HeaderRecordSanitizeForInjection(string)WriteComment(string)WriteConvertedField(string, Type)WriteDynamicHeader(IDynamicMetaObjectProvider)WriteField(string)WriteField(string, bool)WriteField<T, TConverter>(T)WriteField<T>(T)WriteField<T>(T, ITypeConverter)WriteRecord<T>(T)WriteToBuffer(string)
IParserinterface1 changed

Only nullable annotations changed.

Record
IReaderRowinterface40 changed

Only nullable annotations changed.

GetField(Type, int)GetField(Type, int, ITypeConverter)GetField(Type, string)GetField(Type, string, ITypeConverter)GetField(Type, string, int)GetField(Type, string, int, ITypeConverter)GetField(int)GetField(string)GetField(string, int)GetField<T, TConverter>(int)GetField<T, TConverter>(string)GetField<T, TConverter>(string, int)GetField<T>(int)GetField<T>(int, ITypeConverter)GetField<T>(string)GetField<T>(string, ITypeConverter)GetField<T>(string, int)GetField<T>(string, int, ITypeConverter)GetRecord(Type)GetRecord<T>()GetRecord<T>(T)HeaderRecordTryGetField(Type, int, ITypeConverter, out object)TryGetField(Type, int, out object)TryGetField(Type, string, ITypeConverter, out object)TryGetField(Type, string, int, ITypeConverter, out object)TryGetField(Type, string, int, out object)TryGetField(Type, string, out object)TryGetField<T, TConverter>(int, out T)TryGetField<T, TConverter>(string, int, out T)TryGetField<T, TConverter>(string, out T)TryGetField<T>(int, ITypeConverter, out T)TryGetField<T>(int, out T)TryGetField<T>(string, ITypeConverter, out T)TryGetField<T>(string, int, ITypeConverter, out T)TryGetField<T>(string, int, out T)TryGetField<T>(string, out T)this[int]this[string, int]this[string]
IWriterRowinterface8 changed

Only nullable annotations changed.

HeaderRecordWriteComment(string)WriteConvertedField(string, Type)WriteField(string)WriteField(string, bool)WriteField<T, TConverter>(T)WriteField<T>(T)WriteField<T>(T, ITypeConverter)
MissingFieldFoundArgsstruct2 changed

Only nullable annotations changed.

HeaderNamesMissingFieldFoundArgs(string[], int, CsvContext)
PrepareHeaderForMatchArgsstruct2 changed

Only nullable annotations changed.

HeaderPrepareHeaderForMatchArgs(string, int)
ShouldQuoteArgsstruct2 changed

Only nullable annotations changed.

FieldShouldQuoteArgs(string, Type, IWriterRow)

CsvHelper.Configuration

ClassMapCollectionclass2 changed

Only nullable annotations changed.

Find<T>()this[Type]
CsvConfigurationrecord3 changed

Only nullable annotations changed.

DynamicPropertySortReferenceHeaderPrefixShouldSkipRecord
IReaderConfigurationinterface2 changed

Only nullable annotations changed.

ReferenceHeaderPrefixShouldSkipRecord
IWriterConfigurationinterface2 changed

Only nullable annotations changed.

DynamicPropertySortReferenceHeaderPrefix
MemberMapclass1 breaking1 changed

TypeConverterOptionBreaking

The set accessor was removed.

Before
public virtual MemberMapTypeConverterOption TypeConverterOption { get; protected set; }
After
public abstract MemberMapTypeConverterOption TypeConverterOption { get; }
MemberMap<TClass, TMember>class1 changed1 added

Only nullable annotations changed.

MemberMap(MemberInfo)

Added

TypeConverterOption
MemberMapCollectionclass2 changed

Only nullable annotations changed.

Find(MemberInfo)Find<T>(Expression<Func<T, object>>)
MemberMapDataclass9 changed

Only nullable annotations changed.

ConstantDefaultMemberMemberMapData(MemberInfo)ReadingConvertExpressionTypeConverterValidateExpressionValidateMessageExpressionWritingConvertExpression
MemberReferenceMapclass1 changed

Only nullable annotations changed.

Prefix(string, bool)
MemberReferenceMapCollectionclass2 changed

Only nullable annotations changed.

Find(MemberInfo)Find<T>(Expression<Func<T, object>>)
ParameterMapclass2 changed

Only nullable annotations changed.

ConstructorTypeMapReferenceMap
ParameterMapDataclass3 changed

Only nullable annotations changed.

ConstantDefaultTypeConverter
ParameterReferenceMapclass1 changed

Only nullable annotations changed.

Prefix(string, bool)

CsvHelper.Configuration.Attributes

HeaderPrefixAttributeclass1 changed

Only nullable annotations changed.

Prefix

CsvHelper.Expressions

ExpressionManagerclass2 changed

Only nullable annotations changed.

CreateGetFieldExpression(MemberMap)CreateGetMemberExpression(Expression, ClassMap, MemberMap)
RecordWriterclass1 changed

Only nullable annotations changed.

CombineDelegates<T>(IEnumerable<Action<T>>)

CsvHelper.TypeConversion

ArrayConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
BigIntegerConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
BooleanConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
ByteArrayConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
ByteConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
CharConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
CollectionGenericConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
DateTimeConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
DateTimeOffsetConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
DecimalConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
DefaultTypeConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
DoubleConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
EnumConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
EnumerableConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
GuidConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
IDictionaryConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
IDictionaryGenericConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
IEnumerableConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
IEnumerableGenericConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
ITypeConverterinterface2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
Int16Converterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
Int32Converterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
Int64Converterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
NotSupportedTypeConverter<T>class2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(T, IWriterRow, MemberMapData)
NullableConverterclass3 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)UnderlyingType
SByteConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
SingleConverterclass2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(object, IWriterRow, MemberMapData)
StringConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
TimeSpanConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
TypeConverter<T>class2 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)ConvertToString(T, IWriterRow, MemberMapData)
TypeConverterExceptionclass8 changed

Only nullable annotations changed.

TextTypeConverterException(ITypeConverter, MemberMapData, object, CsvContext)TypeConverterException(ITypeConverter, MemberMapData, object, CsvContext, string)TypeConverterException(ITypeConverter, MemberMapData, object, CsvContext, string, Exception)TypeConverterException(ITypeConverter, MemberMapData, string, CsvContext)TypeConverterException(ITypeConverter, MemberMapData, string, CsvContext, string)TypeConverterException(ITypeConverter, MemberMapData, string, CsvContext, string, Exception)Value
TypeConverterOptionsclass2 changed

Only nullable annotations changed.

CultureInfoFormats
UInt16Converterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
UInt32Converterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
UInt64Converterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)
UriConverterclass1 changed

Only nullable annotations changed.

ConvertFromString(string, IReaderRow, MemberMapData)