From d0b6e3b851cb90cfda3b4352ca9e104301256740 Mon Sep 17 00:00:00 2001 From: Josh Close Date: Sun, 19 May 2024 11:23:42 -0500 Subject: [PATCH] Added nullable. --- .editorconfig | 3 +- src/CsvHelper/ArrayHelper.cs | 104 +- src/CsvHelper/BadDataException.cs | 103 +- .../Compatibility/AsyncExtensions.cs | 22 +- .../Attributes/AllowCommentsAttribute.cs | 41 +- .../Attributes/BooleanFalseValuesAttribute.cs | 71 +- .../Attributes/BooleanTrueValuesAttribute.cs | 71 +- .../Attributes/BufferSizeAttribute.cs | 43 +- .../Attributes/CacheFieldsAttribute.cs | 41 +- .../Attributes/CommentAttribute.cs | 43 +- .../Attributes/ConstantAttribute.cs | 59 +- .../Attributes/CountBytesAttribute.cs | 54 +- .../Attributes/CultureInfoAttribute.cs | 104 +- .../Attributes/DateTimeStylesAttribute.cs | 54 +- .../Attributes/DefaultAttribute.cs | 57 +- .../Attributes/DelimiterAttribute.cs | 41 +- .../DetectColumnCountChangesAttribute.cs | 47 +- .../Attributes/DetectDelimiterAttribute.cs | 39 +- .../DetectDelimiterValuesAttribute.cs | 44 +- .../Attributes/EncodingAttribute.cs | 64 +- .../Attributes/EnumIgnoreCaseAttribute.cs | 49 +- .../Attributes/EscapeAttribute.cs | 47 +- ...xceptionMessagesContainRawDataAttribute.cs | 43 +- .../Attributes/FormatAttribute.cs | 67 +- .../Attributes/HasHeaderRecordAttribute.cs | 47 +- .../Attributes/HeaderPrefixAttribute.cs | 115 +- .../Configuration/Attributes/IClassMapper.cs | 19 +- .../Configuration/Attributes/IMemberMapper.cs | 19 +- .../Attributes/IMemberReferenceMapper.cs | 19 +- .../Attributes/IParameterMapper.cs | 23 +- .../Attributes/IParameterReferenceMapper.cs | 23 +- .../Attributes/IgnoreAttribute.cs | 52 +- .../Attributes/IgnoreBaseAttribute.cs | 19 +- .../Attributes/IgnoreBlankLinesAttribute.cs | 45 +- .../Attributes/IgnoreReferencesAttribute.cs | 47 +- .../IncludePrivateMembersAttribute.cs | 45 +- .../Attributes/IndexAttribute.cs | 77 +- .../InjectionCharactersAttribute.cs | 45 +- .../InjectionEscapeCharacterAttribute.cs | 41 +- .../Attributes/InjectionOptionsAttribute.cs | 41 +- ...ineBreakInQuotedFieldIsBadDataAttribute.cs | 43 +- .../Attributes/MaxFieldSizeAttribute.cs | 47 +- .../Attributes/MemberTypesAttribute.cs | 47 +- .../Configuration/Attributes/ModeAttribute.cs | 45 +- .../Configuration/Attributes/NameAttribute.cs | 103 +- .../Attributes/NameIndexAttribute.cs | 55 +- .../Attributes/NewLineAttribute.cs | 47 +- .../Attributes/NullValuesAttribute.cs | 71 +- .../Attributes/NumberStylesAttribute.cs | 54 +- .../Attributes/OptionalAttribute.cs | 31 +- .../ProcessFieldBufferSizeAttribute.cs | 43 +- .../Attributes/QuoteAttribute.cs | 47 +- .../Attributes/TrimOptionsAttribute.cs | 47 +- .../Attributes/TypeConverterAttribute.cs | 76 +- ...wObjectForNullReferenceMembersAttribute.cs | 55 +- .../Attributes/WhiteSpaceCharsAttribute.cs | 49 +- src/CsvHelper/Configuration/ClassMap.cs | 986 ++++---- .../Configuration/ClassMapBuilder.cs | 712 +++--- .../Configuration/ClassMapCollection.cs | 277 ++- src/CsvHelper/Configuration/ClassMap`1.cs | 166 +- .../Configuration/ConfigurationException.cs | 47 +- .../Configuration/ConfigurationFunctions.cs | 419 ++-- .../Configuration/CsvConfiguration.cs | 512 ++-- .../Configuration/DefaultClassMap`1.cs | 17 +- .../Configuration/IParserConfiguration.cs | 327 ++- .../Configuration/IReaderConfiguration.cs | 170 +- .../Configuration/IWriterConfiguration.cs | 315 ++- .../Configuration/InjectionOptions.cs | 45 +- src/CsvHelper/Configuration/MemberMap.cs | 396 +-- .../Configuration/MemberMapCollection.cs | 430 ++-- .../Configuration/MemberMapComparer.cs | 114 +- src/CsvHelper/Configuration/MemberMapData.cs | 296 ++- .../MemberMapTypeConverterOption.cs | 254 +- src/CsvHelper/Configuration/MemberMap`1.cs | 454 ++-- .../Configuration/MemberNameCollection.cs | 150 +- .../Configuration/MemberReferenceMap.cs | 94 +- .../MemberReferenceMapCollection.cs | 294 ++- .../Configuration/MemberReferenceMapData.cs | 89 +- src/CsvHelper/Configuration/MemberTypes.cs | 39 +- src/CsvHelper/Configuration/ParameterMap.cs | 332 ++- .../Configuration/ParameterMapData.cs | 182 +- .../ParameterMapTypeConverterOption.cs | 242 +- .../Configuration/ParameterReferenceMap.cs | 92 +- .../ParameterReferenceMapData.cs | 91 +- src/CsvHelper/Configuration/TrimOptions.cs | 35 +- src/CsvHelper/CsvContext.cs | 334 ++- src/CsvHelper/CsvDataReader.cs | 594 ++--- src/CsvHelper/CsvHelper.csproj | 5 +- src/CsvHelper/CsvHelperException.cs | 224 +- src/CsvHelper/CsvMode.cs | 56 +- src/CsvHelper/CsvParser.cs | 1800 +++++++------- src/CsvHelper/CsvReader.cs | 2116 ++++++++--------- src/CsvHelper/CsvWriter.cs | 1417 ++++++----- src/CsvHelper/Delegates/BadDataFound.cs | 67 +- src/CsvHelper/Delegates/ConvertFromString.cs | 45 +- src/CsvHelper/Delegates/ConvertToString.cs | 47 +- src/CsvHelper/Delegates/GetConstructor.cs | 40 +- src/CsvHelper/Delegates/GetDelimiter.cs | 60 +- .../Delegates/GetDynamicPropertyName.cs | 53 +- src/CsvHelper/Delegates/HeaderValidated.cs | 57 +- src/CsvHelper/Delegates/MissingFieldFound.cs | 69 +- .../Delegates/PrepareHeaderForMatch.cs | 59 +- .../Delegates/ReadingExceptionOccurred.cs | 45 +- .../Delegates/ReferenceHeaderPrefix.cs | 53 +- src/CsvHelper/Delegates/ShouldQuote.cs | 65 +- src/CsvHelper/Delegates/ShouldSkipRecord.cs | 39 +- .../ShouldUseConstructorParameters.cs | 41 +- src/CsvHelper/Delegates/Validate.cs | 65 +- .../Expressions/DynamicRecordCreator.cs | 80 +- .../Expressions/DynamicRecordWriter.cs | 95 +- .../Expressions/ExpandoObjectRecordWriter.cs | 63 +- .../Expressions/ExpressionManager.cs | 764 +++--- .../Expressions/ObjectRecordCreator.cs | 87 +- .../Expressions/ObjectRecordWriter.cs | 167 +- .../Expressions/PrimitiveRecordCreator.cs | 58 +- .../Expressions/PrimitiveRecordWriter.cs | 68 +- src/CsvHelper/Expressions/RecordCreator.cs | 85 +- .../Expressions/RecordCreatorFactory.cs | 60 +- src/CsvHelper/Expressions/RecordHydrator.cs | 167 +- src/CsvHelper/Expressions/RecordManager.cs | 109 +- src/CsvHelper/Expressions/RecordWriter.cs | 138 +- .../Expressions/RecordWriterFactory.cs | 73 +- src/CsvHelper/Factory.cs | 176 +- src/CsvHelper/FastDynamicObject.cs | 12 +- src/CsvHelper/FieldCache.cs | 184 +- src/CsvHelper/FieldValidationException.cs | 81 +- src/CsvHelper/HeaderValidationException.cs | 81 +- src/CsvHelper/IFactory.cs | 134 +- src/CsvHelper/IObjectResolver.cs | 85 +- src/CsvHelper/IParser.cs | 131 +- src/CsvHelper/IReader.cs | 204 +- src/CsvHelper/IReaderRow.cs | 834 ++++--- src/CsvHelper/IWriter.cs | 120 +- src/CsvHelper/IWriterRow.cs | 246 +- src/CsvHelper/InvalidHeader.cs | 29 +- src/CsvHelper/LinkedListExtensions.cs | 27 +- src/CsvHelper/MaxFieldSizeException.cs | 53 +- src/CsvHelper/MissingFieldException.cs | 55 +- src/CsvHelper/ObjectCreator.cs | 276 ++- src/CsvHelper/ObjectResolver.cs | 221 +- src/CsvHelper/ParserException.cs | 53 +- src/CsvHelper/ReaderException.cs | 53 +- src/CsvHelper/RecordTypeInfo.cs | 61 +- src/CsvHelper/ReflectionExtensions.cs | 243 +- src/CsvHelper/ReflectionHelper.cs | 300 ++- .../TypeConversion/ArrayConverter.cs | 98 +- .../TypeConversion/BigIntegerConverter.cs | 80 +- .../TypeConversion/BooleanConverter.cs | 90 +- .../TypeConversion/ByteArrayConverter.cs | 178 +- .../ByteArrayConverterOptions.cs | 55 +- src/CsvHelper/TypeConversion/ByteConverter.cs | 39 +- src/CsvHelper/TypeConversion/CharConverter.cs | 41 +- .../CollectionConverterFactory.cs | 243 +- .../CollectionGenericConverter.cs | 84 +- .../TypeConversion/DateOnlyConverter.cs | 43 +- .../TypeConversion/DateTimeConverter.cs | 50 +- .../TypeConversion/DateTimeOffsetConverter.cs | 52 +- .../TypeConversion/DecimalConverter.cs | 39 +- .../TypeConversion/DefaultTypeConverter.cs | 89 +- .../TypeConversion/DoubleConverter.cs | 75 +- src/CsvHelper/TypeConversion/EnumConverter.cs | 163 +- .../TypeConversion/EnumConverterFactory.cs | 37 +- .../TypeConversion/EnumerableConverter.cs | 69 +- src/CsvHelper/TypeConversion/GuidConverter.cs | 34 +- .../TypeConversion/IDictionaryConverter.cs | 86 +- .../IDictionaryGenericConverter.cs | 58 +- .../TypeConversion/IEnumerableConverter.cs | 115 +- .../IEnumerableGenericConverter.cs | 85 +- .../TypeConversion/ITypeConverter.cs | 41 +- .../TypeConversion/ITypeConverterFactory.cs | 40 +- .../TypeConversion/Int16Converter.cs | 39 +- .../TypeConversion/Int32Converter.cs | 39 +- .../TypeConversion/Int64Converter.cs | 39 +- .../NotSupportedTypeConverter.cs | 72 +- .../TypeConversion/NullableConverter.cs | 138 +- .../NullableConverterFactory.cs | 27 +- .../TypeConversion/SByteConverter.cs | 39 +- .../TypeConversion/SingleConverter.cs | 75 +- .../TypeConversion/StringConverter.cs | 43 +- .../TypeConversion/TimeOnlyConverter.cs | 41 +- .../TypeConversion/TimeSpanConverter.cs | 50 +- src/CsvHelper/TypeConversion/TypeConverter.cs | 46 +- .../TypeConversion/TypeConverterCache.cs | 374 ++- .../TypeConversion/TypeConverterException.cs | 222 +- .../TypeConversion/TypeConverterOptions.cs | 268 +-- .../TypeConverterOptionsCache.cs | 156 +- .../TypeConversion/UInt16Converter.cs | 39 +- .../TypeConversion/UInt32Converter.cs | 39 +- .../TypeConversion/UInt64Converter.cs | 39 +- src/CsvHelper/TypeConversion/UriConverter.cs | 48 +- src/CsvHelper/ValidationException.cs | 53 +- src/CsvHelper/WriterException.cs | 53 +- 192 files changed, 13388 insertions(+), 13984 deletions(-) diff --git a/.editorconfig b/.editorconfig index 350d40c67..6926e212f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ csharp_space_around_binary_operators = before_and_after csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_braces = true:silent -csharp_style_namespace_declarations = block_scoped:silent +csharp_style_namespace_declarations = file_scoped:warning csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_operators = false:silent @@ -44,6 +44,7 @@ csharp_prefer_static_local_function = true:suggestion csharp_style_allow_embedded_statements_on_same_line_experimental = true:suggestion csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:suggestion +csharp_style_prefer_primary_constructors = true:suggestion [*.yml] indent_style = space diff --git a/src/CsvHelper/ArrayHelper.cs b/src/CsvHelper/ArrayHelper.cs index 6f729dc9a..739b69cf3 100644 --- a/src/CsvHelper/ArrayHelper.cs +++ b/src/CsvHelper/ArrayHelper.cs @@ -2,77 +2,71 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Methods to help with arrays. +/// +public static class ArrayHelper { /// - /// Methods to help with arrays. + /// Trims the characters off the start and end of the buffer + /// by updating the start and length arguments. /// - public static class ArrayHelper - { - /// - /// Trims the characters off the start and end of the buffer - /// by updating the start and length arguments. - /// - /// The buffer. - /// The start. - /// The length. - /// The characters to trim. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Trim(char[] buffer, ref int start, ref int length, char[] trimChars) + /// The buffer. + /// The start. + /// The length. + /// The characters to trim. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Trim(char[] buffer, ref int start, ref int length, char[] trimChars) + { + // Trim start. + for (var i = start; i < start + length; i++) { - // Trim start. - for (var i = start; i < start + length; i++) + var c = buffer[i]; + if (!Contains(trimChars, c)) { - var c = buffer[i]; - if (!Contains(trimChars, c)) - { - break; - } - - start++; - length--; + break; } - // Trim end. - for (var i = start + length - 1; i > start; i--) - { - var c = buffer[i]; - if (!Contains(trimChars, c)) - { - break; - } + start++; + length--; + } - length--; + // Trim end. + for (var i = start + length - 1; i > start; i--) + { + var c = buffer[i]; + if (!Contains(trimChars, c)) + { + break; } + + length--; } + } - /// - /// Determines whether this given array contains the given character. - /// - /// The array to search. - /// The character to look for. - /// - /// true if the array contains the characters, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(char[] array, in char c) + /// + /// Determines whether this given array contains the given character. + /// + /// The array to search. + /// The character to look for. + /// + /// true if the array contains the characters, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains(char[] array, in char c) + { + for (var i = 0; i < array.Length; i++) { - for (var i = 0; i < array.Length; i++) + if (array[i] == c) { - if (array[i] == c) - { - return true; - } + return true; } - - return false; } + + return false; } } diff --git a/src/CsvHelper/BadDataException.cs b/src/CsvHelper/BadDataException.cs index ddf2e6c4b..5d8513847 100644 --- a/src/CsvHelper/BadDataException.cs +++ b/src/CsvHelper/BadDataException.cs @@ -2,66 +2,63 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents errors that occur due to bad data. +/// +[Serializable] +public class BadDataException : CsvHelperException { /// - /// Represents errors that occur due to bad data. + /// The full field unedited. /// - [Serializable] - public class BadDataException : CsvHelperException - { - /// - /// The full field unedited. - /// - public readonly string Field; + public readonly string Field; - /// - /// The full row unedited. - /// - public readonly string RawRecord; + /// + /// The full row unedited. + /// + public readonly string RawRecord; - /// - /// Initializes a new instance of the class. - /// - /// The full field unedited. - /// The full row unedited. - /// The reading context. - public BadDataException(string field, string rawRecord, CsvContext context) : base(context) - { - Field = field; - RawRecord = rawRecord; - } + /// + /// Initializes a new instance of the class. + /// + /// The full field unedited. + /// The full row unedited. + /// The reading context. + public BadDataException(string field, string rawRecord, CsvContext context) : base(context) + { + Field = field; + RawRecord = rawRecord; + } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The full field unedited. - /// The full row unedited. - /// The reading context. - /// The message that describes the error. - public BadDataException(string field, string rawRecord, CsvContext context, string message) : base(context, message) - { - Field = field; - RawRecord = rawRecord; - } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The full field unedited. + /// The full row unedited. + /// The reading context. + /// The message that describes the error. + public BadDataException(string field, string rawRecord, CsvContext context, string message) : base(context, message) + { + Field = field; + RawRecord = rawRecord; + } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The full field unedited. - /// The full row unedited. - /// The reading context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public BadDataException(string field, string rawRecord, CsvContext context, string message, Exception innerException) : base(context, message, innerException) - { - Field = field; - RawRecord = rawRecord; - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The full field unedited. + /// The full row unedited. + /// The reading context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public BadDataException(string field, string rawRecord, CsvContext context, string message, Exception innerException) : base(context, message, innerException) + { + Field = field; + RawRecord = rawRecord; } } diff --git a/src/CsvHelper/Compatibility/AsyncExtensions.cs b/src/CsvHelper/Compatibility/AsyncExtensions.cs index 3ff2834be..6864be0ba 100644 --- a/src/CsvHelper/Compatibility/AsyncExtensions.cs +++ b/src/CsvHelper/Compatibility/AsyncExtensions.cs @@ -1,20 +1,16 @@ -using System.IO; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +internal static class AsyncExtensions { - internal static class AsyncExtensions - { #if !(NETSTANDARD2_1_OR_GREATER || NET) - public static ValueTask DisposeAsync(this TextWriter textWriter) + public static ValueTask DisposeAsync(this TextWriter textWriter) + { + if (textWriter != null) { - if (textWriter != null) - { - textWriter.Dispose(); - } - - return default; + textWriter.Dispose(); } -#endif + + return default; } +#endif } diff --git a/src/CsvHelper/Configuration/Attributes/AllowCommentsAttribute.cs b/src/CsvHelper/Configuration/Attributes/AllowCommentsAttribute.cs index 39252bc39..983845294 100644 --- a/src/CsvHelper/Configuration/Attributes/AllowCommentsAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/AllowCommentsAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// A value indicating whether comments are allowed. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class AllowCommentsAttribute : Attribute, IClassMapper { + /// + /// Gets a value indicating whether comments are allowed. + /// + public bool AllowComments { get; private set; } + /// /// A value indicating whether comments are allowed. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class AllowCommentsAttribute : Attribute, IClassMapper + /// The value indicating whether comments are allowed. + public AllowCommentsAttribute(bool allowComments = true) { - /// - /// Gets a value indicating whether comments are allowed. - /// - public bool AllowComments { get; private set; } - - /// - /// A value indicating whether comments are allowed. - /// - /// The value indicating whether comments are allowed. - public AllowCommentsAttribute(bool allowComments = true) - { - AllowComments = allowComments; - } + AllowComments = allowComments; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.AllowComments = AllowComments; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.AllowComments = AllowComments; } } diff --git a/src/CsvHelper/Configuration/Attributes/BooleanFalseValuesAttribute.cs b/src/CsvHelper/Configuration/Attributes/BooleanFalseValuesAttribute.cs index ef450a76d..b2c9088cb 100644 --- a/src/CsvHelper/Configuration/Attributes/BooleanFalseValuesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/BooleanFalseValuesAttribute.cs @@ -2,51 +2,48 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The string values used to represent a boolean false when converting. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class BooleanFalseValuesAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the false values. + /// + public string[] FalseValues { get; private set; } + /// /// The string values used to represent a boolean false when converting. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class BooleanFalseValuesAttribute : Attribute, IMemberMapper, IParameterMapper + /// The false values. + public BooleanFalseValuesAttribute(string falseValue) { - /// - /// Gets the false values. - /// - public string[] FalseValues { get; private set; } - - /// - /// The string values used to represent a boolean false when converting. - /// - /// The false values. - public BooleanFalseValuesAttribute(string falseValue) - { - FalseValues = new string[] { falseValue }; - } + FalseValues = new string[] { falseValue }; + } - /// - /// The string values used to represent a boolean false when converting. - /// - /// The false values. - public BooleanFalseValuesAttribute(params string[] falseValues) - { - FalseValues = falseValues; - } + /// + /// The string values used to represent a boolean false when converting. + /// + /// The false values. + public BooleanFalseValuesAttribute(params string[] falseValues) + { + FalseValues = falseValues; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverterOptions.BooleanFalseValues.Clear(); - memberMap.Data.TypeConverterOptions.BooleanFalseValues.AddRange(FalseValues); - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.TypeConverterOptions.BooleanFalseValues.Clear(); + memberMap.Data.TypeConverterOptions.BooleanFalseValues.AddRange(FalseValues); + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.TypeConverterOptions.BooleanFalseValues.Clear(); - parameterMap.Data.TypeConverterOptions.BooleanFalseValues.AddRange(FalseValues); - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverterOptions.BooleanFalseValues.Clear(); + parameterMap.Data.TypeConverterOptions.BooleanFalseValues.AddRange(FalseValues); } } diff --git a/src/CsvHelper/Configuration/Attributes/BooleanTrueValuesAttribute.cs b/src/CsvHelper/Configuration/Attributes/BooleanTrueValuesAttribute.cs index 6d9fe659a..5c19743d5 100644 --- a/src/CsvHelper/Configuration/Attributes/BooleanTrueValuesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/BooleanTrueValuesAttribute.cs @@ -2,51 +2,48 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The string values used to represent a boolean true when converting. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class BooleanTrueValuesAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the true values. + /// + public string[] TrueValues { get; private set; } + /// /// The string values used to represent a boolean true when converting. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class BooleanTrueValuesAttribute : Attribute, IMemberMapper, IParameterMapper + /// + public BooleanTrueValuesAttribute(string trueValue) { - /// - /// Gets the true values. - /// - public string[] TrueValues { get; private set; } - - /// - /// The string values used to represent a boolean true when converting. - /// - /// - public BooleanTrueValuesAttribute(string trueValue) - { - TrueValues = new string[] { trueValue }; - } + TrueValues = new string[] { trueValue }; + } - /// - /// The string values used to represent a boolean true when converting. - /// - /// - public BooleanTrueValuesAttribute(params string[] trueValues) - { - TrueValues = trueValues; - } + /// + /// The string values used to represent a boolean true when converting. + /// + /// + public BooleanTrueValuesAttribute(params string[] trueValues) + { + TrueValues = trueValues; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverterOptions.BooleanTrueValues.Clear(); - memberMap.Data.TypeConverterOptions.BooleanTrueValues.AddRange(TrueValues); - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.TypeConverterOptions.BooleanTrueValues.Clear(); + memberMap.Data.TypeConverterOptions.BooleanTrueValues.AddRange(TrueValues); + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.TypeConverterOptions.BooleanTrueValues.Clear(); - parameterMap.Data.TypeConverterOptions.BooleanTrueValues.AddRange(TrueValues); - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverterOptions.BooleanTrueValues.Clear(); + parameterMap.Data.TypeConverterOptions.BooleanTrueValues.AddRange(TrueValues); } } diff --git a/src/CsvHelper/Configuration/Attributes/BufferSizeAttribute.cs b/src/CsvHelper/Configuration/Attributes/BufferSizeAttribute.cs index 1a64a6496..b925e0e07 100644 --- a/src/CsvHelper/Configuration/Attributes/BufferSizeAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/BufferSizeAttribute.cs @@ -2,35 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The size of the buffer used for parsing and writing CSV files. +/// Default is 0x1000. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class BufferSizeAttribute : Attribute, IClassMapper { + /// + /// The buffer size. + /// + public int BufferSize { get; private set; } + /// /// The size of the buffer used for parsing and writing CSV files. - /// Default is 0x1000. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class BufferSizeAttribute : Attribute, IClassMapper + /// + public BufferSizeAttribute(int bufferSize) { - /// - /// The buffer size. - /// - public int BufferSize { get; private set; } - - /// - /// The size of the buffer used for parsing and writing CSV files. - /// - /// - public BufferSizeAttribute(int bufferSize) - { - BufferSize = bufferSize; - } + BufferSize = bufferSize; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.BufferSize = BufferSize; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.BufferSize = BufferSize; } } diff --git a/src/CsvHelper/Configuration/Attributes/CacheFieldsAttribute.cs b/src/CsvHelper/Configuration/Attributes/CacheFieldsAttribute.cs index d61e5858f..741b23ff7 100644 --- a/src/CsvHelper/Configuration/Attributes/CacheFieldsAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/CacheFieldsAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Cache fields that are created when parsing. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class CacheFieldsAttribute : Attribute, IClassMapper { /// /// Cache fields that are created when parsing. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class CacheFieldsAttribute : Attribute, IClassMapper - { - /// - /// Cache fields that are created when parsing. - /// - public bool CacheFields { get; private set; } + public bool CacheFields { get; private set; } - /// - /// Cache fields that are created when parsing. - /// - /// - public CacheFieldsAttribute(bool cacheFields = true) - { - CacheFields = cacheFields; - } + /// + /// Cache fields that are created when parsing. + /// + /// + public CacheFieldsAttribute(bool cacheFields = true) + { + CacheFields = cacheFields; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.CacheFields = CacheFields; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.CacheFields = CacheFields; } } diff --git a/src/CsvHelper/Configuration/Attributes/CommentAttribute.cs b/src/CsvHelper/Configuration/Attributes/CommentAttribute.cs index 2b9c9753b..3554481b9 100644 --- a/src/CsvHelper/Configuration/Attributes/CommentAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/CommentAttribute.cs @@ -2,35 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The character used to denote a line that is commented out. +/// Default is #. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class CommentAttribute : Attribute, IClassMapper { + /// + /// Gets the character used to denote a line that is commented out. + /// + public char Comment { get; private set; } + /// /// The character used to denote a line that is commented out. - /// Default is #. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class CommentAttribute : Attribute, IClassMapper + /// The comment character. + public CommentAttribute(char comment) { - /// - /// Gets the character used to denote a line that is commented out. - /// - public char Comment { get; private set; } - - /// - /// The character used to denote a line that is commented out. - /// - /// The comment character. - public CommentAttribute(char comment) - { - Comment = comment; - } + Comment = comment; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.Comment = Comment; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.Comment = Comment; } } diff --git a/src/CsvHelper/Configuration/Attributes/ConstantAttribute.cs b/src/CsvHelper/Configuration/Attributes/ConstantAttribute.cs index 493ed0c73..a7ffa8f0f 100644 --- a/src/CsvHelper/Configuration/Attributes/ConstantAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/ConstantAttribute.cs @@ -2,46 +2,43 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The constant value that will be used for every record when +/// reading and writing. This value will always be used no matter +/// what other mapping configurations are specified. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class ConstantAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the constant. + /// + public object Constant { get; private set; } + /// /// The constant value that will be used for every record when /// reading and writing. This value will always be used no matter /// what other mapping configurations are specified. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class ConstantAttribute : Attribute, IMemberMapper, IParameterMapper + /// The constant. + public ConstantAttribute(object constant) { - /// - /// Gets the constant. - /// - public object Constant { get; private set; } - - /// - /// The constant value that will be used for every record when - /// reading and writing. This value will always be used no matter - /// what other mapping configurations are specified. - /// - /// The constant. - public ConstantAttribute(object constant) - { - Constant = constant; - } + Constant = constant; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.Constant = Constant; - memberMap.Data.IsConstantSet = true; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.Constant = Constant; + memberMap.Data.IsConstantSet = true; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.Constant = Constant; - parameterMap.Data.IsConstantSet = true; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.Constant = Constant; + parameterMap.Data.IsConstantSet = true; } } diff --git a/src/CsvHelper/Configuration/Attributes/CountBytesAttribute.cs b/src/CsvHelper/Configuration/Attributes/CountBytesAttribute.cs index c203f64f8..e02764bf8 100644 --- a/src/CsvHelper/Configuration/Attributes/CountBytesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/CountBytesAttribute.cs @@ -2,10 +2,18 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Text; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// A value indicating whether the number of bytes should +/// be counted while parsing. This will slow down parsing +/// because it needs to get the byte count of every char for the given encoding. +/// The needs to be set correctly for this to be accurate. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class CountBytesAttribute : Attribute, IClassMapper { /// /// A value indicating whether the number of bytes should @@ -13,33 +21,23 @@ namespace CsvHelper.Configuration.Attributes /// because it needs to get the byte count of every char for the given encoding. /// The needs to be set correctly for this to be accurate. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class CountBytesAttribute : Attribute, IClassMapper - { - /// - /// A value indicating whether the number of bytes should - /// be counted while parsing. This will slow down parsing - /// because it needs to get the byte count of every char for the given encoding. - /// The needs to be set correctly for this to be accurate. - /// - public bool CountBytes { get; private set; } + public bool CountBytes { get; private set; } - /// - /// A value indicating whether the number of bytes should - /// be counted while parsing. This will slow down parsing - /// because it needs to get the byte count of every char for the given encoding. - /// The needs to be set correctly for this to be accurate. - /// - /// - public CountBytesAttribute(bool countBytes = true) - { - CountBytes = countBytes; - } + /// + /// A value indicating whether the number of bytes should + /// be counted while parsing. This will slow down parsing + /// because it needs to get the byte count of every char for the given encoding. + /// The needs to be set correctly for this to be accurate. + /// + /// + public CountBytesAttribute(bool countBytes = true) + { + CountBytes = countBytes; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.CountBytes = CountBytes; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.CountBytes = CountBytes; } } diff --git a/src/CsvHelper/Configuration/Attributes/CultureInfoAttribute.cs b/src/CsvHelper/Configuration/Attributes/CultureInfoAttribute.cs index e91528a77..3d2484156 100644 --- a/src/CsvHelper/Configuration/Attributes/CultureInfoAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/CultureInfoAttribute.cs @@ -2,69 +2,67 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Globalization; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// When applied to a member, specifies the +/// used when type converting the member. When applied to a type, the value of +/// in the +/// returned by +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class CultureInfoAttribute : Attribute, IClassMapper, IMemberMapper, IParameterMapper { /// - /// When applied to a member, specifies the - /// used when type converting the member. When applied to a type, the value of - /// in the - /// returned by + /// Gets the culture info. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class CultureInfoAttribute : Attribute, IClassMapper, IMemberMapper, IParameterMapper - { - /// - /// Gets the culture info. - /// - public CultureInfo CultureInfo { get; private set; } + public CultureInfo CultureInfo { get; private set; } - /// - /// - /// The name of a culture (case insensitive), or the literal values "InvariantCulture", - /// "CurrentCulture", "CurrentUICulture", "InstalledUICulture" to use the - /// corresponding static properties on . - /// - public CultureInfoAttribute(string name) + /// + /// + /// The name of a culture (case insensitive), or the literal values "InvariantCulture", + /// "CurrentCulture", "CurrentUICulture", "InstalledUICulture" to use the + /// corresponding static properties on . + /// + public CultureInfoAttribute(string name) + { + switch (name) { - switch(name) - { - case nameof(CultureInfo.InvariantCulture): - CultureInfo = CultureInfo.InvariantCulture; - break; - case nameof(CultureInfo.CurrentCulture): - CultureInfo = CultureInfo.CurrentCulture; - break; - case nameof(CultureInfo.CurrentUICulture): - CultureInfo = CultureInfo.CurrentUICulture; - break; - case nameof(CultureInfo.InstalledUICulture): - CultureInfo = CultureInfo.InstalledUICulture; - break; - default: - CultureInfo = CultureInfo.GetCultureInfo(name); - break; - } + case nameof(CultureInfo.InvariantCulture): + CultureInfo = CultureInfo.InvariantCulture; + break; + case nameof(CultureInfo.CurrentCulture): + CultureInfo = CultureInfo.CurrentCulture; + break; + case nameof(CultureInfo.CurrentUICulture): + CultureInfo = CultureInfo.CurrentUICulture; + break; + case nameof(CultureInfo.InstalledUICulture): + CultureInfo = CultureInfo.InstalledUICulture; + break; + default: + CultureInfo = CultureInfo.GetCultureInfo(name); + break; } + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.CultureInfo = CultureInfo; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.CultureInfo = CultureInfo; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverterOptions.CultureInfo = CultureInfo; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.TypeConverterOptions.CultureInfo = CultureInfo; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.TypeConverterOptions.CultureInfo = CultureInfo; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverterOptions.CultureInfo = CultureInfo; } } diff --git a/src/CsvHelper/Configuration/Attributes/DateTimeStylesAttribute.cs b/src/CsvHelper/Configuration/Attributes/DateTimeStylesAttribute.cs index b15ddc04e..64abcdafc 100644 --- a/src/CsvHelper/Configuration/Attributes/DateTimeStylesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/DateTimeStylesAttribute.cs @@ -2,43 +2,41 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Globalization; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// The to use when type converting. +/// This is used when doing any conversions. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class DateTimeStylesAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the date time styles. + /// + public DateTimeStyles DateTimeStyles { get; private set; } + /// /// The to use when type converting. /// This is used when doing any conversions. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class DateTimeStylesAttribute : Attribute, IMemberMapper, IParameterMapper + /// The date time styles. + public DateTimeStylesAttribute(DateTimeStyles dateTimeStyles) { - /// - /// Gets the date time styles. - /// - public DateTimeStyles DateTimeStyles { get; private set; } - - /// - /// The to use when type converting. - /// This is used when doing any conversions. - /// - /// The date time styles. - public DateTimeStylesAttribute(DateTimeStyles dateTimeStyles) - { - DateTimeStyles = dateTimeStyles; - } + DateTimeStyles = dateTimeStyles; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverterOptions.DateTimeStyle = DateTimeStyles; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.TypeConverterOptions.DateTimeStyle = DateTimeStyles; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.TypeConverterOptions.DateTimeStyle = DateTimeStyles; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverterOptions.DateTimeStyle = DateTimeStyles; } } diff --git a/src/CsvHelper/Configuration/Attributes/DefaultAttribute.cs b/src/CsvHelper/Configuration/Attributes/DefaultAttribute.cs index a43c16af4..ec9c8dae7 100644 --- a/src/CsvHelper/Configuration/Attributes/DefaultAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/DefaultAttribute.cs @@ -2,44 +2,41 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The default value that will be used when reading when +/// the CSV field is empty. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class DefaultAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the default value. + /// + public object Default { get; private set; } + /// /// The default value that will be used when reading when /// the CSV field is empty. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class DefaultAttribute : Attribute, IMemberMapper, IParameterMapper + /// The default value + public DefaultAttribute(object defaultValue) { - /// - /// Gets the default value. - /// - public object Default { get; private set; } - - /// - /// The default value that will be used when reading when - /// the CSV field is empty. - /// - /// The default value - public DefaultAttribute(object defaultValue) - { - Default = defaultValue; - } + Default = defaultValue; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.Default = Default; - memberMap.Data.IsDefaultSet = true; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.Default = Default; + memberMap.Data.IsDefaultSet = true; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.Default = Default; - parameterMap.Data.IsDefaultSet = true; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.Default = Default; + parameterMap.Data.IsDefaultSet = true; } } diff --git a/src/CsvHelper/Configuration/Attributes/DelimiterAttribute.cs b/src/CsvHelper/Configuration/Attributes/DelimiterAttribute.cs index c716b8b87..641cf52f8 100644 --- a/src/CsvHelper/Configuration/Attributes/DelimiterAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/DelimiterAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The delimiter used to separate fields. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class DelimiterAttribute : Attribute, IClassMapper { + /// + /// Gets the delimiter. + /// + public string Delimiter { get; private set; } + /// /// The delimiter used to separate fields. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class DelimiterAttribute : Attribute, IClassMapper + /// The delimiter. + public DelimiterAttribute(string delimiter) { - /// - /// Gets the delimiter. - /// - public string Delimiter { get; private set; } - - /// - /// The delimiter used to separate fields. - /// - /// The delimiter. - public DelimiterAttribute(string delimiter) - { - Delimiter = delimiter; - } + Delimiter = delimiter; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.Delimiter = Delimiter; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.Delimiter = Delimiter; } } diff --git a/src/CsvHelper/Configuration/Attributes/DetectColumnCountChangesAttribute.cs b/src/CsvHelper/Configuration/Attributes/DetectColumnCountChangesAttribute.cs index 3152fbef2..2fa2a7893 100644 --- a/src/CsvHelper/Configuration/Attributes/DetectColumnCountChangesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/DetectColumnCountChangesAttribute.cs @@ -2,39 +2,36 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// A value indicating whether changes in the column +/// count should be detected. If , a +/// will be thrown if a different column count is detected. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class DetectColumnCountChangesAttribute : Attribute, IClassMapper { /// /// A value indicating whether changes in the column /// count should be detected. If , a /// will be thrown if a different column count is detected. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class DetectColumnCountChangesAttribute : Attribute, IClassMapper - { - /// - /// A value indicating whether changes in the column - /// count should be detected. If , a - /// will be thrown if a different column count is detected. - /// - public bool DetectColumnCountChanges { get; private set; } + public bool DetectColumnCountChanges { get; private set; } - /// - /// A value indicating whether changes in the column - /// count should be detected. If , a - /// will be thrown if a different column count is detected. - /// - public DetectColumnCountChangesAttribute(bool detectColumnCountChanges = true) - { - DetectColumnCountChanges = detectColumnCountChanges; - } + /// + /// A value indicating whether changes in the column + /// count should be detected. If , a + /// will be thrown if a different column count is detected. + /// + public DetectColumnCountChangesAttribute(bool detectColumnCountChanges = true) + { + DetectColumnCountChanges = detectColumnCountChanges; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.DetectColumnCountChanges = DetectColumnCountChanges; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.DetectColumnCountChanges = DetectColumnCountChanges; } } diff --git a/src/CsvHelper/Configuration/Attributes/DetectDelimiterAttribute.cs b/src/CsvHelper/Configuration/Attributes/DetectDelimiterAttribute.cs index 6b32d36e9..1b6bde4dd 100644 --- a/src/CsvHelper/Configuration/Attributes/DetectDelimiterAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/DetectDelimiterAttribute.cs @@ -2,33 +2,30 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Detect the delimiter instead of using the delimiter from configuration. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class DetectDelimiterAttribute : Attribute, IClassMapper { /// /// Detect the delimiter instead of using the delimiter from configuration. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class DetectDelimiterAttribute : Attribute, IClassMapper - { - /// - /// Detect the delimiter instead of using the delimiter from configuration. - /// - public bool DetectDelimiter { get; private set; } + public bool DetectDelimiter { get; private set; } - /// - /// Detect the delimiter instead of using the delimiter from configuration. - /// - public DetectDelimiterAttribute(bool detectDelimiter = true) - { - DetectDelimiter = detectDelimiter; - } + /// + /// Detect the delimiter instead of using the delimiter from configuration. + /// + public DetectDelimiterAttribute(bool detectDelimiter = true) + { + DetectDelimiter = detectDelimiter; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.DetectDelimiter = DetectDelimiter; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.DetectDelimiter = DetectDelimiter; } } diff --git a/src/CsvHelper/Configuration/Attributes/DetectDelimiterValuesAttribute.cs b/src/CsvHelper/Configuration/Attributes/DetectDelimiterValuesAttribute.cs index df848638b..467ff85f5 100644 --- a/src/CsvHelper/Configuration/Attributes/DetectDelimiterValuesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/DetectDelimiterValuesAttribute.cs @@ -2,36 +2,34 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Text.RegularExpressions; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// The possible delimiter values used when detecting the delimiter. +/// Default is [",", ";", "|", "\t"]. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class DetectDelimiterValuesAttribute : Attribute, IClassMapper { /// /// The possible delimiter values used when detecting the delimiter. - /// Default is [",", ";", "|", "\t"]. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class DetectDelimiterValuesAttribute : Attribute, IClassMapper - { - /// - /// The possible delimiter values used when detecting the delimiter. - /// - public string[] DetectDelimiterValues { get; private set; } + public string[] DetectDelimiterValues { get; private set; } - /// - /// The possible delimiter values used when detecting the delimiter. - /// - /// Whitespace separated list of values. - public DetectDelimiterValuesAttribute(string detectDelimiterValues) - { - DetectDelimiterValues = Regex.Split(detectDelimiterValues, @"\s+"); - } + /// + /// The possible delimiter values used when detecting the delimiter. + /// + /// Whitespace separated list of values. + public DetectDelimiterValuesAttribute(string detectDelimiterValues) + { + DetectDelimiterValues = Regex.Split(detectDelimiterValues, @"\s+"); + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.DetectDelimiterValues = DetectDelimiterValues; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.DetectDelimiterValues = DetectDelimiterValues; } } diff --git a/src/CsvHelper/Configuration/Attributes/EncodingAttribute.cs b/src/CsvHelper/Configuration/Attributes/EncodingAttribute.cs index f0d00d2be..afea727da 100644 --- a/src/CsvHelper/Configuration/Attributes/EncodingAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/EncodingAttribute.cs @@ -2,44 +2,42 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Text; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// The encoding used when counting bytes. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class EncodingAttribute : Attribute, IClassMapper { - /// - /// The encoding used when counting bytes. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class EncodingAttribute : Attribute, IClassMapper - { - /// - /// Gets the encoding used when counting bytes. - /// - public Encoding Encoding { get; private set; } + /// + /// Gets the encoding used when counting bytes. + /// + public Encoding Encoding { get; private set; } - /// - /// The encoding used when counting bytes. - /// - /// - public EncodingAttribute(string name) - { - Encoding = Encoding.GetEncoding(name); - } + /// + /// The encoding used when counting bytes. + /// + /// + public EncodingAttribute(string name) + { + Encoding = Encoding.GetEncoding(name); + } - /// - /// The encoding used when counting bytes. - /// - /// - public EncodingAttribute(int codepage) - { - Encoding = Encoding.GetEncoding(codepage); - } + /// + /// The encoding used when counting bytes. + /// + /// + public EncodingAttribute(int codepage) + { + Encoding = Encoding.GetEncoding(codepage); + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.Encoding = Encoding; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.Encoding = Encoding; } } diff --git a/src/CsvHelper/Configuration/Attributes/EnumIgnoreCaseAttribute.cs b/src/CsvHelper/Configuration/Attributes/EnumIgnoreCaseAttribute.cs index 7f8e58b60..df6dc2823 100644 --- a/src/CsvHelper/Configuration/Attributes/EnumIgnoreCaseAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/EnumIgnoreCaseAttribute.cs @@ -2,40 +2,33 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Ignore case when parsing enums. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class EnumIgnoreCaseAttribute : Attribute, IMemberMapper, IMemberReferenceMapper, IParameterMapper { - /// - /// Ignore case when parsing enums. - /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class EnumIgnoreCaseAttribute : Attribute, IMemberMapper, IMemberReferenceMapper, IParameterMapper + /// + public void ApplyTo(MemberMap memberMap) { - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverterOptions.EnumIgnoreCase = true; - } - - /// - public void ApplyTo(MemberReferenceMap referenceMap) - { - foreach (var memberMap in referenceMap.Data.Mapping.MemberMaps) - { - ApplyTo(memberMap); - } - } + memberMap.Data.TypeConverterOptions.EnumIgnoreCase = true; + } - /// - public void ApplyTo(ParameterMap parameterMap) + /// + public void ApplyTo(MemberReferenceMap referenceMap) + { + foreach (var memberMap in referenceMap.Data.Mapping.MemberMaps) { - parameterMap.Data.TypeConverterOptions.EnumIgnoreCase = true; + ApplyTo(memberMap); } + } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverterOptions.EnumIgnoreCase = true; } + } diff --git a/src/CsvHelper/Configuration/Attributes/EscapeAttribute.cs b/src/CsvHelper/Configuration/Attributes/EscapeAttribute.cs index 5b698a94c..9564d2790 100644 --- a/src/CsvHelper/Configuration/Attributes/EscapeAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/EscapeAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The escape character used to escape a quote inside a field. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class EscapeAttribute : Attribute, IClassMapper { - /// - /// The escape character used to escape a quote inside a field. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class EscapeAttribute : Attribute, IClassMapper - { - /// - /// Gets the escape character used to escape a quote inside a field. - /// - public char Escape { get; private set; } + /// + /// Gets the escape character used to escape a quote inside a field. + /// + public char Escape { get; private set; } - /// - /// The escape character used to escape a quote inside a field. - /// - /// The escape character. - public EscapeAttribute(char escape) - { - Escape = escape; - } + /// + /// The escape character used to escape a quote inside a field. + /// + /// The escape character. + public EscapeAttribute(char escape) + { + Escape = escape; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.Escape = Escape; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.Escape = Escape; } } diff --git a/src/CsvHelper/Configuration/Attributes/ExceptionMessagesContainRawDataAttribute.cs b/src/CsvHelper/Configuration/Attributes/ExceptionMessagesContainRawDataAttribute.cs index 3b78e9699..8a87c334c 100644 --- a/src/CsvHelper/Configuration/Attributes/ExceptionMessagesContainRawDataAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/ExceptionMessagesContainRawDataAttribute.cs @@ -2,36 +2,33 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// A value indicating whether exception messages contain raw CSV data. +/// if exceptions contain raw CSV data, otherwise . +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class ExceptionMessagesContainRawDataAttribute : Attribute, IClassMapper { /// /// A value indicating whether exception messages contain raw CSV data. /// if exceptions contain raw CSV data, otherwise . /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class ExceptionMessagesContainRawDataAttribute : Attribute, IClassMapper - { - /// - /// A value indicating whether exception messages contain raw CSV data. - /// if exceptions contain raw CSV data, otherwise . - /// - public bool ExceptionMessagesContainRawData { get; private set; } + public bool ExceptionMessagesContainRawData { get; private set; } - /// - /// A value indicating whether exception messages contain raw CSV data. - /// if exceptions contain raw CSV data, otherwise . - /// - public ExceptionMessagesContainRawDataAttribute(bool exceptionMessagesContainRawData = true) - { - ExceptionMessagesContainRawData = exceptionMessagesContainRawData; - } + /// + /// A value indicating whether exception messages contain raw CSV data. + /// if exceptions contain raw CSV data, otherwise . + /// + public ExceptionMessagesContainRawDataAttribute(bool exceptionMessagesContainRawData = true) + { + ExceptionMessagesContainRawData = exceptionMessagesContainRawData; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.ExceptionMessagesContainRawData = ExceptionMessagesContainRawData; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.ExceptionMessagesContainRawData = ExceptionMessagesContainRawData; } } diff --git a/src/CsvHelper/Configuration/Attributes/FormatAttribute.cs b/src/CsvHelper/Configuration/Attributes/FormatAttribute.cs index c8c85a62a..0e965ef77 100644 --- a/src/CsvHelper/Configuration/Attributes/FormatAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/FormatAttribute.cs @@ -2,49 +2,46 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The string format to be used when type converting. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class FormatAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the formats. + /// + public string[] Formats { get; private set; } + /// /// The string format to be used when type converting. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class FormatAttribute : Attribute, IMemberMapper, IParameterMapper + /// The format. + public FormatAttribute(string format) { - /// - /// Gets the formats. - /// - public string[] Formats { get; private set; } - - /// - /// The string format to be used when type converting. - /// - /// The format. - public FormatAttribute(string format) - { - Formats = new string[] { format }; - } + Formats = new string[] { format }; + } - /// - /// The string format to be used when type converting. - /// - /// The formats. - public FormatAttribute(params string[] formats) - { - Formats = formats; - } + /// + /// The string format to be used when type converting. + /// + /// The formats. + public FormatAttribute(params string[] formats) + { + Formats = formats; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverterOptions.Formats = Formats; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.TypeConverterOptions.Formats = Formats; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.TypeConverterOptions.Formats = Formats; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverterOptions.Formats = Formats; } } diff --git a/src/CsvHelper/Configuration/Attributes/HasHeaderRecordAttribute.cs b/src/CsvHelper/Configuration/Attributes/HasHeaderRecordAttribute.cs index ecb90df9f..5d28ff9fe 100644 --- a/src/CsvHelper/Configuration/Attributes/HasHeaderRecordAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/HasHeaderRecordAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// A value indicating whether the CSV file has a header record. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class HasHeaderRecordAttribute : Attribute, IClassMapper { - /// - /// A value indicating whether the CSV file has a header record. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class HasHeaderRecordAttribute : Attribute, IClassMapper - { - /// - /// Gets a value indicating whether the CSV file has a header record. - /// - public bool HasHeaderRecord { get; private set; } + /// + /// Gets a value indicating whether the CSV file has a header record. + /// + public bool HasHeaderRecord { get; private set; } - /// - /// A value indicating whether the CSV file has a header record. - /// - /// A value indicating whether the CSV file has a header record. - public HasHeaderRecordAttribute(bool hasHeaderRecord = true) - { - HasHeaderRecord = hasHeaderRecord; - } + /// + /// A value indicating whether the CSV file has a header record. + /// + /// A value indicating whether the CSV file has a header record. + public HasHeaderRecordAttribute(bool hasHeaderRecord = true) + { + HasHeaderRecord = hasHeaderRecord; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.HasHeaderRecord = HasHeaderRecord; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.HasHeaderRecord = HasHeaderRecord; } } diff --git a/src/CsvHelper/Configuration/Attributes/HeaderPrefixAttribute.cs b/src/CsvHelper/Configuration/Attributes/HeaderPrefixAttribute.cs index e4dfe5406..3c8defd54 100644 --- a/src/CsvHelper/Configuration/Attributes/HeaderPrefixAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/HeaderPrefixAttribute.cs @@ -2,72 +2,69 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Appends a prefix to the header of each field of the reference member. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class HeaderPrefixAttribute : Attribute, IMemberReferenceMapper, IParameterReferenceMapper { - /// - /// Appends a prefix to the header of each field of the reference member. - /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class HeaderPrefixAttribute : Attribute, IMemberReferenceMapper, IParameterReferenceMapper - { - /// - /// Gets the prefix. - /// - public string Prefix { get; private set; } + /// + /// Gets the prefix. + /// + public string? Prefix { get; private set; } - /// - /// Gets a value indicating whether the prefix should inherit parent prefixes. - /// - public bool Inherit { get; private set; } + /// + /// Gets a value indicating whether the prefix should inherit parent prefixes. + /// + public bool Inherit { get; private set; } - /// - /// Appends a prefix to the header of each field of the reference member. - /// - public HeaderPrefixAttribute() { } + /// + /// Appends a prefix to the header of each field of the reference member. + /// + public HeaderPrefixAttribute() { } - /// - /// Appends a prefix to the header of each field of the reference member. - /// - /// The prefix. - public HeaderPrefixAttribute(string prefix) - { - Prefix = prefix; - } + /// + /// Appends a prefix to the header of each field of the reference member. + /// + /// The prefix. + public HeaderPrefixAttribute(string prefix) + { + Prefix = prefix; + } - /// - /// Appends a prefix to the header of each field of the reference member. - /// - /// Inherits parent object prefixes. - public HeaderPrefixAttribute(bool inherit) - { - Inherit = inherit; - } + /// + /// Appends a prefix to the header of each field of the reference member. + /// + /// Inherits parent object prefixes. + public HeaderPrefixAttribute(bool inherit) + { + Inherit = inherit; + } - /// - /// Appends a prefix to the header of each field of the reference member. - /// - /// The prefix. - /// Inherits parent object prefixes. - public HeaderPrefixAttribute(string prefix, bool inherit) - { - Prefix = prefix; - Inherit = inherit; - } + /// + /// Appends a prefix to the header of each field of the reference member. + /// + /// The prefix. + /// Inherits parent object prefixes. + public HeaderPrefixAttribute(string prefix, bool inherit) + { + Prefix = prefix; + Inherit = inherit; + } - /// - public void ApplyTo(MemberReferenceMap referenceMap) - { - referenceMap.Data.Inherit = Inherit; - referenceMap.Data.Prefix = Prefix ?? referenceMap.Data.Member.Name + "."; - } + /// + public void ApplyTo(MemberReferenceMap referenceMap) + { + referenceMap.Data.Inherit = Inherit; + referenceMap.Data.Prefix = Prefix ?? referenceMap.Data.Member.Name + "."; + } - /// - public void ApplyTo(ParameterReferenceMap referenceMap) - { - referenceMap.Data.Inherit = Inherit; - referenceMap.Data.Prefix = Prefix ?? referenceMap.Data.Parameter.Name + "."; - } - } + /// + public void ApplyTo(ParameterReferenceMap referenceMap) + { + referenceMap.Data.Inherit = Inherit; + referenceMap.Data.Prefix = Prefix ?? referenceMap.Data.Parameter.Name + "."; + } } diff --git a/src/CsvHelper/Configuration/Attributes/IClassMapper.cs b/src/CsvHelper/Configuration/Attributes/IClassMapper.cs index 1c184630e..9a379c7e1 100644 --- a/src/CsvHelper/Configuration/Attributes/IClassMapper.cs +++ b/src/CsvHelper/Configuration/Attributes/IClassMapper.cs @@ -2,17 +2,16 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// Defines methods to enable pluggable configuration. +/// +public interface IClassMapper { /// - /// Defines methods to enable pluggable configuration. + /// Applies configuration. /// - public interface IClassMapper - { - /// - /// Applies configuration. - /// - /// The configuration to apply to. - void ApplyTo(CsvConfiguration configuration); - } + /// The configuration to apply to. + void ApplyTo(CsvConfiguration configuration); } diff --git a/src/CsvHelper/Configuration/Attributes/IMemberMapper.cs b/src/CsvHelper/Configuration/Attributes/IMemberMapper.cs index 13848ea2f..6d55e52c0 100644 --- a/src/CsvHelper/Configuration/Attributes/IMemberMapper.cs +++ b/src/CsvHelper/Configuration/Attributes/IMemberMapper.cs @@ -2,19 +2,16 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Defines methods to enable pluggable configuration of member mapping. +/// +public interface IMemberMapper { /// - /// Defines methods to enable pluggable configuration of member mapping. + /// Applies configuration to the given . /// - public interface IMemberMapper - { - /// - /// Applies configuration to the given . - /// - /// The member map. - void ApplyTo(MemberMap memberMap); - } + /// The member map. + void ApplyTo(MemberMap memberMap); } diff --git a/src/CsvHelper/Configuration/Attributes/IMemberReferenceMapper.cs b/src/CsvHelper/Configuration/Attributes/IMemberReferenceMapper.cs index c1c645eeb..108b5e66e 100644 --- a/src/CsvHelper/Configuration/Attributes/IMemberReferenceMapper.cs +++ b/src/CsvHelper/Configuration/Attributes/IMemberReferenceMapper.cs @@ -2,19 +2,16 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Defines methods to enable pluggable configuration of member reference mapping. +/// +public interface IMemberReferenceMapper { /// - /// Defines methods to enable pluggable configuration of member reference mapping. + /// Applies configuration to the given . /// - public interface IMemberReferenceMapper - { - /// - /// Applies configuration to the given . - /// - /// The reference map. - void ApplyTo(MemberReferenceMap referenceMap); - } + /// The reference map. + void ApplyTo(MemberReferenceMap referenceMap); } diff --git a/src/CsvHelper/Configuration/Attributes/IParameterMapper.cs b/src/CsvHelper/Configuration/Attributes/IParameterMapper.cs index 44dd907da..7ab553c8a 100644 --- a/src/CsvHelper/Configuration/Attributes/IParameterMapper.cs +++ b/src/CsvHelper/Configuration/Attributes/IParameterMapper.cs @@ -2,23 +2,16 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Defines methods to enable pluggable configuration of parameter mapping. +/// +public interface IParameterMapper { /// - /// Defines methods to enable pluggable configuration of parameter mapping. + /// Applies configuration to the given . /// - public interface IParameterMapper - { - /// - /// Applies configuration to the given . - /// - /// The parameter map. - void ApplyTo(ParameterMap parameterMap); - } + /// The parameter map. + void ApplyTo(ParameterMap parameterMap); } diff --git a/src/CsvHelper/Configuration/Attributes/IParameterReferenceMapper.cs b/src/CsvHelper/Configuration/Attributes/IParameterReferenceMapper.cs index a29d4fb2d..be68953bf 100644 --- a/src/CsvHelper/Configuration/Attributes/IParameterReferenceMapper.cs +++ b/src/CsvHelper/Configuration/Attributes/IParameterReferenceMapper.cs @@ -2,23 +2,16 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Defines methods to enable pluggable configuration of parameter reference mapping. +/// +public interface IParameterReferenceMapper { /// - /// Defines methods to enable pluggable configuration of parameter reference mapping. + /// Applies configuration to the given . /// - public interface IParameterReferenceMapper - { - /// - /// Applies configuration to the given . - /// - /// The reference map. - void ApplyTo(ParameterReferenceMap referenceMap); - } + /// The reference map. + void ApplyTo(ParameterReferenceMap referenceMap); } diff --git a/src/CsvHelper/Configuration/Attributes/IgnoreAttribute.cs b/src/CsvHelper/Configuration/Attributes/IgnoreAttribute.cs index 600392b97..6e1bd1f24 100644 --- a/src/CsvHelper/Configuration/Attributes/IgnoreAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/IgnoreAttribute.cs @@ -2,40 +2,36 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Reflection; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Ignore the member when reading and writing. +/// If this member has already been mapped as a reference +/// member, either by a class map, or by automapping, calling +/// this method will not ignore all the child members down the +/// tree that have already been mapped. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class IgnoreAttribute : Attribute, IMemberMapper, IMemberReferenceMapper, IParameterMapper { - /// - /// Ignore the member when reading and writing. - /// If this member has already been mapped as a reference - /// member, either by a class map, or by automapping, calling - /// this method will not ignore all the child members down the - /// tree that have already been mapped. - /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class IgnoreAttribute : Attribute, IMemberMapper, IMemberReferenceMapper, IParameterMapper + /// + public void ApplyTo(MemberMap memberMap) { - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.Ignore = true; - } + memberMap.Data.Ignore = true; + } - /// - public void ApplyTo(MemberReferenceMap referenceMap) + /// + public void ApplyTo(MemberReferenceMap referenceMap) + { + foreach (var memberMap in referenceMap.Data.Mapping.MemberMaps) { - foreach (var memberMap in referenceMap.Data.Mapping.MemberMaps) - { - ApplyTo(memberMap); - } + ApplyTo(memberMap); } + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.Ignore = true; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.Ignore = true; } } diff --git a/src/CsvHelper/Configuration/Attributes/IgnoreBaseAttribute.cs b/src/CsvHelper/Configuration/Attributes/IgnoreBaseAttribute.cs index 7949d6098..71d41162c 100644 --- a/src/CsvHelper/Configuration/Attributes/IgnoreBaseAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/IgnoreBaseAttribute.cs @@ -2,19 +2,12 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Ignores base classes when auto mapping. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class IgnoreBaseAttribute : Attribute { - /// - /// Ignores base classes when auto mapping. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class IgnoreBaseAttribute : Attribute - { - } } diff --git a/src/CsvHelper/Configuration/Attributes/IgnoreBlankLinesAttribute.cs b/src/CsvHelper/Configuration/Attributes/IgnoreBlankLinesAttribute.cs index 8820b292a..5875acf14 100644 --- a/src/CsvHelper/Configuration/Attributes/IgnoreBlankLinesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/IgnoreBlankLinesAttribute.cs @@ -2,33 +2,30 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// A value indicating whether blank lines should be ignored when reading. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class IgnoreBlankLinesAttribute : Attribute, IClassMapper { - /// - /// A value indicating whether blank lines should be ignored when reading. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class IgnoreBlankLinesAttribute : Attribute, IClassMapper - { - /// - /// Gets a value indicating whether blank lines should be ignored when reading. - /// - public bool IgnoreBlankLines { get; private set; } + /// + /// Gets a value indicating whether blank lines should be ignored when reading. + /// + public bool IgnoreBlankLines { get; private set; } - /// - /// A value indicating whether blank lines should be ignored when reading. - /// - public IgnoreBlankLinesAttribute(bool ignoreBlankLines = true) - { - IgnoreBlankLines = ignoreBlankLines; - } + /// + /// A value indicating whether blank lines should be ignored when reading. + /// + public IgnoreBlankLinesAttribute(bool ignoreBlankLines = true) + { + IgnoreBlankLines = ignoreBlankLines; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.IgnoreBlankLines = IgnoreBlankLines; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.IgnoreBlankLines = IgnoreBlankLines; } } diff --git a/src/CsvHelper/Configuration/Attributes/IgnoreReferencesAttribute.cs b/src/CsvHelper/Configuration/Attributes/IgnoreReferencesAttribute.cs index 1ce85243c..bc275ba98 100644 --- a/src/CsvHelper/Configuration/Attributes/IgnoreReferencesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/IgnoreReferencesAttribute.cs @@ -2,39 +2,36 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Gets a value indicating whether references +/// should be ignored when auto mapping. to ignore +/// references, otherwise . +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class IgnoreReferencesAttribute : Attribute, IClassMapper { /// /// Gets a value indicating whether references /// should be ignored when auto mapping. to ignore /// references, otherwise . /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class IgnoreReferencesAttribute : Attribute, IClassMapper - { - /// - /// Gets a value indicating whether references - /// should be ignored when auto mapping. to ignore - /// references, otherwise . - /// - public bool IgnoreReferences { get; private set; } + public bool IgnoreReferences { get; private set; } - /// - /// Gets a value indicating whether references - /// should be ignored when auto mapping. to ignore - /// references, otherwise . - /// - public IgnoreReferencesAttribute(bool ignoreReferences = true) - { - IgnoreReferences = ignoreReferences; - } + /// + /// Gets a value indicating whether references + /// should be ignored when auto mapping. to ignore + /// references, otherwise . + /// + public IgnoreReferencesAttribute(bool ignoreReferences = true) + { + IgnoreReferences = ignoreReferences; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.IgnoreReferences = IgnoreReferences; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.IgnoreReferences = IgnoreReferences; } } diff --git a/src/CsvHelper/Configuration/Attributes/IncludePrivateMembersAttribute.cs b/src/CsvHelper/Configuration/Attributes/IncludePrivateMembersAttribute.cs index c802934fa..3e6b1fd7f 100644 --- a/src/CsvHelper/Configuration/Attributes/IncludePrivateMembersAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/IncludePrivateMembersAttribute.cs @@ -2,33 +2,30 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// A value indicating whether private members should be read from and written to. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class IncludePrivateMembersAttribute : Attribute, IClassMapper { - /// - /// A value indicating whether private members should be read from and written to. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class IncludePrivateMembersAttribute : Attribute, IClassMapper - { - /// - /// Gets a value indicating whether private members should be read from and written to. - /// - public bool IncludePrivateMembers { get; private set; } + /// + /// Gets a value indicating whether private members should be read from and written to. + /// + public bool IncludePrivateMembers { get; private set; } - /// - /// A value indicating whether private members should be read from and written to. - /// - public IncludePrivateMembersAttribute(bool includePrivateMembers = true) - { - IncludePrivateMembers = includePrivateMembers; - } + /// + /// A value indicating whether private members should be read from and written to. + /// + public IncludePrivateMembersAttribute(bool includePrivateMembers = true) + { + IncludePrivateMembers = includePrivateMembers; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.IncludePrivateMembers = IncludePrivateMembers; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.IncludePrivateMembers = IncludePrivateMembers; } } diff --git a/src/CsvHelper/Configuration/Attributes/IndexAttribute.cs b/src/CsvHelper/Configuration/Attributes/IndexAttribute.cs index 6f3526e88..2a050118e 100644 --- a/src/CsvHelper/Configuration/Attributes/IndexAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/IndexAttribute.cs @@ -2,56 +2,53 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// When reading, is used to get the field at +/// the given index. When writing, the fields +/// will be written in the order of the field +/// indexes. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class IndexAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the index. + /// + public int Index { get; private set; } + + /// + /// Gets the index end. + /// + public int IndexEnd { get; private set; } + /// /// When reading, is used to get the field at /// the given index. When writing, the fields /// will be written in the order of the field /// indexes. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class IndexAttribute : Attribute, IMemberMapper, IParameterMapper + /// The index. + /// The index end. + public IndexAttribute(int index, int indexEnd = -1) { - /// - /// Gets the index. - /// - public int Index { get; private set; } - - /// - /// Gets the index end. - /// - public int IndexEnd { get; private set; } - - /// - /// When reading, is used to get the field at - /// the given index. When writing, the fields - /// will be written in the order of the field - /// indexes. - /// - /// The index. - /// The index end. - public IndexAttribute(int index, int indexEnd = -1) - { - Index = index; - IndexEnd = indexEnd; - } + Index = index; + IndexEnd = indexEnd; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.Index = Index; - memberMap.Data.IndexEnd = IndexEnd; - memberMap.Data.IsIndexSet = true; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.Index = Index; + memberMap.Data.IndexEnd = IndexEnd; + memberMap.Data.IsIndexSet = true; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.Index = Index; - parameterMap.Data.IsIndexSet = true; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.Index = Index; + parameterMap.Data.IsIndexSet = true; } } diff --git a/src/CsvHelper/Configuration/Attributes/InjectionCharactersAttribute.cs b/src/CsvHelper/Configuration/Attributes/InjectionCharactersAttribute.cs index fac1563cb..41cd33ce2 100644 --- a/src/CsvHelper/Configuration/Attributes/InjectionCharactersAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/InjectionCharactersAttribute.cs @@ -2,37 +2,34 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Linq; using System.Text.RegularExpressions; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// Gets the characters that are used for injection attacks. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class InjectionCharactersAttribute : Attribute, IClassMapper { /// /// Gets the characters that are used for injection attacks. + /// Default is '=', '@', '+', '-', '\t', '\r'. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class InjectionCharactersAttribute : Attribute, IClassMapper - { - /// - /// Gets the characters that are used for injection attacks. - /// Default is '=', '@', '+', '-', '\t', '\r'. - /// - public char[] InjectionCharacters { get; private set; } + public char[] InjectionCharacters { get; private set; } - /// - /// Gets the characters that are used for injection attacks. - /// - /// - public InjectionCharactersAttribute(string injectionCharacters) - { - InjectionCharacters = Regex.Split(injectionCharacters, @"\s+").Select(s => s[0]).ToArray(); - } + /// + /// Gets the characters that are used for injection attacks. + /// + /// + public InjectionCharactersAttribute(string injectionCharacters) + { + InjectionCharacters = Regex.Split(injectionCharacters, @"\s+").Select(s => s[0]).ToArray(); + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.InjectionCharacters = InjectionCharacters; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.InjectionCharacters = InjectionCharacters; } } diff --git a/src/CsvHelper/Configuration/Attributes/InjectionEscapeCharacterAttribute.cs b/src/CsvHelper/Configuration/Attributes/InjectionEscapeCharacterAttribute.cs index d4a8c3f3f..775264626 100644 --- a/src/CsvHelper/Configuration/Attributes/InjectionEscapeCharacterAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/InjectionEscapeCharacterAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The character used to escape a detected injection. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class InjectionEscapeCharacterAttribute : Attribute, IClassMapper { /// /// The character used to escape a detected injection. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class InjectionEscapeCharacterAttribute : Attribute, IClassMapper - { - /// - /// The character used to escape a detected injection. - /// - public char InjectionEscapeCharacter { get; private set; } + public char InjectionEscapeCharacter { get; private set; } - /// - /// The character used to escape a detected injection. - /// - /// - public InjectionEscapeCharacterAttribute(char injectionEscapeCharacter) - { - InjectionEscapeCharacter = injectionEscapeCharacter; - } + /// + /// The character used to escape a detected injection. + /// + /// + public InjectionEscapeCharacterAttribute(char injectionEscapeCharacter) + { + InjectionEscapeCharacter = injectionEscapeCharacter; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.InjectionEscapeCharacter = InjectionEscapeCharacter; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.InjectionEscapeCharacter = InjectionEscapeCharacter; } } diff --git a/src/CsvHelper/Configuration/Attributes/InjectionOptionsAttribute.cs b/src/CsvHelper/Configuration/Attributes/InjectionOptionsAttribute.cs index 4031a09af..febf89eae 100644 --- a/src/CsvHelper/Configuration/Attributes/InjectionOptionsAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/InjectionOptionsAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The injection options. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class InjectionOptionsAttribute : Attribute, IClassMapper { /// /// The injection options. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class InjectionOptionsAttribute : Attribute, IClassMapper - { - /// - /// The injection options. - /// - public InjectionOptions InjectionOptions { get; private set; } + public InjectionOptions InjectionOptions { get; private set; } - /// - /// The injection options. - /// - /// - public InjectionOptionsAttribute(InjectionOptions injectionOptions) - { - InjectionOptions = injectionOptions; - } + /// + /// The injection options. + /// + /// + public InjectionOptionsAttribute(InjectionOptions injectionOptions) + { + InjectionOptions = injectionOptions; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.InjectionOptions = InjectionOptions; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.InjectionOptions = InjectionOptions; } } diff --git a/src/CsvHelper/Configuration/Attributes/LineBreakInQuotedFieldIsBadDataAttribute.cs b/src/CsvHelper/Configuration/Attributes/LineBreakInQuotedFieldIsBadDataAttribute.cs index b72839976..b6468de73 100644 --- a/src/CsvHelper/Configuration/Attributes/LineBreakInQuotedFieldIsBadDataAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/LineBreakInQuotedFieldIsBadDataAttribute.cs @@ -2,36 +2,33 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// A value indicating whether a line break found in a quote field should +/// be considered bad data. to consider a line break bad data, otherwise . +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class LineBreakInQuotedFieldIsBadDataAttribute : Attribute, IClassMapper { /// /// A value indicating whether a line break found in a quote field should /// be considered bad data. to consider a line break bad data, otherwise . /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class LineBreakInQuotedFieldIsBadDataAttribute : Attribute, IClassMapper - { - /// - /// A value indicating whether a line break found in a quote field should - /// be considered bad data. to consider a line break bad data, otherwise . - /// - public bool LineBreakInQuotedFieldIsBadData { get; private set; } + public bool LineBreakInQuotedFieldIsBadData { get; private set; } - /// - /// A value indicating whether a line break found in a quote field should - /// be considered bad data. to consider a line break bad data, otherwise . - /// - public LineBreakInQuotedFieldIsBadDataAttribute(bool lineBreakInQuotedFieldIsBadData = true) - { - LineBreakInQuotedFieldIsBadData = lineBreakInQuotedFieldIsBadData; - } + /// + /// A value indicating whether a line break found in a quote field should + /// be considered bad data. to consider a line break bad data, otherwise . + /// + public LineBreakInQuotedFieldIsBadDataAttribute(bool lineBreakInQuotedFieldIsBadData = true) + { + LineBreakInQuotedFieldIsBadData = lineBreakInQuotedFieldIsBadData; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.LineBreakInQuotedFieldIsBadData = LineBreakInQuotedFieldIsBadData; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.LineBreakInQuotedFieldIsBadData = LineBreakInQuotedFieldIsBadData; } } diff --git a/src/CsvHelper/Configuration/Attributes/MaxFieldSizeAttribute.cs b/src/CsvHelper/Configuration/Attributes/MaxFieldSizeAttribute.cs index 9bbb3949c..c7eeb847b 100644 --- a/src/CsvHelper/Configuration/Attributes/MaxFieldSizeAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/MaxFieldSizeAttribute.cs @@ -2,39 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Gets or sets the maximum size of a field. +/// Defaults to 0, indicating maximum field size is not checked. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class MaxFieldSizeAttribute : Attribute, IClassMapper { /// /// Gets or sets the maximum size of a field. - /// Defaults to 0, indicating maximum field size is not checked. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class MaxFieldSizeAttribute : Attribute, IClassMapper - { - /// - /// Gets or sets the maximum size of a field. - /// - public double MaxFieldSize { get; private set; } + public double MaxFieldSize { get; private set; } - /// - /// Gets or sets the maximum size of a field. - /// - /// - public MaxFieldSizeAttribute(double maxFieldSize) - { - MaxFieldSize = maxFieldSize; - } + /// + /// Gets or sets the maximum size of a field. + /// + /// + public MaxFieldSizeAttribute(double maxFieldSize) + { + MaxFieldSize = maxFieldSize; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.MaxFieldSize = MaxFieldSize; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.MaxFieldSize = MaxFieldSize; } } diff --git a/src/CsvHelper/Configuration/Attributes/MemberTypesAttribute.cs b/src/CsvHelper/Configuration/Attributes/MemberTypesAttribute.cs index 3cd6a4182..5392d3e2f 100644 --- a/src/CsvHelper/Configuration/Attributes/MemberTypesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/MemberTypesAttribute.cs @@ -2,39 +2,36 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The member types that are used when auto mapping. +/// MemberTypes are flags, so you can choose more than one. +/// Default is Properties. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class MemberTypesAttribute : Attribute, IClassMapper { /// /// The member types that are used when auto mapping. /// MemberTypes are flags, so you can choose more than one. /// Default is Properties. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class MemberTypesAttribute : Attribute, IClassMapper - { - /// - /// The member types that are used when auto mapping. - /// MemberTypes are flags, so you can choose more than one. - /// Default is Properties. - /// - public MemberTypes MemberTypes { get; private set; } + public MemberTypes MemberTypes { get; private set; } - /// - /// The member types that are used when auto mapping. - /// MemberTypes are flags, so you can choose more than one. - /// Default is Properties. - /// - public MemberTypesAttribute(MemberTypes memberTypes) - { - MemberTypes = memberTypes; - } + /// + /// The member types that are used when auto mapping. + /// MemberTypes are flags, so you can choose more than one. + /// Default is Properties. + /// + public MemberTypesAttribute(MemberTypes memberTypes) + { + MemberTypes = memberTypes; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.MemberTypes = MemberTypes; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.MemberTypes = MemberTypes; } } diff --git a/src/CsvHelper/Configuration/Attributes/ModeAttribute.cs b/src/CsvHelper/Configuration/Attributes/ModeAttribute.cs index d4e4c3374..79832480f 100644 --- a/src/CsvHelper/Configuration/Attributes/ModeAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/ModeAttribute.cs @@ -2,37 +2,34 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The mode. +/// See for more details. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class ModeAttribute : Attribute, IClassMapper { /// /// The mode. /// See for more details. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class ModeAttribute : Attribute, IClassMapper - { - /// - /// The mode. - /// See for more details. - /// - public CsvMode Mode { get; private set; } + public CsvMode Mode { get; private set; } - /// - /// The mode. - /// See for more details. - /// - /// - public ModeAttribute(CsvMode mode) - { - Mode = mode; - } + /// + /// The mode. + /// See for more details. + /// + /// + public ModeAttribute(CsvMode mode) + { + Mode = mode; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.Mode = Mode; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.Mode = Mode; } } diff --git a/src/CsvHelper/Configuration/Attributes/NameAttribute.cs b/src/CsvHelper/Configuration/Attributes/NameAttribute.cs index 27e2d104d..197ffcc5f 100644 --- a/src/CsvHelper/Configuration/Attributes/NameAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/NameAttribute.cs @@ -2,10 +2,25 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// When reading, is used to get the field +/// at the index of the name if there was a +/// header specified. It will look for the +/// first name match in the order listed. +/// When writing, sets the name of the +/// field in the header record. +/// The first name will be used. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class NameAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the names. + /// + public string[] Names { get; private set; } + /// /// When reading, is used to get the field /// at the index of the name if there was a @@ -15,63 +30,45 @@ namespace CsvHelper.Configuration.Attributes /// field in the header record. /// The first name will be used. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class NameAttribute : Attribute, IMemberMapper, IParameterMapper + /// The name + public NameAttribute(string name) { - /// - /// Gets the names. - /// - public string[] Names { get; private set; } + Names = new string[] { name }; + } - /// - /// When reading, is used to get the field - /// at the index of the name if there was a - /// header specified. It will look for the - /// first name match in the order listed. - /// When writing, sets the name of the - /// field in the header record. - /// The first name will be used. - /// - /// The name - public NameAttribute(string name) + /// + /// When reading, is used to get the field + /// at the index of the name if there was a + /// header specified. It will look for the + /// first name match in the order listed. + /// When writing, sets the name of the + /// field in the header record. + /// The first name will be used. + /// + /// The names. + public NameAttribute(params string[] names) + { + if (names == null || names.Length == 0) { - Names = new string[] { name }; + throw new ArgumentNullException(nameof(names)); } - /// - /// When reading, is used to get the field - /// at the index of the name if there was a - /// header specified. It will look for the - /// first name match in the order listed. - /// When writing, sets the name of the - /// field in the header record. - /// The first name will be used. - /// - /// The names. - public NameAttribute(params string[] names) - { - if (names == null || names.Length == 0) - { - throw new ArgumentNullException(nameof(names)); - } - - Names = names; - } + Names = names; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.Names.Clear(); - memberMap.Data.Names.AddRange(Names); - memberMap.Data.IsNameSet = true; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.Names.Clear(); + memberMap.Data.Names.AddRange(Names); + memberMap.Data.IsNameSet = true; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.Names.Clear(); - parameterMap.Data.Names.AddRange(Names); - parameterMap.Data.IsNameSet = true; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.Names.Clear(); + parameterMap.Data.Names.AddRange(Names); + parameterMap.Data.IsNameSet = true; } } diff --git a/src/CsvHelper/Configuration/Attributes/NameIndexAttribute.cs b/src/CsvHelper/Configuration/Attributes/NameIndexAttribute.cs index 821a23ee2..13d96fe19 100644 --- a/src/CsvHelper/Configuration/Attributes/NameIndexAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/NameIndexAttribute.cs @@ -2,44 +2,41 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// When reading, is used to get the +/// index of the name used when there +/// are multiple names that are the same. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class NameIndexAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// The name index. + /// + public int NameIndex { get; private set; } + /// /// When reading, is used to get the /// index of the name used when there /// are multiple names that are the same. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class NameIndexAttribute : Attribute, IMemberMapper, IParameterMapper + /// The name index. + public NameIndexAttribute(int nameIndex) { - /// - /// The name index. - /// - public int NameIndex { get; private set; } - - /// - /// When reading, is used to get the - /// index of the name used when there - /// are multiple names that are the same. - /// - /// The name index. - public NameIndexAttribute(int nameIndex) - { - NameIndex = nameIndex; - } + NameIndex = nameIndex; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.NameIndex = NameIndex; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.NameIndex = NameIndex; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.NameIndex = NameIndex; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.NameIndex = NameIndex; } } diff --git a/src/CsvHelper/Configuration/Attributes/NewLineAttribute.cs b/src/CsvHelper/Configuration/Attributes/NewLineAttribute.cs index ff299ffc5..8b2f407c3 100644 --- a/src/CsvHelper/Configuration/Attributes/NewLineAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/NewLineAttribute.cs @@ -2,38 +2,35 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The newline string to use. Default is \r\n (CRLF). +/// When writing, this value is always used. +/// When reading, this value is only used if explicitly set. +/// If not set, the parser uses one of \r\n, \r, or \n. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class NewLineAttribute : Attribute, IClassMapper { - /// /// The newline string to use. Default is \r\n (CRLF). /// When writing, this value is always used. /// When reading, this value is only used if explicitly set. /// If not set, the parser uses one of \r\n, \r, or \n. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class NewLineAttribute : Attribute, IClassMapper - { - /// The newline string to use. Default is \r\n (CRLF). - /// When writing, this value is always used. - /// When reading, this value is only used if explicitly set. - /// If not set, the parser uses one of \r\n, \r, or \n. - public string NewLine { get; private set; } + public string NewLine { get; private set; } - /// The newline string to use. Default is \r\n (CRLF). - /// When writing, this value is always used. - /// When reading, this value is only used if explicitly set. - /// If not set, the parser uses one of \r\n, \r, or \n. - public NewLineAttribute(string newLine) - { - NewLine = newLine; - } + /// The newline string to use. Default is \r\n (CRLF). + /// When writing, this value is always used. + /// When reading, this value is only used if explicitly set. + /// If not set, the parser uses one of \r\n, \r, or \n. + public NewLineAttribute(string newLine) + { + NewLine = newLine; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.NewLine = NewLine; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.NewLine = NewLine; } } diff --git a/src/CsvHelper/Configuration/Attributes/NullValuesAttribute.cs b/src/CsvHelper/Configuration/Attributes/NullValuesAttribute.cs index 99ec6c40d..e4b83e53a 100644 --- a/src/CsvHelper/Configuration/Attributes/NullValuesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/NullValuesAttribute.cs @@ -2,51 +2,48 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The string values used to represent null when converting. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class NullValuesAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the null values. + /// + public string[] NullValues { get; private set; } + /// /// The string values used to represent null when converting. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class NullValuesAttribute : Attribute, IMemberMapper, IParameterMapper + /// The null values. + public NullValuesAttribute(string nullValue) { - /// - /// Gets the null values. - /// - public string[] NullValues { get; private set; } - - /// - /// The string values used to represent null when converting. - /// - /// The null values. - public NullValuesAttribute(string nullValue) - { - NullValues = new string[] { nullValue }; - } + NullValues = new string[] { nullValue }; + } - /// - /// The string values used to represent null when converting. - /// - /// The null values. - public NullValuesAttribute(params string[] nullValues) - { - NullValues = nullValues; - } + /// + /// The string values used to represent null when converting. + /// + /// The null values. + public NullValuesAttribute(params string[] nullValues) + { + NullValues = nullValues; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverterOptions.NullValues.Clear(); - memberMap.Data.TypeConverterOptions.NullValues.AddRange(NullValues); - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.TypeConverterOptions.NullValues.Clear(); + memberMap.Data.TypeConverterOptions.NullValues.AddRange(NullValues); + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.TypeConverterOptions.NullValues.Clear(); - parameterMap.Data.TypeConverterOptions.NullValues.AddRange(NullValues); - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverterOptions.NullValues.Clear(); + parameterMap.Data.TypeConverterOptions.NullValues.AddRange(NullValues); } } diff --git a/src/CsvHelper/Configuration/Attributes/NumberStylesAttribute.cs b/src/CsvHelper/Configuration/Attributes/NumberStylesAttribute.cs index 67405dd0c..af3397001 100644 --- a/src/CsvHelper/Configuration/Attributes/NumberStylesAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/NumberStylesAttribute.cs @@ -2,43 +2,41 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Globalization; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// The to use when type converting. +/// This is used when doing any number conversions. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class NumberStylesAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the number styles. + /// + public NumberStyles NumberStyles { get; private set; } + /// /// The to use when type converting. /// This is used when doing any number conversions. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class NumberStylesAttribute : Attribute, IMemberMapper, IParameterMapper + /// The number styles. + public NumberStylesAttribute(NumberStyles numberStyles) { - /// - /// Gets the number styles. - /// - public NumberStyles NumberStyles { get; private set; } - - /// - /// The to use when type converting. - /// This is used when doing any number conversions. - /// - /// The number styles. - public NumberStylesAttribute(NumberStyles numberStyles) - { - NumberStyles = numberStyles; - } + NumberStyles = numberStyles; + } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverterOptions.NumberStyles = NumberStyles; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.TypeConverterOptions.NumberStyles = NumberStyles; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.TypeConverterOptions.NumberStyles = NumberStyles; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverterOptions.NumberStyles = NumberStyles; } } diff --git a/src/CsvHelper/Configuration/Attributes/OptionalAttribute.cs b/src/CsvHelper/Configuration/Attributes/OptionalAttribute.cs index 6a00e9b95..8b566db05 100644 --- a/src/CsvHelper/Configuration/Attributes/OptionalAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/OptionalAttribute.cs @@ -2,26 +2,23 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Ignore the member when reading if no matching field name can be found. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class OptionalAttribute : Attribute, IMemberMapper, IParameterMapper { - /// - /// Ignore the member when reading if no matching field name can be found. - /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class OptionalAttribute : Attribute, IMemberMapper, IParameterMapper + /// + public void ApplyTo(MemberMap memberMap) { - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.IsOptional = true; - } + memberMap.Data.IsOptional = true; + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.IsOptional = true; - } + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.IsOptional = true; } } diff --git a/src/CsvHelper/Configuration/Attributes/ProcessFieldBufferSizeAttribute.cs b/src/CsvHelper/Configuration/Attributes/ProcessFieldBufferSizeAttribute.cs index 13b2d6639..68954b523 100644 --- a/src/CsvHelper/Configuration/Attributes/ProcessFieldBufferSizeAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/ProcessFieldBufferSizeAttribute.cs @@ -2,35 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The size of the buffer used when processing fields. +/// Default is 1024. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class ProcessFieldBufferSizeAttribute : Attribute, IClassMapper { /// /// The size of the buffer used when processing fields. - /// Default is 1024. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class ProcessFieldBufferSizeAttribute : Attribute, IClassMapper - { - /// - /// The size of the buffer used when processing fields. - /// - public int ProcessFieldBufferSize { get; private set; } + public int ProcessFieldBufferSize { get; private set; } - /// - /// The size of the buffer used when processing fields. - /// - /// - public ProcessFieldBufferSizeAttribute(int processFieldBufferSize) - { - ProcessFieldBufferSize = processFieldBufferSize; - } + /// + /// The size of the buffer used when processing fields. + /// + /// + public ProcessFieldBufferSizeAttribute(int processFieldBufferSize) + { + ProcessFieldBufferSize = processFieldBufferSize; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.ProcessFieldBufferSize = ProcessFieldBufferSize; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.ProcessFieldBufferSize = ProcessFieldBufferSize; } } diff --git a/src/CsvHelper/Configuration/Attributes/QuoteAttribute.cs b/src/CsvHelper/Configuration/Attributes/QuoteAttribute.cs index 085138d5e..25c5eb91d 100644 --- a/src/CsvHelper/Configuration/Attributes/QuoteAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/QuoteAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The character used to quote fields. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class QuoteAttribute : Attribute, IClassMapper { - /// - /// The character used to quote fields. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class QuoteAttribute : Attribute, IClassMapper - { - /// - /// Gets the character used to quote fields. - /// - public char Quote { get; private set; } + /// + /// Gets the character used to quote fields. + /// + public char Quote { get; private set; } - /// - /// The character used to quote fields. - /// - /// The quote character. - public QuoteAttribute(char quote) - { - Quote = quote; - } + /// + /// The character used to quote fields. + /// + /// The quote character. + public QuoteAttribute(char quote) + { + Quote = quote; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.Quote = Quote; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.Quote = Quote; } } diff --git a/src/CsvHelper/Configuration/Attributes/TrimOptionsAttribute.cs b/src/CsvHelper/Configuration/Attributes/TrimOptionsAttribute.cs index 38a17d7ef..83b20bf37 100644 --- a/src/CsvHelper/Configuration/Attributes/TrimOptionsAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/TrimOptionsAttribute.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// The fields trimming options. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class TrimOptionsAttribute : Attribute, IClassMapper { - /// - /// The fields trimming options. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class TrimOptionsAttribute : Attribute, IClassMapper - { - /// - /// Gets the fields trimming options. - /// - public TrimOptions TrimOptions { get; private set; } + /// + /// Gets the fields trimming options. + /// + public TrimOptions TrimOptions { get; private set; } - /// - /// The fields trimming options. - /// - /// The TrimOptions. - public TrimOptionsAttribute(TrimOptions trimOptions) - { - TrimOptions = trimOptions; - } + /// + /// The fields trimming options. + /// + /// The TrimOptions. + public TrimOptionsAttribute(TrimOptions trimOptions) + { + TrimOptions = trimOptions; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.TrimOptions = TrimOptions; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.TrimOptions = TrimOptions; } } diff --git a/src/CsvHelper/Configuration/Attributes/TypeConverterAttribute.cs b/src/CsvHelper/Configuration/Attributes/TypeConverterAttribute.cs index 9ba321843..170664c98 100644 --- a/src/CsvHelper/Configuration/Attributes/TypeConverterAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/TypeConverterAttribute.cs @@ -3,55 +3,53 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.TypeConversion; -using System; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// Specifies the to use +/// when converting the member to and from a CSV field. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] +public class TypeConverterAttribute : Attribute, IMemberMapper, IParameterMapper { + /// + /// Gets the type converter. + /// + public ITypeConverter TypeConverter { get; private set; } + /// /// Specifies the to use /// when converting the member to and from a CSV field. /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public class TypeConverterAttribute : Attribute, IMemberMapper, IParameterMapper - { - /// - /// Gets the type converter. - /// - public ITypeConverter TypeConverter { get; private set; } - - /// - /// Specifies the to use - /// when converting the member to and from a CSV field. - /// - /// The type of the . - public TypeConverterAttribute(Type typeConverterType) : this(typeConverterType, new object[0]) { } + /// The type of the . + public TypeConverterAttribute(Type typeConverterType) : this(typeConverterType, new object[0]) { } - /// - /// Specifies the to use - /// when converting the member to and from a CSV field. - /// - /// The type of the . - /// Type constructor arguments for the type converter. - public TypeConverterAttribute(Type typeConverterType, params object[] constructorArgs) + /// + /// Specifies the to use + /// when converting the member to and from a CSV field. + /// + /// The type of the . + /// Type constructor arguments for the type converter. + public TypeConverterAttribute(Type typeConverterType, params object[] constructorArgs) + { + if (typeConverterType == null) { - if (typeConverterType == null) - { - throw new ArgumentNullException(nameof(typeConverterType)); - } - - TypeConverter = ObjectResolver.Current.Resolve(typeConverterType, constructorArgs) as ITypeConverter ?? throw new ArgumentException($"Type '{typeConverterType.FullName}' does not implement {nameof(ITypeConverter)}"); + throw new ArgumentNullException(nameof(typeConverterType)); } - /// - public void ApplyTo(MemberMap memberMap) - { - memberMap.Data.TypeConverter = TypeConverter; - } + TypeConverter = ObjectResolver.Current.Resolve(typeConverterType, constructorArgs) as ITypeConverter ?? throw new ArgumentException($"Type '{typeConverterType.FullName}' does not implement {nameof(ITypeConverter)}"); + } - /// - public void ApplyTo(ParameterMap parameterMap) - { - parameterMap.Data.TypeConverter = TypeConverter; - } + /// + public void ApplyTo(MemberMap memberMap) + { + memberMap.Data.TypeConverter = TypeConverter; + } + + /// + public void ApplyTo(ParameterMap parameterMap) + { + parameterMap.Data.TypeConverter = TypeConverter; } } diff --git a/src/CsvHelper/Configuration/Attributes/UseNewObjectForNullReferenceMembersAttribute.cs b/src/CsvHelper/Configuration/Attributes/UseNewObjectForNullReferenceMembersAttribute.cs index 2aa806dbc..711054d38 100644 --- a/src/CsvHelper/Configuration/Attributes/UseNewObjectForNullReferenceMembersAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/UseNewObjectForNullReferenceMembersAttribute.cs @@ -2,9 +2,17 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration.Attributes; -namespace CsvHelper.Configuration.Attributes +/// +/// Gets a value indicating that during writing whether a new +/// object should be created when a reference member is . +/// to create a new object and use its defaults for the +/// fields, or to leave the fields empty for all the +/// reference member's members. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class UseNewObjectForNullReferenceMembersAttribute : Attribute, IClassMapper { /// /// Gets a value indicating that during writing whether a new @@ -13,34 +21,23 @@ namespace CsvHelper.Configuration.Attributes /// fields, or to leave the fields empty for all the /// reference member's members. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class UseNewObjectForNullReferenceMembersAttribute : Attribute, IClassMapper - { - /// - /// Gets a value indicating that during writing whether a new - /// object should be created when a reference member is . - /// to create a new object and use its defaults for the - /// fields, or to leave the fields empty for all the - /// reference member's members. - /// - public bool UseNewObjectForNullReferenceMembers { get; private set; } + public bool UseNewObjectForNullReferenceMembers { get; private set; } - /// - /// Gets a value indicating that during writing whether a new - /// object should be created when a reference member is . - /// to create a new object and use its defaults for the - /// fields, or to leave the fields empty for all the - /// reference member's members. - /// - public UseNewObjectForNullReferenceMembersAttribute(bool useNewObjectForNullReferenceMembers = true) - { - UseNewObjectForNullReferenceMembers = useNewObjectForNullReferenceMembers; - } + /// + /// Gets a value indicating that during writing whether a new + /// object should be created when a reference member is . + /// to create a new object and use its defaults for the + /// fields, or to leave the fields empty for all the + /// reference member's members. + /// + public UseNewObjectForNullReferenceMembersAttribute(bool useNewObjectForNullReferenceMembers = true) + { + UseNewObjectForNullReferenceMembers = useNewObjectForNullReferenceMembers; + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.UseNewObjectForNullReferenceMembers = UseNewObjectForNullReferenceMembers; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.UseNewObjectForNullReferenceMembers = UseNewObjectForNullReferenceMembers; } } diff --git a/src/CsvHelper/Configuration/Attributes/WhiteSpaceCharsAttribute.cs b/src/CsvHelper/Configuration/Attributes/WhiteSpaceCharsAttribute.cs index a360fa7f2..a36f43523 100644 --- a/src/CsvHelper/Configuration/Attributes/WhiteSpaceCharsAttribute.cs +++ b/src/CsvHelper/Configuration/Attributes/WhiteSpaceCharsAttribute.cs @@ -2,40 +2,37 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Linq; using System.Text.RegularExpressions; -namespace CsvHelper.Configuration.Attributes +namespace CsvHelper.Configuration.Attributes; + +/// +/// Characters considered whitespace. +/// Used when trimming fields. +/// Default is [' ']. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class WhiteSpaceCharsAttribute : Attribute, IClassMapper { /// /// Characters considered whitespace. /// Used when trimming fields. - /// Default is [' ']. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class WhiteSpaceCharsAttribute : Attribute, IClassMapper - { - /// - /// Characters considered whitespace. - /// Used when trimming fields. - /// - public char[] WhiteSpaceChars { get; private set; } + public char[] WhiteSpaceChars { get; private set; } - /// - /// Characters considered whitespace. - /// Used when trimming fields. - /// - /// - public WhiteSpaceCharsAttribute(string whiteSpaceChars) - { - WhiteSpaceChars = Regex.Split(whiteSpaceChars, @"\s").Select(s => s[0]).ToArray(); - } + /// + /// Characters considered whitespace. + /// Used when trimming fields. + /// + /// + public WhiteSpaceCharsAttribute(string whiteSpaceChars) + { + WhiteSpaceChars = Regex.Split(whiteSpaceChars, @"\s").Select(s => s[0]).ToArray(); + } - /// - public void ApplyTo(CsvConfiguration configuration) - { - configuration.WhiteSpaceChars = WhiteSpaceChars; - } + /// + public void ApplyTo(CsvConfiguration configuration) + { + configuration.WhiteSpaceChars = WhiteSpaceChars; } } diff --git a/src/CsvHelper/Configuration/ClassMap.cs b/src/CsvHelper/Configuration/ClassMap.cs index 7e219b6ae..f1d1c3f09 100644 --- a/src/CsvHelper/Configuration/ClassMap.cs +++ b/src/CsvHelper/Configuration/ClassMap.cs @@ -4,645 +4,641 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration.Attributes; using CsvHelper.TypeConversion; -using System; using System.Collections; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Maps class members to CSV fields. +/// +public abstract class ClassMap { - /// - /// Maps class members to CSV fields. - /// - public abstract class ClassMap + private static readonly List enumerableConverters = new List { - private static readonly List enumerableConverters = new List - { - typeof(ArrayConverter), - typeof(CollectionGenericConverter), - typeof(EnumerableConverter), - typeof(IDictionaryConverter), - typeof(IDictionaryGenericConverter), - typeof(IEnumerableConverter), - typeof(IEnumerableGenericConverter) - }; - - /// - /// The type of the class this map is for. - /// - public virtual Type ClassType { get; private set; } - - /// - /// The class constructor parameter mappings. - /// - public virtual List ParameterMaps { get; } = new List(); - - /// - /// The class member mappings. - /// - public virtual MemberMapCollection MemberMaps { get; } = new MemberMapCollection(); - - /// - /// The class member reference mappings. - /// - public virtual MemberReferenceMapCollection ReferenceMaps { get; } = new MemberReferenceMapCollection(); - - /// - /// Allow only internal creation of CsvClassMap. - /// - /// The type of the class this map is for. - internal ClassMap(Type classType) - { - ClassType = classType; - } + typeof(ArrayConverter), + typeof(CollectionGenericConverter), + typeof(EnumerableConverter), + typeof(IDictionaryConverter), + typeof(IDictionaryGenericConverter), + typeof(IEnumerableConverter), + typeof(IEnumerableGenericConverter) + }; + + /// + /// The type of the class this map is for. + /// + public virtual Type ClassType { get; private set; } + + /// + /// The class constructor parameter mappings. + /// + public virtual List ParameterMaps { get; } = new List(); + + /// + /// The class member mappings. + /// + public virtual MemberMapCollection MemberMaps { get; } = new MemberMapCollection(); + + /// + /// The class member reference mappings. + /// + public virtual MemberReferenceMapCollection ReferenceMaps { get; } = new MemberReferenceMapCollection(); + + /// + /// Allow only internal creation of CsvClassMap. + /// + /// The type of the class this map is for. + internal ClassMap(Type classType) + { + ClassType = classType; + } - /// - /// Maps a member to a CSV field. - /// - /// The type of the class this map is for. This may not be the same type - /// as the member.DeclaringType or the current ClassType due to nested member mappings. - /// The member to map. - /// If true, an existing map will be used if available. - /// If false, a new map is created for the same member. - /// The member mapping. - public MemberMap Map(Type classType, MemberInfo member, bool useExistingMap = true) + /// + /// Maps a member to a CSV field. + /// + /// The type of the class this map is for. This may not be the same type + /// as the member.DeclaringType or the current ClassType due to nested member mappings. + /// The member to map. + /// If true, an existing map will be used if available. + /// If false, a new map is created for the same member. + /// The member mapping. + public MemberMap Map(Type classType, MemberInfo member, bool useExistingMap = true) + { + if (useExistingMap) { - if (useExistingMap) + var existingMap = MemberMaps.Find(member); + if (existingMap != null) { - var existingMap = MemberMaps.Find(member); - if (existingMap != null) - { - return existingMap; - } + return existingMap; } + } - var memberMap = MemberMap.CreateGeneric(classType, member); - memberMap.Data.Index = GetMaxIndex() + 1; - MemberMaps.Add(memberMap); + var memberMap = MemberMap.CreateGeneric(classType, member); + memberMap.Data.Index = GetMaxIndex() + 1; + MemberMaps.Add(memberMap); - return memberMap; - } + return memberMap; + } - /// - /// Maps a non-member to a CSV field. This allows for writing - /// data that isn't mapped to a class member. - /// - /// The member mapping. - public virtual MemberMap Map() - { - var memberMap = new MemberMap(null); - memberMap.Data.Index = GetMaxIndex() + 1; - MemberMaps.Add(memberMap); + /// + /// Maps a non-member to a CSV field. This allows for writing + /// data that isn't mapped to a class member. + /// + /// The member mapping. + public virtual MemberMap Map() + { + var memberMap = new MemberMap(null); + memberMap.Data.Index = GetMaxIndex() + 1; + MemberMaps.Add(memberMap); - return memberMap; + return memberMap; + } + + /// + /// Maps a member to another class map. + /// + /// The type of the class map. + /// The member. + /// Constructor arguments used to create the reference map. + /// The reference mapping for the member. + public virtual MemberReferenceMap References(Type classMapType, MemberInfo member, params object[] constructorArgs) + { + if (!typeof(ClassMap).IsAssignableFrom(classMapType)) + { + throw new InvalidOperationException($"Argument {nameof(classMapType)} is not a CsvClassMap."); } - /// - /// Maps a member to another class map. - /// - /// The type of the class map. - /// The member. - /// Constructor arguments used to create the reference map. - /// The reference mapping for the member. - public virtual MemberReferenceMap References(Type classMapType, MemberInfo member, params object[] constructorArgs) + var existingMap = ReferenceMaps.Find(member); + + if (existingMap != null) { - if (!typeof(ClassMap).IsAssignableFrom(classMapType)) - { - throw new InvalidOperationException($"Argument {nameof(classMapType)} is not a CsvClassMap."); - } + return existingMap; + } - var existingMap = ReferenceMaps.Find(member); + var map = (ClassMap)ObjectResolver.Current.Resolve(classMapType, constructorArgs); + map.ReIndex(GetMaxIndex() + 1); + var reference = new MemberReferenceMap(member, map); + ReferenceMaps.Add(reference); - if (existingMap != null) - { - return existingMap; - } + return reference; + } - var map = (ClassMap)ObjectResolver.Current.Resolve(classMapType, constructorArgs); - map.ReIndex(GetMaxIndex() + 1); - var reference = new MemberReferenceMap(member, map); - ReferenceMaps.Add(reference); + /// + /// Maps a constructor parameter to a CSV field. + /// + /// The name of the constructor parameter. + public virtual ParameterMap Parameter(string name) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); - return reference; - } + var args = new GetConstructorArgs(ClassType); - /// - /// Maps a constructor parameter to a CSV field. - /// - /// The name of the constructor parameter. - public virtual ParameterMap Parameter(string name) - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + return Parameter(() => ConfigurationFunctions.GetConstructor(args), name); + } - var args = new GetConstructorArgs(ClassType); + /// + /// Maps a constructor parameter to a CSV field. + /// + /// A function that returns the for the constructor. + /// The name of the constructor parameter. + public virtual ParameterMap Parameter(Func getConstructor, string name) + { + if (getConstructor == null) throw new ArgumentNullException(nameof(getConstructor)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); - return Parameter(() => ConfigurationFunctions.GetConstructor(args), name); + var constructor = getConstructor(); + var parameters = constructor.GetParameters(); + var parameter = parameters.SingleOrDefault(p => p.Name == name); + if (parameter == null) + { + throw new ConfigurationException($"Constructor {constructor.GetDefinition()} doesn't contain a paramter with name '{name}'."); } - /// - /// Maps a constructor parameter to a CSV field. - /// - /// A function that returns the for the constructor. - /// The name of the constructor parameter. - public virtual ParameterMap Parameter(Func getConstructor, string name) - { - if (getConstructor == null) throw new ArgumentNullException(nameof(getConstructor)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + return Parameter(constructor, parameter); + } - var constructor = getConstructor(); - var parameters = constructor.GetParameters(); - var parameter = parameters.SingleOrDefault(p => p.Name == name); - if (parameter == null) - { - throw new ConfigurationException($"Constructor {constructor.GetDefinition()} doesn't contain a paramter with name '{name}'."); - } + /// + /// Maps a constructor parameter to a CSV field. + /// + /// The for the constructor. + /// The for the constructor parameter. + public virtual ParameterMap Parameter(ConstructorInfo constructor, ParameterInfo parameter) + { + if (constructor == null) throw new ArgumentNullException(nameof(constructor)); + if (parameter == null) throw new ArgumentNullException(nameof(parameter)); - return Parameter(constructor, parameter); + if (!constructor.GetParameters().Contains(parameter)) + { + throw new ConfigurationException($"Constructor {constructor.GetDefinition()} doesn't contain parameter '{parameter.GetDefinition()}'."); } - /// - /// Maps a constructor parameter to a CSV field. - /// - /// The for the constructor. - /// The for the constructor parameter. - public virtual ParameterMap Parameter(ConstructorInfo constructor, ParameterInfo parameter) + var parameterMap = new ParameterMap(parameter); + parameterMap.Data.Index = GetMaxIndex(isParameter: true) + 1; + ParameterMaps.Add(parameterMap); + + return parameterMap; + } + + /// + /// Auto maps all members for the given type. If a member + /// is mapped again it will override the existing map. + /// + /// The culture. + public virtual void AutoMap(CultureInfo culture) + { + AutoMap(new CsvConfiguration(culture)); + } + + /// + /// Auto maps all members for the given type. If a member + /// is mapped again it will override the existing map. + /// + /// The configuration. + public virtual void AutoMap(CsvConfiguration configuration) + { + AutoMap(new CsvContext(configuration)); + } + + /// + /// Auto maps all members for the given type. If a member + /// is mapped again it will override the existing map. + /// + /// The context. + public virtual void AutoMap(CsvContext context) + { + var type = GetGenericType(); + if (typeof(IEnumerable).IsAssignableFrom(type)) { - if (constructor == null) throw new ArgumentNullException(nameof(constructor)); - if (parameter == null) throw new ArgumentNullException(nameof(parameter)); + throw new ConfigurationException("Types that inherit IEnumerable cannot be auto mapped. " + + "Did you accidentally call GetRecord or WriteRecord which " + + "acts on a single record instead of calling GetRecords or " + + "WriteRecords which acts on a list of records?"); + } - if (!constructor.GetParameters().Contains(parameter)) - { - throw new ConfigurationException($"Constructor {constructor.GetDefinition()} doesn't contain parameter '{parameter.GetDefinition()}'."); - } + var mapParents = new LinkedList(); + var args = new ShouldUseConstructorParametersArgs(type); + if (context.Configuration.ShouldUseConstructorParameters(args)) + { + // This type doesn't have a parameterless constructor so we can't create an + // instance and set it's member. Constructor parameters need to be created + // instead. Writing only uses getters, so members will also be mapped + // for writing purposes. + AutoMapConstructorParameters(this, context, mapParents); + } - var parameterMap = new ParameterMap(parameter); - parameterMap.Data.Index = GetMaxIndex(isParameter: true) + 1; - ParameterMaps.Add(parameterMap); + AutoMapMembers(this, context, mapParents); + } - return parameterMap; + /// + /// Get the largest index for the + /// members and references. + /// + /// The max index. + public virtual int GetMaxIndex(bool isParameter = false) + { + if (isParameter) + { + return ParameterMaps.Select(parameterMap => parameterMap.GetMaxIndex()).DefaultIfEmpty(-1).Max(); } - /// - /// Auto maps all members for the given type. If a member - /// is mapped again it will override the existing map. - /// - /// The culture. - public virtual void AutoMap(CultureInfo culture) + if (MemberMaps.Count == 0 && ReferenceMaps.Count == 0) { - AutoMap(new CsvConfiguration(culture)); + return -1; } - /// - /// Auto maps all members for the given type. If a member - /// is mapped again it will override the existing map. - /// - /// The configuration. - public virtual void AutoMap(CsvConfiguration configuration) + var indexes = new List(); + if (MemberMaps.Count > 0) { - AutoMap(new CsvContext(configuration)); + indexes.Add(MemberMaps.Max(pm => pm.Data.Index)); } - /// - /// Auto maps all members for the given type. If a member - /// is mapped again it will override the existing map. - /// - /// The context. - public virtual void AutoMap(CsvContext context) + if (ReferenceMaps.Count > 0) { - var type = GetGenericType(); - if (typeof(IEnumerable).IsAssignableFrom(type)) - { - throw new ConfigurationException("Types that inherit IEnumerable cannot be auto mapped. " + - "Did you accidentally call GetRecord or WriteRecord which " + - "acts on a single record instead of calling GetRecords or " + - "WriteRecords which acts on a list of records?"); - } + indexes.AddRange(ReferenceMaps.Select(referenceMap => referenceMap.GetMaxIndex())); + } - var mapParents = new LinkedList(); - var args = new ShouldUseConstructorParametersArgs(type); - if (context.Configuration.ShouldUseConstructorParameters(args)) - { - // This type doesn't have a parameterless constructor so we can't create an - // instance and set it's member. Constructor parameters need to be created - // instead. Writing only uses getters, so members will also be mapped - // for writing purposes. - AutoMapConstructorParameters(this, context, mapParents); - } + return indexes.Max(); + } - AutoMapMembers(this, context, mapParents); + /// + /// Resets the indexes based on the given start index. + /// + /// The index start. + /// The last index + 1. + public virtual int ReIndex(int indexStart = 0) + { + foreach (var parameterMap in ParameterMaps) + { + parameterMap.Data.Index = indexStart + parameterMap.Data.Index; } - /// - /// Get the largest index for the - /// members and references. - /// - /// The max index. - public virtual int GetMaxIndex(bool isParameter = false) + foreach (var memberMap in MemberMaps) { - if (isParameter) + if (!memberMap.Data.IsIndexSet) { - return ParameterMaps.Select(parameterMap => parameterMap.GetMaxIndex()).DefaultIfEmpty(-1).Max(); + memberMap.Data.Index = indexStart + memberMap.Data.Index; } + } - if (MemberMaps.Count == 0 && ReferenceMaps.Count == 0) - { - return -1; - } + foreach (var referenceMap in ReferenceMaps) + { + indexStart = referenceMap.Data.Mapping.ReIndex(indexStart); + } - var indexes = new List(); - if (MemberMaps.Count > 0) - { - indexes.Add(MemberMaps.Max(pm => pm.Data.Index)); - } + return indexStart; + } - if (ReferenceMaps.Count > 0) - { - indexes.AddRange(ReferenceMaps.Select(referenceMap => referenceMap.GetMaxIndex())); - } + /// + /// Auto maps the given map and checks for circular references as it goes. + /// + /// The map to auto map. + /// The context. + /// The list of parents for the map. + /// The index starting point. + protected virtual void AutoMapMembers(ClassMap map, CsvContext context, LinkedList mapParents, int indexStart = 0) + { + var type = map.GetGenericType(); - return indexes.Max(); + var flags = BindingFlags.Instance | BindingFlags.Public; + if (context.Configuration.IncludePrivateMembers) + { + flags = flags | BindingFlags.NonPublic; } - /// - /// Resets the indexes based on the given start index. - /// - /// The index start. - /// The last index + 1. - public virtual int ReIndex(int indexStart = 0) + var members = new List(); + if ((context.Configuration.MemberTypes & MemberTypes.Properties) == MemberTypes.Properties) { - foreach (var parameterMap in ParameterMaps) - { - parameterMap.Data.Index = indexStart + parameterMap.Data.Index; - } - - foreach (var memberMap in MemberMaps) + // We need to go up the declaration tree and find the actual type the property + // exists on and use that PropertyInfo instead. This is so we can get the private + // set method for the property. + var properties = new List(); + foreach (var property in ReflectionHelper.GetUniqueProperties(type, flags)) { - if (!memberMap.Data.IsIndexSet) + if (properties.Any(p => p.Name == property.Name)) { - memberMap.Data.Index = indexStart + memberMap.Data.Index; + // Multiple properties could have the same name if a child class property + // is hiding a parent class property by using `new`. It's possible that + // the order of the properties returned + continue; } - } - foreach (var referenceMap in ReferenceMaps) - { - indexStart = referenceMap.Data.Mapping.ReIndex(indexStart); + properties.Add(ReflectionHelper.GetDeclaringProperty(type, property, flags)); } - return indexStart; + members.AddRange(properties); } - /// - /// Auto maps the given map and checks for circular references as it goes. - /// - /// The map to auto map. - /// The context. - /// The list of parents for the map. - /// The index starting point. - protected virtual void AutoMapMembers(ClassMap map, CsvContext context, LinkedList mapParents, int indexStart = 0) + if ((context.Configuration.MemberTypes & MemberTypes.Fields) == MemberTypes.Fields) { - var type = map.GetGenericType(); - - var flags = BindingFlags.Instance | BindingFlags.Public; - if (context.Configuration.IncludePrivateMembers) + // We need to go up the declaration tree and find the actual type the field + // exists on and use that FieldInfo instead. + var fields = new List(); + foreach (var field in ReflectionHelper.GetUniqueFields(type, flags)) { - flags = flags | BindingFlags.NonPublic; - } - - var members = new List(); - if ((context.Configuration.MemberTypes & MemberTypes.Properties) == MemberTypes.Properties) - { - // We need to go up the declaration tree and find the actual type the property - // exists on and use that PropertyInfo instead. This is so we can get the private - // set method for the property. - var properties = new List(); - foreach (var property in ReflectionHelper.GetUniqueProperties(type, flags)) + if (fields.Any(p => p.Name == field.Name)) { - if (properties.Any(p => p.Name == property.Name)) - { - // Multiple properties could have the same name if a child class property - // is hiding a parent class property by using `new`. It's possible that - // the order of the properties returned - continue; - } - - properties.Add(ReflectionHelper.GetDeclaringProperty(type, property, flags)); + // Multiple fields could have the same name if a child class field + // is hiding a parent class field by using `new`. It's possible that + // the order of the fields returned + continue; } - members.AddRange(properties); + if (!field.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any()) + { + fields.Add(ReflectionHelper.GetDeclaringField(type, field, flags)); + } } - if ((context.Configuration.MemberTypes & MemberTypes.Fields) == MemberTypes.Fields) + members.AddRange(fields); + } + + foreach (var member in members) + { + if (member.GetCustomAttribute() != null) { - // We need to go up the declaration tree and find the actual type the field - // exists on and use that FieldInfo instead. - var fields = new List(); - foreach (var field in ReflectionHelper.GetUniqueFields(type, flags)) - { - if (fields.Any(p => p.Name == field.Name)) - { - // Multiple fields could have the same name if a child class field - // is hiding a parent class field by using `new`. It's possible that - // the order of the fields returned - continue; - } + // Ignore this member including its tree if it's a reference. + continue; + } - if (!field.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any()) - { - fields.Add(ReflectionHelper.GetDeclaringField(type, field, flags)); - } - } + var typeConverterType = context.TypeConverterCache.GetConverter(member).GetType(); - members.AddRange(fields); + if (context.Configuration.HasHeaderRecord && enumerableConverters.Contains(typeConverterType)) + { + // Enumerable converters can't write the header properly, so skip it. + continue; } - foreach (var member in members) + var memberTypeInfo = member.MemberType().GetTypeInfo(); + var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); + if (isDefaultConverter) { - if (member.GetCustomAttribute() != null) + // If the type is not one covered by our type converters + // and it has a parameterless constructor, create a + // reference map for it. + + if (context.Configuration.IgnoreReferences) { - // Ignore this member including its tree if it's a reference. continue; } - var typeConverterType = context.TypeConverterCache.GetConverter(member).GetType(); - - if (context.Configuration.HasHeaderRecord && enumerableConverters.Contains(typeConverterType)) + if (CheckForCircularReference(member.MemberType(), mapParents)) { - // Enumerable converters can't write the header properly, so skip it. continue; } - var memberTypeInfo = member.MemberType().GetTypeInfo(); - var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); - if (isDefaultConverter) - { - // If the type is not one covered by our type converters - // and it has a parameterless constructor, create a - // reference map for it. - - if (context.Configuration.IgnoreReferences) - { - continue; - } + mapParents.AddLast(type); + var refMapType = typeof(DefaultClassMap<>).MakeGenericType(member.MemberType()); + var refMap = (ClassMap)ObjectResolver.Current.Resolve(refMapType); - if (CheckForCircularReference(member.MemberType(), mapParents)) - { - continue; - } + if (memberTypeInfo.HasConstructor() && !memberTypeInfo.HasParameterlessConstructor() && !memberTypeInfo.IsUserDefinedStruct()) + { + AutoMapConstructorParameters(refMap, context, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); + } - mapParents.AddLast(type); - var refMapType = typeof(DefaultClassMap<>).MakeGenericType(member.MemberType()); - var refMap = (ClassMap)ObjectResolver.Current.Resolve(refMapType); + // Need to use Max here for nested types. + AutoMapMembers(refMap, context, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); + mapParents.Drop(mapParents.Find(type)); - if (memberTypeInfo.HasConstructor() && !memberTypeInfo.HasParameterlessConstructor() && !memberTypeInfo.IsUserDefinedStruct()) + if (refMap.MemberMaps.Count > 0 || refMap.ReferenceMaps.Count > 0) + { + var referenceMap = new MemberReferenceMap(member, refMap); + if (context.Configuration.ReferenceHeaderPrefix != null) { - AutoMapConstructorParameters(refMap, context, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); + var args = new ReferenceHeaderPrefixArgs(member.MemberType(), member.Name); + referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(args); } - // Need to use Max here for nested types. - AutoMapMembers(refMap, context, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); - mapParents.Drop(mapParents.Find(type)); - - if (refMap.MemberMaps.Count > 0 || refMap.ReferenceMaps.Count > 0) - { - var referenceMap = new MemberReferenceMap(member, refMap); - if (context.Configuration.ReferenceHeaderPrefix != null) - { - var args = new ReferenceHeaderPrefixArgs(member.MemberType(), member.Name); - referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(args); - } - - ApplyAttributes(referenceMap); + ApplyAttributes(referenceMap); - map.ReferenceMaps.Add(referenceMap); - } + map.ReferenceMaps.Add(referenceMap); } - else - { - // Only add the member map if it can be converted later on. - // If the member will use the default converter, don't add it because - // we don't want the .ToString() value to be used when auto mapping. + } + else + { + // Only add the member map if it can be converted later on. + // If the member will use the default converter, don't add it because + // we don't want the .ToString() value to be used when auto mapping. - // Use the top of the map tree. This will maps that have been auto mapped - // to later on get a reference to a map by doing map.Map( m => m.A.B.C.Id ) - // and it will return the correct parent map type of A instead of C. - var classType = mapParents.First?.Value ?? map.ClassType; - var memberMap = MemberMap.CreateGeneric(classType, member); + // Use the top of the map tree. This will maps that have been auto mapped + // to later on get a reference to a map by doing map.Map( m => m.A.B.C.Id ) + // and it will return the correct parent map type of A instead of C. + var classType = mapParents.First?.Value ?? map.ClassType; + var memberMap = MemberMap.CreateGeneric(classType, member); - // Use global values as the starting point. - memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), context.TypeConverterOptionsCache.GetOptions(member.MemberType()), memberMap.Data.TypeConverterOptions); - memberMap.Data.Index = map.GetMaxIndex() + 1; + // Use global values as the starting point. + memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), context.TypeConverterOptionsCache.GetOptions(member.MemberType()), memberMap.Data.TypeConverterOptions); + memberMap.Data.Index = map.GetMaxIndex() + 1; - ApplyAttributes(memberMap); + ApplyAttributes(memberMap); - map.MemberMaps.Add(memberMap); - } + map.MemberMaps.Add(memberMap); } - - map.ReIndex(indexStart); } - /// - /// Auto maps the given map using constructor parameters. - /// - /// The map. - /// The context. - /// The list of parents for the map. - /// The index starting point. - protected virtual void AutoMapConstructorParameters(ClassMap map, CsvContext context, LinkedList mapParents, int indexStart = 0) + map.ReIndex(indexStart); + } + + /// + /// Auto maps the given map using constructor parameters. + /// + /// The map. + /// The context. + /// The list of parents for the map. + /// The index starting point. + protected virtual void AutoMapConstructorParameters(ClassMap map, CsvContext context, LinkedList mapParents, int indexStart = 0) + { + var type = map.GetGenericType(); + var args = new GetConstructorArgs(map.ClassType); + var constructor = context.Configuration.GetConstructor(args); + var parameters = constructor.GetParameters(); + + foreach (var parameter in parameters) { - var type = map.GetGenericType(); - var args = new GetConstructorArgs(map.ClassType); - var constructor = context.Configuration.GetConstructor(args); - var parameters = constructor.GetParameters(); + var parameterMap = new ParameterMap(parameter); - foreach (var parameter in parameters) + if (parameter.GetCustomAttributes(true).Any() || parameter.GetCustomAttributes(true).Any()) { - var parameterMap = new ParameterMap(parameter); + // If there is an IgnoreAttribute or ConstantAttribute, we still need to add a map because a constructor requires + // all parameters to be present. A default value will be used later on. - if (parameter.GetCustomAttributes(true).Any() || parameter.GetCustomAttributes(true).Any()) - { - // If there is an IgnoreAttribute or ConstantAttribute, we still need to add a map because a constructor requires - // all parameters to be present. A default value will be used later on. + ApplyAttributes(parameterMap); + map.ParameterMaps.Add(parameterMap); + continue; + } - ApplyAttributes(parameterMap); - map.ParameterMaps.Add(parameterMap); - continue; - } + var typeConverterType = context.TypeConverterCache.GetConverter(parameter.ParameterType).GetType(); + var memberTypeInfo = parameter.ParameterType.GetTypeInfo(); + var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); + if (isDefaultConverter && (memberTypeInfo.HasParameterlessConstructor() || memberTypeInfo.IsUserDefinedStruct())) + { + // If the type is not one covered by our type converters + // and it has a parameterless constructor, create a + // reference map for it. - var typeConverterType = context.TypeConverterCache.GetConverter(parameter.ParameterType).GetType(); - var memberTypeInfo = parameter.ParameterType.GetTypeInfo(); - var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); - if (isDefaultConverter && (memberTypeInfo.HasParameterlessConstructor() || memberTypeInfo.IsUserDefinedStruct())) + if (context.Configuration.IgnoreReferences) { - // If the type is not one covered by our type converters - // and it has a parameterless constructor, create a - // reference map for it. - - if (context.Configuration.IgnoreReferences) - { - throw new InvalidOperationException($"Configuration '{nameof(CsvConfiguration.IgnoreReferences)}' can't be true " + - "when using types without a default constructor. Constructor parameters " + - "are used and all members including references must be used."); - } - - if (CheckForCircularReference(parameter.ParameterType, mapParents)) - { - throw new InvalidOperationException($"A circular reference was detected in constructor paramter '{parameter.Name}'." + - "Since all parameters must be supplied for a constructor, this parameter can't be skipped."); - } - - mapParents.AddLast(type); - var refMapType = typeof(DefaultClassMap<>).MakeGenericType(parameter.ParameterType); - var refMap = (ClassMap)ObjectResolver.Current.Resolve(refMapType); - AutoMapMembers(refMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); - mapParents.Drop(mapParents.Find(type)); + throw new InvalidOperationException($"Configuration '{nameof(CsvConfiguration.IgnoreReferences)}' can't be true " + + "when using types without a default constructor. Constructor parameters " + + "are used and all members including references must be used."); + } - var referenceMap = new ParameterReferenceMap(parameter, refMap); - if (context.Configuration.ReferenceHeaderPrefix != null) - { - var referenceHeaderPrefix = new ReferenceHeaderPrefixArgs(memberTypeInfo.MemberType(), memberTypeInfo.Name); - referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(referenceHeaderPrefix); - } + if (CheckForCircularReference(parameter.ParameterType, mapParents)) + { + throw new InvalidOperationException($"A circular reference was detected in constructor paramter '{parameter.Name}'." + + "Since all parameters must be supplied for a constructor, this parameter can't be skipped."); + } - ApplyAttributes(referenceMap); + mapParents.AddLast(type); + var refMapType = typeof(DefaultClassMap<>).MakeGenericType(parameter.ParameterType); + var refMap = (ClassMap)ObjectResolver.Current.Resolve(refMapType); + AutoMapMembers(refMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); + mapParents.Drop(mapParents.Find(type)); - parameterMap.ReferenceMap = referenceMap; - } - else if (isDefaultConverter && context.Configuration.ShouldUseConstructorParameters(new ShouldUseConstructorParametersArgs(parameter.ParameterType))) + var referenceMap = new ParameterReferenceMap(parameter, refMap); + if (context.Configuration.ReferenceHeaderPrefix != null) { - // If the type is not one covered by our type converters - // and it should use contructor parameters, create a - // constructor map for it. - - mapParents.AddLast(type); - var constructorMapType = typeof(DefaultClassMap<>).MakeGenericType(parameter.ParameterType); - var constructorMap = (ClassMap)ObjectResolver.Current.Resolve(constructorMapType); - // Need to use Max here for nested types. - AutoMapConstructorParameters(constructorMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); - mapParents.Drop(mapParents.Find(type)); - - parameterMap.ConstructorTypeMap = constructorMap; + var referenceHeaderPrefix = new ReferenceHeaderPrefixArgs(memberTypeInfo.MemberType(), memberTypeInfo.Name); + referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(referenceHeaderPrefix); } - else - { - parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), context.TypeConverterOptionsCache.GetOptions(parameter.ParameterType), parameterMap.Data.TypeConverterOptions); - parameterMap.Data.Index = map.GetMaxIndex(isParameter: true) + 1; - ApplyAttributes(parameterMap); - } + ApplyAttributes(referenceMap); - map.ParameterMaps.Add(parameterMap); + parameterMap.ReferenceMap = referenceMap; } - - map.ReIndex(indexStart); - } - - /// - /// Checks for circular references. - /// - /// The type to check for. - /// The list of parents to check against. - /// A value indicating if a circular reference was found. - /// True if a circular reference was found, otherwise false. - protected virtual bool CheckForCircularReference(Type type, LinkedList mapParents) - { - if (mapParents.Count == 0) + else if (isDefaultConverter && context.Configuration.ShouldUseConstructorParameters(new ShouldUseConstructorParametersArgs(parameter.ParameterType))) { - return false; + // If the type is not one covered by our type converters + // and it should use contructor parameters, create a + // constructor map for it. + + mapParents.AddLast(type); + var constructorMapType = typeof(DefaultClassMap<>).MakeGenericType(parameter.ParameterType); + var constructorMap = (ClassMap)ObjectResolver.Current.Resolve(constructorMapType); + // Need to use Max here for nested types. + AutoMapConstructorParameters(constructorMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); + mapParents.Drop(mapParents.Find(type)); + + parameterMap.ConstructorTypeMap = constructorMap; } - - var node = mapParents.Last; - while (true) + else { - if (node?.Value == type) - { - return true; - } + parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), context.TypeConverterOptionsCache.GetOptions(parameter.ParameterType), parameterMap.Data.TypeConverterOptions); + parameterMap.Data.Index = map.GetMaxIndex(isParameter: true) + 1; - node = node?.Previous; - if (node == null) - { - break; - } + ApplyAttributes(parameterMap); } - return false; + map.ParameterMaps.Add(parameterMap); } - /// - /// Gets the generic type for this class map. - /// - protected virtual Type GetGenericType() + map.ReIndex(indexStart); + } + + /// + /// Checks for circular references. + /// + /// The type to check for. + /// The list of parents to check against. + /// A value indicating if a circular reference was found. + /// True if a circular reference was found, otherwise false. + protected virtual bool CheckForCircularReference(Type type, LinkedList mapParents) + { + if (mapParents.Count == 0) { - return GetType().GetTypeInfo().BaseType?.GetGenericArguments()[0] ?? throw new ConfigurationException(); + return false; } - /// - /// Applies attribute configurations to the map. - /// - /// The parameter map. - protected virtual void ApplyAttributes(ParameterMap parameterMap) + var node = mapParents.Last; + while (true) { - var parameter = parameterMap.Data.Parameter; - var attributes = parameter.GetCustomAttributes().OfType(); + if (node?.Value == type) + { + return true; + } - foreach (var attribute in attributes) + node = node?.Previous; + if (node == null) { - attribute.ApplyTo(parameterMap); + break; } } - /// - /// Applies attribute configurations to the map. - /// - /// The parameter reference map. - protected virtual void ApplyAttributes(ParameterReferenceMap referenceMap) + return false; + } + + /// + /// Gets the generic type for this class map. + /// + protected virtual Type GetGenericType() + { + return GetType().GetTypeInfo().BaseType?.GetGenericArguments()[0] ?? throw new ConfigurationException(); + } + + /// + /// Applies attribute configurations to the map. + /// + /// The parameter map. + protected virtual void ApplyAttributes(ParameterMap parameterMap) + { + var parameter = parameterMap.Data.Parameter; + var attributes = parameter.GetCustomAttributes().OfType(); + + foreach (var attribute in attributes) { - var parameter = referenceMap.Data.Parameter; - var attributes = parameter.GetCustomAttributes().OfType(); + attribute.ApplyTo(parameterMap); + } + } - foreach (var attribute in attributes) - { - attribute.ApplyTo(referenceMap); - } + /// + /// Applies attribute configurations to the map. + /// + /// The parameter reference map. + protected virtual void ApplyAttributes(ParameterReferenceMap referenceMap) + { + var parameter = referenceMap.Data.Parameter; + var attributes = parameter.GetCustomAttributes().OfType(); + + foreach (var attribute in attributes) + { + attribute.ApplyTo(referenceMap); } + } - /// - /// Applies attribute configurations to the map. - /// - /// The member map. - protected virtual void ApplyAttributes(MemberMap memberMap) + /// + /// Applies attribute configurations to the map. + /// + /// The member map. + protected virtual void ApplyAttributes(MemberMap memberMap) + { + if (memberMap.Data.Member == null) { - if (memberMap.Data.Member == null) - { - return; - } + return; + } - var member = memberMap.Data.Member; - var attributes = member.GetCustomAttributes().OfType(); + var member = memberMap.Data.Member; + var attributes = member.GetCustomAttributes().OfType(); - foreach (var attribute in attributes) - { - attribute.ApplyTo(memberMap); - } + foreach (var attribute in attributes) + { + attribute.ApplyTo(memberMap); } + } - /// - /// Applies attribute configurations to the map. - /// - /// The member reference map. - protected virtual void ApplyAttributes(MemberReferenceMap referenceMap) - { - var member = referenceMap.Data.Member; - var attributes = member.GetCustomAttributes().OfType(); + /// + /// Applies attribute configurations to the map. + /// + /// The member reference map. + protected virtual void ApplyAttributes(MemberReferenceMap referenceMap) + { + var member = referenceMap.Data.Member; + var attributes = member.GetCustomAttributes().OfType(); - foreach (var attribute in attributes) - { - attribute.ApplyTo(referenceMap); - } + foreach (var attribute in attributes) + { + attribute.ApplyTo(referenceMap); } } } diff --git a/src/CsvHelper/Configuration/ClassMapBuilder.cs b/src/CsvHelper/Configuration/ClassMapBuilder.cs index 4ec80a09c..7ac7d3807 100644 --- a/src/CsvHelper/Configuration/ClassMapBuilder.cs +++ b/src/CsvHelper/Configuration/ClassMapBuilder.cs @@ -2,431 +2,429 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Linq.Expressions; using CsvHelper.TypeConversion; using System.Collections; +using System.Linq.Expressions; + +namespace CsvHelper.Configuration; -namespace CsvHelper.Configuration +/// +/// Has mapping capabilities. +/// +/// The class type. +public interface IHasMap : IBuildableClass { /// - /// Has mapping capabilities. + /// Maps a member to a CSV field. /// - /// The class type. - public interface IHasMap : IBuildableClass - { - /// - /// Maps a member to a CSV field. - /// - /// The member to map. - /// If true, an existing map will be used if available. - /// If false, a new map is created for the same member. - /// The member mapping. - IHasMapOptions Map(Expression> expression, bool useExistingMap = true); - } + /// The member to map. + /// If true, an existing map will be used if available. + /// If false, a new map is created for the same member. + /// The member mapping. + IHasMapOptions Map(Expression> expression, bool useExistingMap = true); +} +/// +/// Options after a mapping call. +/// +/// The class type. +/// The member type. +public interface IHasMapOptions : + IHasMap, + IHasTypeConverter, + IHasIndex, + IHasName, + IHasOptional, + IHasConvertUsing, + IHasDefault, + IHasConstant, + IHasValidate +{ } + +/// +/// Has type converter capabilities. +/// +/// The class type. +/// The member type. +public interface IHasTypeConverter : IBuildableClass +{ /// - /// Options after a mapping call. + /// Specifies the to use + /// when converting the member to and from a CSV field. /// - /// The class type. - /// The member type. - public interface IHasMapOptions : - IHasMap, - IHasTypeConverter, - IHasIndex, - IHasName, - IHasOptional, - IHasConvertUsing, - IHasDefault, - IHasConstant, - IHasValidate - { } + /// The TypeConverter to use. + IHasTypeConverterOptions TypeConverter(ITypeConverter typeConverter); /// - /// Has type converter capabilities. + /// Specifies the to use + /// when converting the member to and from a CSV field. /// - /// The class type. - /// The member type. - public interface IHasTypeConverter : IBuildableClass - { - /// - /// Specifies the to use - /// when converting the member to and from a CSV field. - /// - /// The TypeConverter to use. - IHasTypeConverterOptions TypeConverter(ITypeConverter typeConverter); - - /// - /// Specifies the to use - /// when converting the member to and from a CSV field. - /// - /// The of the - /// to use. - IHasTypeConverterOptions TypeConverter() where TConverter : ITypeConverter; - } + /// The of the + /// to use. + IHasTypeConverterOptions TypeConverter() where TConverter : ITypeConverter; +} +/// +/// Options after a type converter call. +/// +/// The class type. +/// The member type. +public interface IHasTypeConverterOptions : + IHasMap, + IHasDefault, + IHasValidate +{ } + +/// +/// Has index capabilities. +/// +/// The class type. +/// The member type. +public interface IHasIndex : IBuildableClass +{ /// - /// Options after a type converter call. + /// When reading, is used to get the field at + /// the given index. When writing, the fields + /// will be written in the order of the field + /// indexes. /// - /// The class type. - /// The member type. - public interface IHasTypeConverterOptions : - IHasMap, - IHasDefault, - IHasValidate - { } + /// The index of the CSV field. + /// The end index used when mapping to an member. + IHasIndexOptions Index(int index, int indexEnd = -1); +} +/// +/// Options after an index call. +/// +/// The class type. +/// The member type. +public interface IHasIndexOptions : + IHasMap, + IHasTypeConverter, + IHasName, + IHasDefault, + IHasValidate +{ } + +/// +/// Has optional capabilities. +/// +/// The class type. +/// The member type. +public interface IHasOptional : IBuildableClass +{ /// - /// Has index capabilities. + /// Ignore the member when reading if no matching field name can be found. /// - /// The class type. - /// The member type. - public interface IHasIndex : IBuildableClass - { - /// - /// When reading, is used to get the field at - /// the given index. When writing, the fields - /// will be written in the order of the field - /// indexes. - /// - /// The index of the CSV field. - /// The end index used when mapping to an member. - IHasIndexOptions Index(int index, int indexEnd = -1); - } + IHasOptionalOptions Optional(); +} +/// +/// Options after an optional call. +/// +/// The class type. +/// The member type. +public interface IHasOptionalOptions : + IHasMap, + IHasTypeConverter, + IHasName, + IHasDefault, + IHasValidate +{ } + +/// +/// Has name capabilities. +/// +/// The class type. +/// The member type. +public interface IHasName : IBuildableClass +{ /// - /// Options after an index call. + /// When reading, is used to get the field + /// at the index of the name if there was a + /// header specified. It will look for the + /// first name match in the order listed. + /// When writing, sets the name of the + /// field in the header record. + /// The first name will be used. /// - /// The class type. - /// The member type. - public interface IHasIndexOptions : - IHasMap, - IHasTypeConverter, - IHasName, - IHasDefault, - IHasValidate - { } + /// The possible names of the CSV field. + IHasNameOptions Name(params string[] names); +} +/// +/// Options after a name call. +/// +/// The class type. +/// The member type. +public interface IHasNameOptions : + IHasMap, + IHasTypeConverter, + IHasNameIndex, + IHasDefault, + IHasValidate +{ } + +/// +/// Has name index capabilities. +/// +/// The class type. +/// The member type. +public interface IHasNameIndex : IBuildableClass +{ /// - /// Has optional capabilities. + /// When reading, is used to get the + /// index of the name used when there + /// are multiple names that are the same. /// - /// The class type. - /// The member type. - public interface IHasOptional : IBuildableClass - { - /// - /// Ignore the member when reading if no matching field name can be found. - /// - IHasOptionalOptions Optional(); - } + /// The index of the name. + IHasNameIndexOptions NameIndex(int index); +} +/// +/// Options after a name index call. +/// +/// The class type. +/// The member type. +public interface IHasNameIndexOptions : + IHasMap, + IHasTypeConverter, + IHasDefault, + IHasValidate +{ } + +/// +/// Has convert using capabilities. +/// +/// The class type. +/// The member type. +public interface IHasConvertUsing : IBuildableClass +{ /// - /// Options after an optional call. + /// Specifies an expression to be used to convert data in the + /// row to the member. /// - /// The class type. - /// The member type. - public interface IHasOptionalOptions : - IHasMap, - IHasTypeConverter, - IHasName, - IHasDefault, - IHasValidate - { } + /// The convert expression. + IHasMap ConvertUsing(ConvertFromString convertExpression); /// - /// Has name capabilities. + /// Specifies an expression to be used to convert the object + /// to a field. /// - /// The class type. - /// The member type. - public interface IHasName : IBuildableClass - { - /// - /// When reading, is used to get the field - /// at the index of the name if there was a - /// header specified. It will look for the - /// first name match in the order listed. - /// When writing, sets the name of the - /// field in the header record. - /// The first name will be used. - /// - /// The possible names of the CSV field. - IHasNameOptions Name(params string[] names); - } + /// The convert expression. + IHasMap ConvertUsing(ConvertToString convertExpression); +} +/// +/// Has default capabilities. +/// +/// The class type. +/// The member type. +public interface IHasDefault : IBuildableClass +{ /// - /// Options after a name call. + /// The default value that will be used when reading when + /// the CSV field is empty. /// - /// The class type. - /// The member type. - public interface IHasNameOptions : - IHasMap, - IHasTypeConverter, - IHasNameIndex, - IHasDefault, - IHasValidate - { } + /// The default value. + IHasDefaultOptions Default(TMember defaultValue); /// - /// Has name index capabilities. + /// The default value that will be used when reading when + /// the CSV field is empty. This value is not type checked + /// and will use a to convert + /// the field. This could potentially have runtime errors. /// - /// The class type. - /// The member type. - public interface IHasNameIndex : IBuildableClass - { - /// - /// When reading, is used to get the - /// index of the name used when there - /// are multiple names that are the same. - /// - /// The index of the name. - IHasNameIndexOptions NameIndex(int index); - } + /// The default value. + IHasDefaultOptions Default(string defaultValue); +} +/// +/// Options after a default call. +/// +/// The class type. +/// The member type. +public interface IHasDefaultOptions : + IHasMap, + IHasValidate +{ } + +/// +/// Has constant capabilities. +/// +/// The class type. +/// The member type. +public interface IHasConstant : IBuildableClass +{ /// - /// Options after a name index call. + /// The constant value that will be used for every record when + /// reading and writing. This value will always be used no matter + /// what other mapping configurations are specified. /// - /// The class type. - /// The member type. - public interface IHasNameIndexOptions : - IHasMap, - IHasTypeConverter, - IHasDefault, - IHasValidate - { } + /// The constant value. + IHasMap Constant(TMember value); +} +/// +/// Has validate capabilities. +/// +/// The class type. +/// The member type. +public interface IHasValidate : IBuildableClass +{ /// - /// Has convert using capabilities. + /// The validate expression that will be called on every field when reading. + /// The expression should return true if the field is valid. + /// If false is returned, a + /// will be thrown. /// - /// The class type. - /// The member type. - public interface IHasConvertUsing : IBuildableClass - { - /// - /// Specifies an expression to be used to convert data in the - /// row to the member. - /// - /// The convert expression. - IHasMap ConvertUsing(ConvertFromString convertExpression); - - /// - /// Specifies an expression to be used to convert the object - /// to a field. - /// - /// The convert expression. - IHasMap ConvertUsing(ConvertToString convertExpression); - } + /// The validation expression. + IHasMap Validate(Validate validateExpression); +} +/// +/// Has build capabilities. +/// +/// The class type. +public interface IBuildableClass +{ /// - /// Has default capabilities. + /// Builds the . /// - /// The class type. - /// The member type. - public interface IHasDefault : IBuildableClass + ClassMap Build(); +} + +internal class ClassMapBuilder : IHasMap +{ + private readonly ClassMap map; + + public ClassMapBuilder() { - /// - /// The default value that will be used when reading when - /// the CSV field is empty. - /// - /// The default value. - IHasDefaultOptions Default(TMember defaultValue); - - /// - /// The default value that will be used when reading when - /// the CSV field is empty. This value is not type checked - /// and will use a to convert - /// the field. This could potentially have runtime errors. - /// - /// The default value. - IHasDefaultOptions Default(string defaultValue); + map = new BuilderClassMap(); } - /// - /// Options after a default call. - /// - /// The class type. - /// The member type. - public interface IHasDefaultOptions : - IHasMap, - IHasValidate - { } + public IHasMapOptions Map(Expression> expression, bool useExistingMap = true) + { + return new MemberMapBuilder(map, map.Map(expression, useExistingMap)); + } - /// - /// Has constant capabilities. - /// - /// The class type. - /// The member type. - public interface IHasConstant : IBuildableClass + public ClassMap Build() { - /// - /// The constant value that will be used for every record when - /// reading and writing. This value will always be used no matter - /// what other mapping configurations are specified. - /// - /// The constant value. - IHasMap Constant(TMember value); + return map; } - /// - /// Has validate capabilities. - /// - /// The class type. - /// The member type. - public interface IHasValidate : IBuildableClass + private class BuilderClassMap : ClassMap { } +} + +internal class MemberMapBuilder : + IHasMap, + IHasMapOptions, + IHasTypeConverter, + IHasTypeConverterOptions, + IHasIndex, + IHasIndexOptions, + IHasName, + IHasNameOptions, + IHasNameIndex, + IHasNameIndexOptions, + IHasOptional, + IHasOptionalOptions, + IHasConvertUsing, + IHasDefault, + IHasDefaultOptions, + IHasConstant, + IHasValidate +{ + private readonly ClassMap classMap; + private readonly MemberMap memberMap; + + public MemberMapBuilder(ClassMap classMap, MemberMap memberMap) { - /// - /// The validate expression that will be called on every field when reading. - /// The expression should return true if the field is valid. - /// If false is returned, a - /// will be thrown. - /// - /// The validation expression. - IHasMap Validate(Validate validateExpression); + this.classMap = classMap; + this.memberMap = memberMap; } - /// - /// Has build capabilities. - /// - /// The class type. - public interface IBuildableClass +#pragma warning disable CS0693 // Type parameter has the same name as the type parameter from outer type + public IHasMapOptions Map(Expression> expression, bool useExistingMap = true) + { + return new MemberMapBuilder(classMap, classMap.Map(expression, useExistingMap)); + } +#pragma warning restore CS0693 // Type parameter has the same name as the type parameter from outer type + + public IHasMap ConvertUsing(ConvertFromString convertExpression) + { + memberMap.Convert(convertExpression); + return this; + } + + public IHasMap ConvertUsing(ConvertToString convertExpression) + { + memberMap.Convert(convertExpression); + return this; + } + + public IHasDefaultOptions Default(TMember defaultValue) { - /// - /// Builds the . - /// - ClassMap Build(); + memberMap.Default(defaultValue); + return this; } - internal class ClassMapBuilder : IHasMap + public IHasDefaultOptions Default(string defaultValue) { - private readonly ClassMap map; + memberMap.Default(defaultValue); + return this; + } - public ClassMapBuilder() - { - map = new BuilderClassMap(); - } + public IHasIndexOptions Index(int index, int indexEnd = -1) + { + memberMap.Index(index, indexEnd); + return this; + } - public IHasMapOptions Map(Expression> expression, bool useExistingMap = true) - { - return new MemberMapBuilder(map, map.Map(expression, useExistingMap)); - } + public IHasNameOptions Name(params string[] names) + { + memberMap.Name(names); + return this; + } - public ClassMap Build() - { - return map; - } + public IHasNameIndexOptions NameIndex(int index) + { + memberMap.NameIndex(index); + return this; + } - private class BuilderClassMap : ClassMap { } + public IHasOptionalOptions Optional() + { + memberMap.Optional(); + return this; } - internal class MemberMapBuilder : - IHasMap, - IHasMapOptions, - IHasTypeConverter, - IHasTypeConverterOptions, - IHasIndex, - IHasIndexOptions, - IHasName, - IHasNameOptions, - IHasNameIndex, - IHasNameIndexOptions, - IHasOptional, - IHasOptionalOptions, - IHasConvertUsing, - IHasDefault, - IHasDefaultOptions, - IHasConstant, - IHasValidate + public IHasTypeConverterOptions TypeConverter(ITypeConverter typeConverter) { - private readonly ClassMap classMap; - private readonly MemberMap memberMap; + memberMap.TypeConverter(typeConverter); + return this; + } - public MemberMapBuilder(ClassMap classMap, MemberMap memberMap) - { - this.classMap = classMap; - this.memberMap = memberMap; - } + public IHasTypeConverterOptions TypeConverter() where TConverter : ITypeConverter + { + memberMap.TypeConverter(); + return this; + } -#pragma warning disable CS0693 // Type parameter has the same name as the type parameter from outer type - public IHasMapOptions Map(Expression> expression, bool useExistingMap = true) - { - return new MemberMapBuilder(classMap, classMap.Map(expression, useExistingMap)); - } -#pragma warning restore CS0693 // Type parameter has the same name as the type parameter from outer type + public IHasMap Constant(TMember value) + { + memberMap.Constant(value); + return this; + } - public IHasMap ConvertUsing(ConvertFromString convertExpression) - { - memberMap.Convert(convertExpression); - return this; - } - - public IHasMap ConvertUsing(ConvertToString convertExpression) - { - memberMap.Convert(convertExpression); - return this; - } - - public IHasDefaultOptions Default(TMember defaultValue) - { - memberMap.Default(defaultValue); - return this; - } - - public IHasDefaultOptions Default(string defaultValue) - { - memberMap.Default(defaultValue); - return this; - } - - public IHasIndexOptions Index(int index, int indexEnd = -1) - { - memberMap.Index(index, indexEnd); - return this; - } - - public IHasNameOptions Name(params string[] names) - { - memberMap.Name(names); - return this; - } - - public IHasNameIndexOptions NameIndex(int index) - { - memberMap.NameIndex(index); - return this; - } - - public IHasOptionalOptions Optional() - { - memberMap.Optional(); - return this; - } - - public IHasTypeConverterOptions TypeConverter(ITypeConverter typeConverter) - { - memberMap.TypeConverter(typeConverter); - return this; - } - - public IHasTypeConverterOptions TypeConverter() where TConverter : ITypeConverter - { - memberMap.TypeConverter(); - return this; - } - - public IHasMap Constant(TMember value) - { - memberMap.Constant(value); - return this; - } - - public IHasMap Validate(Validate validateExpression) - { - memberMap.Validate(validateExpression); - return this; - } - - public ClassMap Build() - { - return classMap; - } + public IHasMap Validate(Validate validateExpression) + { + memberMap.Validate(validateExpression); + return this; + } + + public ClassMap Build() + { + return classMap; } } diff --git a/src/CsvHelper/Configuration/ClassMapCollection.cs b/src/CsvHelper/Configuration/ClassMapCollection.cs index c63d50b7f..4778040ff 100644 --- a/src/CsvHelper/Configuration/ClassMapCollection.cs +++ b/src/CsvHelper/Configuration/ClassMapCollection.cs @@ -2,186 +2,185 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Collection that holds CsvClassMaps for record types. +/// +public class ClassMapCollection { + private readonly Dictionary data = new Dictionary(); + private readonly CsvContext context; + /// - /// Collection that holds CsvClassMaps for record types. + /// Gets the for the specified record type. /// - public class ClassMapCollection + /// + /// The . + /// + /// The record type. + /// The for the specified record type. + public virtual ClassMap? this[Type type] { - private readonly Dictionary data = new Dictionary(); - private readonly CsvContext context; - - /// - /// Gets the for the specified record type. - /// - /// - /// The . - /// - /// The record type. - /// The for the specified record type. - public virtual ClassMap this[Type type] + get { - get + // Go up the inheritance tree to find the matching type. + // We can't use IsAssignableFrom because both a child + // and it's parent/grandparent/etc could be mapped. + var currentType = type; + while (true) { - // Go up the inheritance tree to find the matching type. - // We can't use IsAssignableFrom because both a child - // and it's parent/grandparent/etc could be mapped. - var currentType = type; - while (true) + if (data.TryGetValue(currentType, out var map)) + { + return map; + } + + currentType = currentType.GetTypeInfo().BaseType; + if (currentType == null) { - if (data.TryGetValue(currentType, out var map)) - { - return map; - } - - currentType = currentType.GetTypeInfo().BaseType; - if (currentType == null) - { - return null; - } + return null; } } } + } - /// - /// Creates a new instance using the given configuration. - /// - /// The context. - public ClassMapCollection(CsvContext context) - { - this.context = context; - } + /// + /// Creates a new instance using the given configuration. + /// + /// The context. + public ClassMapCollection(CsvContext context) + { + this.context = context; + } - /// - /// Finds the for the specified record type. - /// - /// The record type. - /// The for the specified record type. - public virtual ClassMap Find() - { - return (ClassMap)this[typeof(T)]; - } + /// + /// Finds the for the specified record type. + /// + /// The record type. + /// The for the specified record type. + public virtual ClassMap? Find() + { + return (ClassMap?)this[typeof(T)]; + } - /// - /// Adds the specified map for it's record type. If a map - /// already exists for the record type, the specified - /// map will replace it. - /// - /// The map. - internal virtual void Add(ClassMap map) - { - SetMapDefaults(map); + /// + /// Adds the specified map for it's record type. If a map + /// already exists for the record type, the specified + /// map will replace it. + /// + /// The map. + internal virtual void Add(ClassMap map) + { + SetMapDefaults(map); - var type = GetGenericCsvClassMapType(map.GetType()).GetGenericArguments().First(); + var type = GetGenericCsvClassMapType(map.GetType()).GetGenericArguments().First(); - data[type] = map; - } + data[type] = map; + } - /// - /// Removes the class map. - /// - /// The class map type. - internal virtual void Remove(Type classMapType) + /// + /// Removes the class map. + /// + /// The class map type. + internal virtual void Remove(Type classMapType) + { + if (!typeof(ClassMap).IsAssignableFrom(classMapType)) { - if (!typeof(ClassMap).IsAssignableFrom(classMapType)) - { - throw new ArgumentException("The class map type must inherit from CsvClassMap."); - } + throw new ArgumentException("The class map type must inherit from CsvClassMap."); + } - var type = GetGenericCsvClassMapType(classMapType).GetGenericArguments().First(); + var type = GetGenericCsvClassMapType(classMapType).GetGenericArguments().First(); - data.Remove(type); - } + data.Remove(type); + } + + /// + /// Removes all maps. + /// + internal virtual void Clear() + { + data.Clear(); + } - /// - /// Removes all maps. - /// - internal virtual void Clear() + /// + /// Goes up the inheritance tree to find the type instance of CsvClassMap{}. + /// + /// The type to traverse. + /// The type that is CsvClassMap{}. + private Type GetGenericCsvClassMapType(Type type) + { + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(ClassMap<>)) { - data.Clear(); + return type; } - /// - /// Goes up the inheritance tree to find the type instance of CsvClassMap{}. - /// - /// The type to traverse. - /// The type that is CsvClassMap{}. - private Type GetGenericCsvClassMapType(Type type) - { - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(ClassMap<>)) - { - return type; - } + var baseType = type.GetTypeInfo().BaseType ?? throw new InvalidOperationException($"The type '{type.FullName}' does not have a base type."); - return GetGenericCsvClassMapType(type.GetTypeInfo().BaseType); - } + return GetGenericCsvClassMapType(baseType); + } - /// - /// Sets defaults for the mapping tree. The defaults used - /// to be set inside the classes, but this didn't allow for - /// the TypeConverter to be created from the Configuration's - /// TypeConverterFactory. - /// - /// The map to set defaults on. - private void SetMapDefaults(ClassMap map) + /// + /// Sets defaults for the mapping tree. The defaults used + /// to be set inside the classes, but this didn't allow for + /// the TypeConverter to be created from the Configuration's + /// TypeConverterFactory. + /// + /// The map to set defaults on. + private void SetMapDefaults(ClassMap map) + { + foreach (var parameterMap in map.ParameterMaps) { - foreach (var parameterMap in map.ParameterMaps) + if (parameterMap.ConstructorTypeMap != null) { - if (parameterMap.ConstructorTypeMap != null) - { - SetMapDefaults(parameterMap.ConstructorTypeMap); - } - else if (parameterMap.ReferenceMap != null) - { - SetMapDefaults(parameterMap.ReferenceMap.Data.Mapping); - } - else - { - if (parameterMap.Data.TypeConverter == null) - { - parameterMap.Data.TypeConverter = context.TypeConverterCache.GetConverter(parameterMap.Data.Parameter.ParameterType); - } - - if (parameterMap.Data.Names.Count == 0) - { - parameterMap.Data.Names.Add(parameterMap.Data.Parameter.Name); - } - } + SetMapDefaults(parameterMap.ConstructorTypeMap); } - - foreach (var memberMap in map.MemberMaps) + else if (parameterMap.ReferenceMap != null) { - if (memberMap.Data.Member == null) + SetMapDefaults(parameterMap.ReferenceMap.Data.Mapping); + } + else + { + if (parameterMap.Data.TypeConverter == null) { - continue; + parameterMap.Data.TypeConverter = context.TypeConverterCache.GetConverter(parameterMap.Data.Parameter.ParameterType); } - if (memberMap.Data.TypeConverter == null && memberMap.Data.ReadingConvertExpression == null && memberMap.Data.WritingConvertExpression == null) + if (parameterMap.Data.Names.Count == 0) { - memberMap.Data.TypeConverter = context.TypeConverterCache.GetConverter(memberMap.Data.Member.MemberType()); + var parametersName = parameterMap.Data.Parameter.Name ?? throw new InvalidOperationException($"Parameter name cannot be null."); + parameterMap.Data.Names.Add(parametersName); } + } + } - if (memberMap.Data.Names.Count == 0) - { - memberMap.Data.Names.Add(memberMap.Data.Member.Name); - } + foreach (var memberMap in map.MemberMaps) + { + if (memberMap.Data.Member == null) + { + continue; + } + + if (memberMap.Data.TypeConverter == null && memberMap.Data.ReadingConvertExpression == null && memberMap.Data.WritingConvertExpression == null) + { + memberMap.Data.TypeConverter = context.TypeConverterCache.GetConverter(memberMap.Data.Member.MemberType()); } - foreach (var referenceMap in map.ReferenceMaps) + if (memberMap.Data.Names.Count == 0) { - SetMapDefaults(referenceMap.Data.Mapping); + memberMap.Data.Names.Add(memberMap.Data.Member.Name); + } + } - if (context.Configuration.ReferenceHeaderPrefix != null) - { - var args = new ReferenceHeaderPrefixArgs(referenceMap.Data.Member.MemberType(), referenceMap.Data.Member.Name); - referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(args); - } + foreach (var referenceMap in map.ReferenceMaps) + { + SetMapDefaults(referenceMap.Data.Mapping); + + if (context.Configuration.ReferenceHeaderPrefix != null) + { + var args = new ReferenceHeaderPrefixArgs(referenceMap.Data.Member.MemberType(), referenceMap.Data.Member.Name); + referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(args); } } } diff --git a/src/CsvHelper/Configuration/ClassMap`1.cs b/src/CsvHelper/Configuration/ClassMap`1.cs index af5b50ca5..3748994f0 100644 --- a/src/CsvHelper/Configuration/ClassMap`1.cs +++ b/src/CsvHelper/Configuration/ClassMap`1.cs @@ -2,111 +2,109 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Linq.Expressions; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Maps class members to CSV fields. +/// +/// The of class to map. +public abstract class ClassMap : ClassMap { /// - /// Maps class members to CSV fields. + /// Creates an instance of . + /// + public ClassMap() : base(typeof(TClass)) { } + + /// + /// Maps a member to a CSV field. /// - /// The of class to map. - public abstract class ClassMap : ClassMap + /// The member to map. + /// If true, an existing map will be used if available. + /// If false, a new map is created for the same member. + /// The member mapping. + public virtual MemberMap Map(Expression> expression, bool useExistingMap = true) { - /// - /// Creates an instance of . - /// - public ClassMap() : base(typeof(TClass)) { } + var (classMap, member) = GetMemberMap(expression); + var memberMap = classMap.Map(typeof(TClass), member, useExistingMap); ; - /// - /// Maps a member to a CSV field. - /// - /// The member to map. - /// If true, an existing map will be used if available. - /// If false, a new map is created for the same member. - /// The member mapping. - public virtual MemberMap Map(Expression> expression, bool useExistingMap = true) - { - var (classMap, member) = GetMemberMap(expression); - var memberMap = classMap.Map(typeof(TClass), member, useExistingMap); ; + return (MemberMap)memberMap; + } - return (MemberMap)memberMap; - } + /// + /// Maps a member to a CSV field. + /// + /// The member to map. + /// If true, an existing map will be used if available. + /// If false, a new map is created for the same member. + /// The member mapping. + public virtual MemberMap Map(Expression> expression, bool useExistingMap = true) + { + var (classMap, member) = GetMemberMap(expression); + var memberMap = classMap.Map(typeof(TClass), member, useExistingMap); - /// - /// Maps a member to a CSV field. - /// - /// The member to map. - /// If true, an existing map will be used if available. - /// If false, a new map is created for the same member. - /// The member mapping. - public virtual MemberMap Map(Expression> expression, bool useExistingMap = true) - { - var (classMap, member) = GetMemberMap(expression); - var memberMap = classMap.Map(typeof(TClass), member, useExistingMap); + return memberMap; + } - return memberMap; - } + /// + /// Meant for internal use only. + /// Maps a member to another class map. When this is used, accessing a property through + /// sub-property mapping later won't work. You can only use one or the other. When using + /// this, ConvertUsing will also not work. + /// + /// The type of the class map. + /// The expression. + /// Constructor arguments used to create the reference map. + /// The reference mapping for the member. + public virtual MemberReferenceMap References(Expression> expression, params object[] constructorArgs) where TClassMap : ClassMap + { + var member = ReflectionHelper.GetMember(expression); + return References(typeof(TClassMap), member, constructorArgs); + } - /// - /// Meant for internal use only. - /// Maps a member to another class map. When this is used, accessing a property through - /// sub-property mapping later won't work. You can only use one or the other. When using - /// this, ConvertUsing will also not work. - /// - /// The type of the class map. - /// The expression. - /// Constructor arguments used to create the reference map. - /// The reference mapping for the member. - public virtual MemberReferenceMap References(Expression> expression, params object[] constructorArgs) where TClassMap : ClassMap + private (ClassMap, MemberInfo) GetMemberMap(Expression> expression) + { + var stack = ReflectionHelper.GetMembers(expression); + if (stack.Count == 0) { - var member = ReflectionHelper.GetMember(expression); - return References(typeof(TClassMap), member, constructorArgs); + throw new InvalidOperationException($"No members were found in expression '{expression}'."); } - private (ClassMap, MemberInfo) GetMemberMap(Expression> expression) - { - var stack = ReflectionHelper.GetMembers(expression); - if (stack.Count == 0) - { - throw new InvalidOperationException($"No members were found in expression '{expression}'."); - } - - ClassMap currentClassMap = this; - MemberInfo member; + ClassMap currentClassMap = this; + MemberInfo member; - if (stack.Count > 1) + if (stack.Count > 1) + { + // We need to add a reference map for every sub member. + while (stack.Count > 1) { - // We need to add a reference map for every sub member. - while (stack.Count > 1) + member = stack.Pop(); + Type mapType; + var property = member as PropertyInfo; + var field = member as FieldInfo; + if (property != null) { - member = stack.Pop(); - Type mapType; - var property = member as PropertyInfo; - var field = member as FieldInfo; - if (property != null) - { - mapType = typeof(DefaultClassMap<>).MakeGenericType(property.PropertyType); - } - else if (field != null) - { - mapType = typeof(DefaultClassMap<>).MakeGenericType(field.FieldType); - } - else - { - throw new InvalidOperationException("The given expression was not a property or a field."); - } - - var referenceMap = currentClassMap.References(mapType, member); - currentClassMap = referenceMap.Data.Mapping; + mapType = typeof(DefaultClassMap<>).MakeGenericType(property.PropertyType); } + else if (field != null) + { + mapType = typeof(DefaultClassMap<>).MakeGenericType(field.FieldType); + } + else + { + throw new InvalidOperationException("The given expression was not a property or a field."); + } + + var referenceMap = currentClassMap.References(mapType, member); + currentClassMap = referenceMap.Data.Mapping; } + } - // Add the member map to the last reference map. - member = stack.Pop(); + // Add the member map to the last reference map. + member = stack.Pop(); - return (currentClassMap, member); - } + return (currentClassMap, member); } } diff --git a/src/CsvHelper/Configuration/ConfigurationException.cs b/src/CsvHelper/Configuration/ConfigurationException.cs index c518766b0..1415437db 100644 --- a/src/CsvHelper/Configuration/ConfigurationException.cs +++ b/src/CsvHelper/Configuration/ConfigurationException.cs @@ -2,35 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration; -namespace CsvHelper.Configuration +/// +/// Represents configuration errors that occur. +/// +[Serializable] +public class ConfigurationException : CsvHelperException { /// - /// Represents configuration errors that occur. + /// Initializes a new instance of the class. /// - [Serializable] - public class ConfigurationException : CsvHelperException - { - /// - /// Initializes a new instance of the class. - /// - public ConfigurationException() { } + public ConfigurationException() { } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The message that describes the error. - public ConfigurationException( string message ) : base( message ) { } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public ConfigurationException(string message) : base(message) { } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public ConfigurationException( string message, Exception innerException ) : base( message, innerException ) { } - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public ConfigurationException(string message, Exception innerException) : base(message, innerException) { } } diff --git a/src/CsvHelper/Configuration/ConfigurationFunctions.cs b/src/CsvHelper/Configuration/ConfigurationFunctions.cs index b17e28668..99ed29616 100644 --- a/src/CsvHelper/Configuration/ConfigurationFunctions.cs +++ b/src/CsvHelper/Configuration/ConfigurationFunctions.cs @@ -3,266 +3,267 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Delegates; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// Holds the default callback methods for delegate members of CsvHelper.Configuration.Configuration. +public static class ConfigurationFunctions { - /// Holds the default callback methods for delegate members of CsvHelper.Configuration.Configuration. - public static class ConfigurationFunctions + private static readonly char[] lineEndingChars = new char[] { '\r', '\n' }; + + /// + /// Throws a if is not empty. + /// + public static void HeaderValidated(HeaderValidatedArgs args) { - private static readonly char[] lineEndingChars = new char[] { '\r', '\n' }; + if (args.InvalidHeaders.Count() == 0) + { + return; + } - /// - /// Throws a if is not empty. - /// - public static void HeaderValidated(HeaderValidatedArgs args) + var errorMessage = new StringBuilder(); + foreach (var invalidHeader in args.InvalidHeaders) { - if (args.InvalidHeaders.Count() == 0) - { - return; - } + errorMessage.AppendLine($"Header with name '{string.Join("' or '", invalidHeader.Names)}'[{invalidHeader.Index}] was not found."); + } - var errorMessage = new StringBuilder(); - foreach (var invalidHeader in args.InvalidHeaders) - { - errorMessage.AppendLine($"Header with name '{string.Join("' or '", invalidHeader.Names)}'[{invalidHeader.Index}] was not found."); - } + if (args.Context.Reader?.HeaderRecord != null) + { + errorMessage.AppendLine($"Headers: '{string.Join("', '", args.Context.Reader.HeaderRecord)}'"); + } - if (args.Context.Reader.HeaderRecord != null) - { - errorMessage.AppendLine($"Headers: '{string.Join("', '", args.Context.Reader.HeaderRecord)}'"); - } + var messagePostfix = + $"If you are expecting some headers to be missing and want to ignore this validation, " + + $"set the configuration {nameof(HeaderValidated)} to null. You can also change the " + + $"functionality to do something else, like logging the issue."; + errorMessage.AppendLine(messagePostfix); - var messagePostfix = - $"If you are expecting some headers to be missing and want to ignore this validation, " + - $"set the configuration {nameof(HeaderValidated)} to null. You can also change the " + - $"functionality to do something else, like logging the issue."; - errorMessage.AppendLine(messagePostfix); + throw new HeaderValidationException(args.Context, args.InvalidHeaders, errorMessage.ToString()); + } - throw new HeaderValidationException(args.Context, args.InvalidHeaders, errorMessage.ToString()); - } + /// + /// Throws a MissingFieldException. + /// + public static void MissingFieldFound(MissingFieldFoundArgs args) + { + var messagePostfix = $"You can ignore missing fields by setting {nameof(MissingFieldFound)} to null."; - /// - /// Throws a MissingFieldException. - /// - public static void MissingFieldFound(MissingFieldFoundArgs args) + // Get by index. + + if (args.HeaderNames == null || args.HeaderNames.Length == 0) { - var messagePostfix = $"You can ignore missing fields by setting {nameof(MissingFieldFound)} to null."; + throw new MissingFieldException(args.Context, $"Field at index '{args.Index}' does not exist. {messagePostfix}"); + } - // Get by index. + // Get by name. - if (args.HeaderNames == null || args.HeaderNames.Length == 0) - { - throw new MissingFieldException(args.Context, $"Field at index '{args.Index}' does not exist. {messagePostfix}"); - } + var indexText = args.Index > 0 ? $" at field index '{args.Index}'" : string.Empty; - // Get by name. + if (args.HeaderNames.Length == 1) + { + throw new MissingFieldException(args.Context, $"Field with name '{args.HeaderNames[0]}'{indexText} does not exist. {messagePostfix}"); + } - var indexText = args.Index > 0 ? $" at field index '{args.Index}'" : string.Empty; + throw new MissingFieldException(args.Context, $"Field containing names '{string.Join("' or '", args.HeaderNames)}'{indexText} does not exist. {messagePostfix}"); + } - if (args.HeaderNames.Length == 1) - { - throw new MissingFieldException(args.Context, $"Field with name '{args.HeaderNames[0]}'{indexText} does not exist. {messagePostfix}"); - } + /// + /// Throws a . + /// + public static void BadDataFound(BadDataFoundArgs args) + { + throw new BadDataException(args.Field, args.RawRecord, args.Context, $"You can ignore bad data by setting {nameof(BadDataFound)} to null."); + } - throw new MissingFieldException(args.Context, $"Field containing names '{string.Join("' or '", args.HeaderNames)}'{indexText} does not exist. {messagePostfix}"); - } + /// + /// Throws the given . + /// + public static bool ReadingExceptionOccurred(ReadingExceptionOccurredArgs args) + { + return true; + } - /// - /// Throws a . - /// - public static void BadDataFound(BadDataFoundArgs args) - { - throw new BadDataException(args.Field, args.RawRecord, args.Context, $"You can ignore bad data by setting {nameof(BadDataFound)} to null."); - } + /// + /// Returns true if the field contains a , + /// starts with a space, ends with a space, contains \r or \n, or contains + /// the . + /// + /// The args. + /// true if the field should be quoted, otherwise false. + public static bool ShouldQuote(ShouldQuoteArgs args) + { + var config = args.Row.Configuration; + var field = args.Field; - /// - /// Throws the given . - /// - public static bool ReadingExceptionOccurred(ReadingExceptionOccurredArgs args) + if (field == null || field.Length == 0) { - return true; + return false; } - /// - /// Returns true if the field contains a , - /// starts with a space, ends with a space, contains \r or \n, or contains - /// the . - /// - /// The args. - /// true if the field should be quoted, otherwise false. - public static bool ShouldQuote(ShouldQuoteArgs args) - { - var config = args.Row.Configuration; - var field = args.Field; - - var shouldQuote = !string.IsNullOrEmpty(field) && - ( - field[0] == ' ' // Starts with a space - || field[field.Length - 1] == ' ' // Ends with a space - || field.Contains(config.Quote) // Contains quote - || !config.IsNewLineSet && field.IndexOfAny(lineEndingChars) > -1 // Contains line ending characters - || config.IsNewLineSet && field.Contains(config.NewLine) // Contains newline - || (config.Delimiter.Length > 0 && field.Contains(config.Delimiter)) // Contains delimiter - ); - - return shouldQuote; - } + var shouldQuote = + ( + field[0] == ' ' // Starts with a space + || field[field.Length - 1] == ' ' // Ends with a space + || field.Contains(config.Quote) // Contains quote + || !config.IsNewLineSet && field.IndexOfAny(lineEndingChars) > -1 // Contains line ending characters + || config.IsNewLineSet && field.Contains(config.NewLine) // Contains newline + || (config.Delimiter.Length > 0 && field.Contains(config.Delimiter)) // Contains delimiter + ); + + return shouldQuote; + } + + /// + /// Returns the as given. + /// + public static string PrepareHeaderForMatch(PrepareHeaderForMatchArgs args) + { + return args.Header ?? string.Empty; + } + + /// + /// Returns true if : + /// 1. does not have a parameterless constructor + /// 2. has a constructor + /// 3. is not a value type + /// 4. is not a primitive + /// 5. is not an enum + /// 6. is not an interface + /// 7. TypeCode is an Object. + /// + public static bool ShouldUseConstructorParameters(ShouldUseConstructorParametersArgs args) + { + return !args.ParameterType.HasParameterlessConstructor() + && args.ParameterType.HasConstructor() + && !args.ParameterType.IsValueType + && !args.ParameterType.IsPrimitive + && !args.ParameterType.IsEnum + && !args.ParameterType.IsInterface + && Type.GetTypeCode(args.ParameterType) == TypeCode.Object; + } + + /// + /// Returns the type's constructor with the most parameters. + /// If two constructors have the same number of parameters, then + /// there is no guarantee which one will be returned. If you have + /// that situation, you should probably implement this function yourself. + /// + public static ConstructorInfo GetConstructor(GetConstructorArgs args) + { + return args.ClassType.GetConstructorWithMostParameters(); + } - /// - /// Returns the as given. - /// - public static string PrepareHeaderForMatch(PrepareHeaderForMatchArgs args) + /// + /// Returns the header name ran through . + /// If no header exists, property names will be Field1, Field2, Field3, etc. + /// + /// The args. + public static string GetDynamicPropertyName(GetDynamicPropertyNameArgs args) + { + if (args.Context.Reader?.HeaderRecord == null) { - return args.Header; + return $"Field{args.FieldIndex + 1}"; } - /// - /// Returns true if : - /// 1. does not have a parameterless constructor - /// 2. has a constructor - /// 3. is not a value type - /// 4. is not a primitive - /// 5. is not an enum - /// 6. is not an interface - /// 7. TypeCode is an Object. - /// - public static bool ShouldUseConstructorParameters(ShouldUseConstructorParametersArgs args) + var header = args.Context.Reader.HeaderRecord[args.FieldIndex]; + var prepareHeaderForMatchArgs = new PrepareHeaderForMatchArgs(header, args.FieldIndex); + header = args.Context.Reader.Configuration.PrepareHeaderForMatch(prepareHeaderForMatchArgs); + + return header; + } + + /// + /// Detects the delimiter based on the given text. + /// Return the detected delimiter or null if one wasn't found. + /// + /// The args. + public static string GetDelimiter(GetDelimiterArgs args) + { + var text = args.Text; + var config = args.Configuration; + + if (config.Mode == CsvMode.RFC4180) { - return !args.ParameterType.HasParameterlessConstructor() - && args.ParameterType.HasConstructor() - && !args.ParameterType.IsValueType - && !args.ParameterType.IsPrimitive - && !args.ParameterType.IsEnum - && !args.ParameterType.IsInterface - && Type.GetTypeCode(args.ParameterType) == TypeCode.Object; + // Remove text in between pairs of quotes. + text = Regex.Replace(text, $"{config.Quote}.*?{config.Quote}", string.Empty, RegexOptions.Singleline); } - - /// - /// Returns the type's constructor with the most parameters. - /// If two constructors have the same number of parameters, then - /// there is no guarantee which one will be returned. If you have - /// that situation, you should probably implement this function yourself. - /// - public static ConstructorInfo GetConstructor(GetConstructorArgs args) + else if (config.Mode == CsvMode.Escape) { - return args.ClassType.GetConstructorWithMostParameters(); + // Remove escaped characters. + text = Regex.Replace(text, $"({config.Escape}.)", string.Empty, RegexOptions.Singleline); } - /// - /// Returns the header name ran through . - /// If no header exists, property names will be Field1, Field2, Field3, etc. - /// - /// The args. - public static string GetDynamicPropertyName(GetDynamicPropertyNameArgs args) + var newLine = config.NewLine; + if ((new[] { "\r\n", "\r", "\n" }).Contains(newLine)) { - if (args.Context.Reader.HeaderRecord == null) - { - return $"Field{args.FieldIndex + 1}"; - } - - var header = args.Context.Reader.HeaderRecord[args.FieldIndex]; - var prepareHeaderForMatchArgs = new PrepareHeaderForMatchArgs(header, args.FieldIndex); - header = args.Context.Reader.Configuration.PrepareHeaderForMatch(prepareHeaderForMatchArgs); - - return header; + newLine = "\r\n|\r|\n"; } - /// - /// Detects the delimiter based on the given text. - /// Return the detected delimiter or null if one wasn't found. - /// - /// The args. - public static string GetDelimiter(GetDelimiterArgs args) + var lineDelimiterCounts = new List>(); + while (text.Length > 0) { - var text = args.Text; - var config = args.Configuration; - - if (config.Mode == CsvMode.RFC4180) - { - // Remove text in between pairs of quotes. - text = Regex.Replace(text, $"{config.Quote}.*?{config.Quote}", string.Empty, RegexOptions.Singleline); - } - else if (config.Mode == CsvMode.Escape) - { - // Remove escaped characters. - text = Regex.Replace(text, $"({config.Escape}.)", string.Empty, RegexOptions.Singleline); - } + // Since all escaped text has been removed, we can reliably read line by line. + var match = Regex.Match(text, newLine); + var line = match.Success ? text.Substring(0, match.Index) : text; - var newLine = config.NewLine; - if ((new[] { "\r\n", "\r", "\n" }).Contains(newLine)) + if (line.Length > 0) { - newLine = "\r\n|\r|\n"; - } - - var lineDelimiterCounts = new List>(); - while (text.Length > 0) - { - // Since all escaped text has been removed, we can reliably read line by line. - var match = Regex.Match(text, newLine); - var line = match.Success ? text.Substring(0, match.Index) : text; - - if (line.Length > 0) + var delimiterCounts = new Dictionary(); + foreach (var delimiter in config.DetectDelimiterValues) { - var delimiterCounts = new Dictionary(); - foreach (var delimiter in config.DetectDelimiterValues) - { - // Escape regex special chars to use as regex pattern. - var pattern = Regex.Replace(delimiter, @"([.$^{\[(|)*+?\\])", "\\$1"); - delimiterCounts[delimiter] = Regex.Matches(line, pattern).Count; - } - - lineDelimiterCounts.Add(delimiterCounts); + // Escape regex special chars to use as regex pattern. + var pattern = Regex.Replace(delimiter, @"([.$^{\[(|)*+?\\])", "\\$1"); + delimiterCounts[delimiter] = Regex.Matches(line, pattern).Count; } - text = match.Success ? text.Substring(match.Index + match.Length) : string.Empty; + lineDelimiterCounts.Add(delimiterCounts); } - if (lineDelimiterCounts.Count > 1) - { - // The last line isn't complete and can't be used to reliably detect a delimiter. - lineDelimiterCounts.Remove(lineDelimiterCounts.Last()); - } + text = match.Success ? text.Substring(match.Index + match.Length) : string.Empty; + } - // Rank only the delimiters that appear on every line. - var delimiters = - ( - from counts in lineDelimiterCounts - from count in counts - group count by count.Key into g - where g.All(x => x.Value > 0) - let sum = g.Sum(x => x.Value) - orderby sum descending - select new - { - Delimiter = g.Key, - Count = sum - } - ).ToList(); + if (lineDelimiterCounts.Count > 1) + { + // The last line isn't complete and can't be used to reliably detect a delimiter. + lineDelimiterCounts.Remove(lineDelimiterCounts.Last()); + } - string newDelimiter = null; - if (delimiters.Any(x => x.Delimiter == config.CultureInfo.TextInfo.ListSeparator) && lineDelimiterCounts.Count > 1) + // Rank only the delimiters that appear on every line. + var delimiters = + ( + from counts in lineDelimiterCounts + from count in counts + group count by count.Key into g + where g.All(x => x.Value > 0) + let sum = g.Sum(x => x.Value) + orderby sum descending + select new { - // The culture's separator is on every line. Assume this is the delimiter. - newDelimiter = config.CultureInfo.TextInfo.ListSeparator; - } - else - { - // Choose the highest ranked delimiter. - newDelimiter = delimiters.Select(x => x.Delimiter).FirstOrDefault(); + Delimiter = g.Key, + Count = sum } + ).ToList(); - if (newDelimiter != null) - { - config.Validate(); - } + string? newDelimiter = null; + if (delimiters.Any(x => x.Delimiter == config.CultureInfo.TextInfo.ListSeparator) && lineDelimiterCounts.Count > 1) + { + // The culture's separator is on every line. Assume this is the delimiter. + newDelimiter = config.CultureInfo.TextInfo.ListSeparator; + } + else + { + // Choose the highest ranked delimiter. + newDelimiter = delimiters.Select(x => x.Delimiter).FirstOrDefault(); + } - return newDelimiter ?? config.Delimiter; + if (newDelimiter != null) + { + config.Validate(); } + + return newDelimiter ?? config.Delimiter; } } diff --git a/src/CsvHelper/Configuration/CsvConfiguration.cs b/src/CsvHelper/Configuration/CsvConfiguration.cs index a0b83a2e3..6051aef52 100644 --- a/src/CsvHelper/Configuration/CsvConfiguration.cs +++ b/src/CsvHelper/Configuration/CsvConfiguration.cs @@ -4,331 +4,327 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration.Attributes; using CsvHelper.Delegates; -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Reflection; using System.Text; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Configuration used for reading and writing CSV data. +/// +public record CsvConfiguration : IReaderConfiguration, IWriterConfiguration { - /// - /// Configuration used for reading and writing CSV data. - /// - public record CsvConfiguration : IReaderConfiguration, IWriterConfiguration - { - private string newLine = "\r\n"; + private string newLine = "\r\n"; - /// - public virtual bool AllowComments { get; set; } + /// + public virtual bool AllowComments { get; set; } - /// - public virtual BadDataFound BadDataFound { get; set; } = ConfigurationFunctions.BadDataFound; + /// + public virtual BadDataFound BadDataFound { get; set; } = ConfigurationFunctions.BadDataFound; - /// - public virtual int BufferSize { get; set; } = 0x1000; + /// + public virtual int BufferSize { get; set; } = 0x1000; - /// - public virtual bool CacheFields { get; set; } + /// + public virtual bool CacheFields { get; set; } - /// - public virtual char Comment { get; set; } = '#'; + /// + public virtual char Comment { get; set; } = '#'; - /// - public virtual bool CountBytes { get; set; } + /// + public virtual bool CountBytes { get; set; } - /// - public virtual CultureInfo CultureInfo { get; protected internal set; } + /// + public virtual CultureInfo CultureInfo { get; protected internal set; } - /// - public virtual string Delimiter { get; set; } + /// + public virtual string Delimiter { get; set; } - /// - public virtual bool DetectDelimiter { get; set; } + /// + public virtual bool DetectDelimiter { get; set; } - /// - public virtual GetDelimiter GetDelimiter { get; set; } = ConfigurationFunctions.GetDelimiter; + /// + public virtual GetDelimiter GetDelimiter { get; set; } = ConfigurationFunctions.GetDelimiter; - /// - public virtual string[] DetectDelimiterValues { get; set; } = [",", ";", "|", "\t"]; + /// + public virtual string[] DetectDelimiterValues { get; set; } = [",", ";", "|", "\t"]; - /// - public virtual bool DetectColumnCountChanges { get; set; } + /// + public virtual bool DetectColumnCountChanges { get; set; } - /// - public virtual IComparer DynamicPropertySort { get; set; } + /// + public virtual IComparer? DynamicPropertySort { get; set; } - /// - public virtual Encoding Encoding { get; set; } = Encoding.UTF8; + /// + public virtual Encoding Encoding { get; set; } = Encoding.UTF8; - /// - public virtual char Escape { get; set; } = '"'; + /// + public virtual char Escape { get; set; } = '"'; - /// - public virtual bool ExceptionMessagesContainRawData { get; set; } = true; + /// + public virtual bool ExceptionMessagesContainRawData { get; set; } = true; - /// - public virtual GetConstructor GetConstructor { get; set; } = ConfigurationFunctions.GetConstructor; + /// + public virtual GetConstructor GetConstructor { get; set; } = ConfigurationFunctions.GetConstructor; - /// - public virtual GetDynamicPropertyName GetDynamicPropertyName { get; set; } = ConfigurationFunctions.GetDynamicPropertyName; + /// + public virtual GetDynamicPropertyName GetDynamicPropertyName { get; set; } = ConfigurationFunctions.GetDynamicPropertyName; - /// - public virtual bool HasHeaderRecord { get; set; } = true; + /// + public virtual bool HasHeaderRecord { get; set; } = true; - /// - public virtual HeaderValidated HeaderValidated { get; set; } = ConfigurationFunctions.HeaderValidated; + /// + public virtual HeaderValidated HeaderValidated { get; set; } = ConfigurationFunctions.HeaderValidated; - /// - public virtual bool IgnoreBlankLines { get; set; } = true; + /// + public virtual bool IgnoreBlankLines { get; set; } = true; - /// - public virtual bool IgnoreReferences { get; set; } + /// + public virtual bool IgnoreReferences { get; set; } - /// - public virtual bool IncludePrivateMembers { get; set; } + /// + public virtual bool IncludePrivateMembers { get; set; } - /// - public virtual char[] InjectionCharacters { get; set; } = ['=', '@', '+', '-', '\t', '\r']; + /// + public virtual char[] InjectionCharacters { get; set; } = ['=', '@', '+', '-', '\t', '\r']; - /// - public virtual char InjectionEscapeCharacter { get; set; } = '\''; + /// + public virtual char InjectionEscapeCharacter { get; set; } = '\''; - /// - public virtual InjectionOptions InjectionOptions { get; set; } + /// + public virtual InjectionOptions InjectionOptions { get; set; } - /// - public bool IsNewLineSet { get; private set; } + /// + public bool IsNewLineSet { get; private set; } - /// - public virtual bool LineBreakInQuotedFieldIsBadData { get; set; } + /// + public virtual bool LineBreakInQuotedFieldIsBadData { get; set; } - /// - public double MaxFieldSize { get; set; } + /// + public double MaxFieldSize { get; set; } - /// - public virtual MemberTypes MemberTypes { get; set; } = MemberTypes.Properties; + /// + public virtual MemberTypes MemberTypes { get; set; } = MemberTypes.Properties; - /// - public virtual MissingFieldFound MissingFieldFound { get; set; } = ConfigurationFunctions.MissingFieldFound; + /// + public virtual MissingFieldFound MissingFieldFound { get; set; } = ConfigurationFunctions.MissingFieldFound; - /// - public virtual CsvMode Mode { get; set; } + /// + public virtual CsvMode Mode { get; set; } - /// - public virtual string NewLine + /// + public virtual string NewLine + { + get => newLine; + set { - get => newLine; - set - { - IsNewLineSet = true; - newLine = value; - } + IsNewLineSet = true; + newLine = value; } + } - /// - public virtual PrepareHeaderForMatch PrepareHeaderForMatch { get; set; } = ConfigurationFunctions.PrepareHeaderForMatch; + /// + public virtual PrepareHeaderForMatch PrepareHeaderForMatch { get; set; } = ConfigurationFunctions.PrepareHeaderForMatch; - /// - public virtual int ProcessFieldBufferSize { get; set; } = 1024; + /// + public virtual int ProcessFieldBufferSize { get; set; } = 1024; - /// - public virtual char Quote { get; set; } = '"'; + /// + public virtual char Quote { get; set; } = '"'; - /// - public virtual ReadingExceptionOccurred ReadingExceptionOccurred { get; set; } = ConfigurationFunctions.ReadingExceptionOccurred; + /// + public virtual ReadingExceptionOccurred ReadingExceptionOccurred { get; set; } = ConfigurationFunctions.ReadingExceptionOccurred; - /// - public virtual ReferenceHeaderPrefix ReferenceHeaderPrefix { get; set; } + /// + public virtual ReferenceHeaderPrefix? ReferenceHeaderPrefix { get; set; } - /// - public ShouldQuote ShouldQuote { get; set; } = ConfigurationFunctions.ShouldQuote; + /// + public ShouldQuote ShouldQuote { get; set; } = ConfigurationFunctions.ShouldQuote; - /// - public virtual ShouldSkipRecord ShouldSkipRecord { get; set; } + /// + public virtual ShouldSkipRecord? ShouldSkipRecord { get; set; } - /// - public virtual ShouldUseConstructorParameters ShouldUseConstructorParameters { get; set; } = ConfigurationFunctions.ShouldUseConstructorParameters; + /// + public virtual ShouldUseConstructorParameters ShouldUseConstructorParameters { get; set; } = ConfigurationFunctions.ShouldUseConstructorParameters; - /// - public virtual TrimOptions TrimOptions { get; set; } + /// + public virtual TrimOptions TrimOptions { get; set; } - /// - public virtual bool UseNewObjectForNullReferenceMembers { get; set; } = true; + /// + public virtual bool UseNewObjectForNullReferenceMembers { get; set; } = true; - /// - public virtual char[] WhiteSpaceChars { get; set; } = [' ']; + /// + public virtual char[] WhiteSpaceChars { get; set; } = [' ']; - /// - /// Initializes a new instance of the class - /// using the given . Since - /// uses for its default, the given - /// will be used instead. - /// - /// The culture information. - public CsvConfiguration(CultureInfo cultureInfo) - { - CultureInfo = cultureInfo; - Delimiter = cultureInfo.TextInfo.ListSeparator; - } + /// + /// Initializes a new instance of the class + /// using the given . Since + /// uses for its default, the given + /// will be used instead. + /// + /// The culture information. + public CsvConfiguration(CultureInfo cultureInfo) + { + CultureInfo = cultureInfo; + Delimiter = cultureInfo.TextInfo.ListSeparator; + } - /// - /// Initializes a new instance of the class - /// using the given . Since - /// uses for its default, the given - /// will be used instead. - /// - /// The culture information. - /// The type that contains the configuration attributes. - /// This will call automatically. - [Obsolete("This constructor is deprecated and will be removed in the next major release. Use CsvConfiguration(CultureInfo) instead.", false)] - public CsvConfiguration(CultureInfo cultureInfo, Type attributesType) - { - CultureInfo = cultureInfo; - Delimiter = cultureInfo.TextInfo.ListSeparator; + /// + /// Initializes a new instance of the class + /// using the given . Since + /// uses for its default, the given + /// will be used instead. + /// + /// The culture information. + /// The type that contains the configuration attributes. + /// This will call automatically. + [Obsolete("This constructor is deprecated and will be removed in the next major release. Use CsvConfiguration(CultureInfo) instead.", false)] + public CsvConfiguration(CultureInfo cultureInfo, Type attributesType) + { + CultureInfo = cultureInfo; + Delimiter = cultureInfo.TextInfo.ListSeparator; - ApplyAttributes(attributesType); - } + ApplyAttributes(attributesType); + } - /// - /// Validates the configuration. - /// - public void Validate() - { - var escape = Escape.ToString(); - var quote = Quote.ToString(); - var lineEndings = new[] { "\r", "\n", "\r\n" }; - var whiteSpaceChars = WhiteSpaceChars.Select(c => c.ToString()).ToArray(); - - // Escape - if (escape == Delimiter) throw new ConfigurationException($"The escape character '{Escape}' and delimiter '{Delimiter}' cannot be the same."); - if (escape == NewLine && IsNewLineSet) throw new ConfigurationException($"The escape character '{Escape}' and new line '{NewLine}' cannot be the same."); - if (lineEndings.Contains(Escape.ToString()) && !IsNewLineSet) throw new ConfigurationException($"The escape character '{Escape}' cannot be a line ending. ('\\r', '\\n', '\\r\\n')"); - if (whiteSpaceChars.Contains(escape)) throw new ConfigurationException($"The escape character '{Escape}' cannot be a WhiteSpaceChar."); - - // Quote - if (quote == Delimiter) throw new ConfigurationException($"The quote character '{Quote}' and the delimiter '{Delimiter}' cannot be the same."); - if (quote == NewLine && IsNewLineSet) throw new ConfigurationException($"The quote character '{Quote}' and new line '{NewLine}' cannot be the same."); - if (lineEndings.Contains(quote)) throw new ConfigurationException($"The quote character '{Quote}' cannot be a line ending. ('\\r', '\\n', '\\r\\n')"); - if (whiteSpaceChars.Contains(quote)) throw new ConfigurationException($"The quote character '{Quote}' cannot be a WhiteSpaceChar."); - - // Delimiter - if (Delimiter == NewLine && IsNewLineSet) throw new ConfigurationException($"The delimiter '{Delimiter}' and new line '{NewLine}' cannot be the same."); - if (lineEndings.Contains(Delimiter)) throw new ConfigurationException($"The delimiter '{Delimiter}' cannot be a line ending. ('\\r', '\\n', '\\r\\n')"); - if (whiteSpaceChars.Contains(Delimiter)) throw new ConfigurationException($"The delimiter '{Delimiter}' cannot be a WhiteSpaceChar."); - - // Detect Delimiter - if (DetectDelimiter && DetectDelimiterValues.Length == 0) throw new ConfigurationException($"At least one value is required for {nameof(DetectDelimiterValues)} when {nameof(DetectDelimiter)} is enabled."); - } + /// + /// Validates the configuration. + /// + public void Validate() + { + var escape = Escape.ToString(); + var quote = Quote.ToString(); + var lineEndings = new[] { "\r", "\n", "\r\n" }; + var whiteSpaceChars = WhiteSpaceChars.Select(c => c.ToString()).ToArray(); + + // Escape + if (escape == Delimiter) throw new ConfigurationException($"The escape character '{Escape}' and delimiter '{Delimiter}' cannot be the same."); + if (escape == NewLine && IsNewLineSet) throw new ConfigurationException($"The escape character '{Escape}' and new line '{NewLine}' cannot be the same."); + if (lineEndings.Contains(Escape.ToString()) && !IsNewLineSet) throw new ConfigurationException($"The escape character '{Escape}' cannot be a line ending. ('\\r', '\\n', '\\r\\n')"); + if (whiteSpaceChars.Contains(escape)) throw new ConfigurationException($"The escape character '{Escape}' cannot be a WhiteSpaceChar."); + + // Quote + if (quote == Delimiter) throw new ConfigurationException($"The quote character '{Quote}' and the delimiter '{Delimiter}' cannot be the same."); + if (quote == NewLine && IsNewLineSet) throw new ConfigurationException($"The quote character '{Quote}' and new line '{NewLine}' cannot be the same."); + if (lineEndings.Contains(quote)) throw new ConfigurationException($"The quote character '{Quote}' cannot be a line ending. ('\\r', '\\n', '\\r\\n')"); + if (whiteSpaceChars.Contains(quote)) throw new ConfigurationException($"The quote character '{Quote}' cannot be a WhiteSpaceChar."); + + // Delimiter + if (Delimiter == NewLine && IsNewLineSet) throw new ConfigurationException($"The delimiter '{Delimiter}' and new line '{NewLine}' cannot be the same."); + if (lineEndings.Contains(Delimiter)) throw new ConfigurationException($"The delimiter '{Delimiter}' cannot be a line ending. ('\\r', '\\n', '\\r\\n')"); + if (whiteSpaceChars.Contains(Delimiter)) throw new ConfigurationException($"The delimiter '{Delimiter}' cannot be a WhiteSpaceChar."); + + // Detect Delimiter + if (DetectDelimiter && DetectDelimiterValues.Length == 0) throw new ConfigurationException($"At least one value is required for {nameof(DetectDelimiterValues)} when {nameof(DetectDelimiter)} is enabled."); + } + + /// + /// Applies class level attribute to configuration. + /// + /// Type with attributes. + public CsvConfiguration ApplyAttributes() + { + return ApplyAttributes(typeof(T)); + } - /// - /// Applies class level attribute to configuration. - /// - /// Type with attributes. - public CsvConfiguration ApplyAttributes() + /// + /// Applies class level attribute to configuration. + /// + /// Type with attributes. + public CsvConfiguration ApplyAttributes(Type type) + { + var attributes = type.GetCustomAttributes().OfType(); + foreach (var attribute in attributes) { - return ApplyAttributes(typeof(T)); + attribute.ApplyTo(this); } - /// - /// Applies class level attribute to configuration. - /// - /// Type with attributes. - public CsvConfiguration ApplyAttributes(Type type) - { - var attributes = type.GetCustomAttributes().OfType(); - foreach (var attribute in attributes) - { - attribute.ApplyTo(this); - } + return this; + } - return this; - } + /// + /// Creates a instance configured using CsvHelper attributes applied + /// to at the type-level. This method requires to + /// be annotated with (or to sub-class a type which is). + /// + /// + /// The type whose attributes should be used to configure the instance. + /// This is normally the type you are intending to map for reading and writing. + /// + /// A new instance configured with attributes applied to . + /// + /// CsvHelper attributes applied to members and parameters do not influence the return value of this method. + /// Such attributes do not define values which are used in and instead influence + /// the maps which are built and used during reading and writing. See and . + /// + /// If is not annotated with . + /// If the argument to the is . + /// If the argument to the does not specify a supported culture. + public static CsvConfiguration FromAttributes() + { + return FromAttributes(typeof(T)); + } - /// - /// Creates a instance configured using CsvHelper attributes applied - /// to at the type-level. This method requires to - /// be annotated with (or to sub-class a type which is). - /// - /// - /// The type whose attributes should be used to configure the instance. - /// This is normally the type you are intending to map for reading and writing. - /// - /// A new instance configured with attributes applied to . - /// - /// CsvHelper attributes applied to members and parameters do not influence the return value of this method. - /// Such attributes do not define values which are used in and instead influence - /// the maps which are built and used during reading and writing. See and . - /// - /// If is not annotated with . - /// If the argument to the is . - /// If the argument to the does not specify a supported culture. - public static CsvConfiguration FromAttributes() - { - return FromAttributes(typeof(T)); - } + /// + /// Creates a instance configured using + /// and CsvHelper attributes applied to at the type-level. + /// This method ignores any applied to . + /// + /// + /// The to configure the returned with. + /// A new instance configured with and attributes applied to . + /// + public static CsvConfiguration FromAttributes(CultureInfo cultureInfo) + { + return FromAttributes(typeof(T), cultureInfo); + } - /// - /// Creates a instance configured using - /// and CsvHelper attributes applied to at the type-level. - /// This method ignores any applied to . - /// - /// - /// The to configure the returned with. - /// A new instance configured with and attributes applied to . - /// - public static CsvConfiguration FromAttributes(CultureInfo cultureInfo) + /// + /// Creates a instance configured using CsvHelper attributes applied + /// to at the type-level. This method requires to + /// be annotated with (or to sub-class a type which is). + /// + /// + /// A new instance configured with attributes applied to . + /// + /// CsvHelper attributes applied to members and parameters do not influence the return value of this method. + /// Such attributes do not define values which are used in and instead influence + /// the maps which are built and used during reading and writing. See and . + /// + /// If is not annotated with . + /// If the argument to the is . + /// If the argument to the does not specify a supported culture. + public static CsvConfiguration FromAttributes(Type type) + { + var cultureInfoAttribute = (CultureInfoAttribute?)Attribute.GetCustomAttribute(type, typeof(CultureInfoAttribute)); + if (cultureInfoAttribute == null) { - return FromAttributes(typeof(T), cultureInfo); + throw new ConfigurationException($"A {nameof(CultureInfoAttribute)} is required on type '{type.Name}' to use this method."); } - /// - /// Creates a instance configured using CsvHelper attributes applied - /// to at the type-level. This method requires to - /// be annotated with (or to sub-class a type which is). - /// - /// - /// A new instance configured with attributes applied to . - /// - /// CsvHelper attributes applied to members and parameters do not influence the return value of this method. - /// Such attributes do not define values which are used in and instead influence - /// the maps which are built and used during reading and writing. See and . - /// - /// If is not annotated with . - /// If the argument to the is . - /// If the argument to the does not specify a supported culture. - public static CsvConfiguration FromAttributes(Type type) - { - var cultureInfoAttribute = (CultureInfoAttribute)Attribute.GetCustomAttribute(type, typeof(CultureInfoAttribute)); - if (cultureInfoAttribute == null) - { - throw new ConfigurationException($"A {nameof(CultureInfoAttribute)} is required on type '{type.Name}' to use this method."); - } - - var config = new CsvConfiguration(CultureInfo.InvariantCulture); - config.ApplyAttributes(type); + var config = new CsvConfiguration(CultureInfo.InvariantCulture); + config.ApplyAttributes(type); - return config; - } + return config; + } - /// - /// Creates a instance configured using - /// and CsvHelper attributes applied to at the type-level. - /// This method ignores any applied to . - /// - /// - /// - /// A new instance configured with and attributes applied to - /// - public static CsvConfiguration FromAttributes(Type type, CultureInfo cultureInfo) - { - var config = new CsvConfiguration(cultureInfo); - config.ApplyAttributes(type); - // Override the attribute. - config.CultureInfo = cultureInfo; + /// + /// Creates a instance configured using + /// and CsvHelper attributes applied to at the type-level. + /// This method ignores any applied to . + /// + /// + /// + /// A new instance configured with and attributes applied to + /// + public static CsvConfiguration FromAttributes(Type type, CultureInfo cultureInfo) + { + var config = new CsvConfiguration(cultureInfo); + config.ApplyAttributes(type); + // Override the attribute. + config.CultureInfo = cultureInfo; - return config; - } + return config; } } diff --git a/src/CsvHelper/Configuration/DefaultClassMap`1.cs b/src/CsvHelper/Configuration/DefaultClassMap`1.cs index 7f44a42ec..0b1163e69 100644 --- a/src/CsvHelper/Configuration/DefaultClassMap`1.cs +++ b/src/CsvHelper/Configuration/DefaultClassMap`1.cs @@ -2,14 +2,13 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// A default that can be used +/// to create a class map dynamically. +/// +/// +public class DefaultClassMap : ClassMap { - /// - /// A default that can be used - /// to create a class map dynamically. - /// - /// - public class DefaultClassMap : ClassMap - { - } } diff --git a/src/CsvHelper/Configuration/IParserConfiguration.cs b/src/CsvHelper/Configuration/IParserConfiguration.cs index ec2ff6e13..16da49f32 100644 --- a/src/CsvHelper/Configuration/IParserConfiguration.cs +++ b/src/CsvHelper/Configuration/IParserConfiguration.cs @@ -3,174 +3,171 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Delegates; -using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Configuration used for the . +/// +public interface IParserConfiguration { /// - /// Configuration used for the . - /// - public interface IParserConfiguration - { - /// - /// Gets the culture info used to read an write CSV files. - /// - CultureInfo CultureInfo { get; } - - /// - /// Cache fields that are created when parsing. - /// Default is false. - /// - bool CacheFields { get; } - - /// - /// The newline string to use. Default is \r\n (CRLF). - /// When writing, this value is always used. - /// When reading, this value is only used if explicitly set. - /// If not set, the parser uses one of \r\n, \r, or \n. - /// - string NewLine { get; } - - /// - /// A value indicating if was set. - /// - /// - /// true if was set. false if is the default. - /// - bool IsNewLineSet { get; } - - /// - /// The mode. - /// See for more details. - /// - CsvMode Mode { get; } - - /// - /// Gets the size of the buffer - /// used for parsing and writing CSV files. - /// Default is 0x1000. - /// - int BufferSize { get; } - - /// - /// The size of the buffer used when processing fields. - /// Default is 1024. - /// - int ProcessFieldBufferSize { get; } - - /// - /// Gets a value indicating whether the number of bytes should - /// be counted while parsing. Default is false. This will slow down parsing - /// because it needs to get the byte count of every char for the given encoding. - /// The needs to be set correctly for this to be accurate. - /// - bool CountBytes { get; } - - /// - /// Gets the encoding used when counting bytes. - /// - Encoding Encoding { get; } - - /// - /// Gets the function that is called when bad field data is found. A field - /// has bad data if it contains a quote and the field is not quoted (escaped). - /// You can supply your own function to do other things like logging the issue - /// instead of throwing an exception. - /// - BadDataFound BadDataFound { get; } - - /// - /// Gets or sets the maximum size of a field. - /// Defaults to 0, indicating maximum field size is not checked. - /// - double MaxFieldSize { get; } - - /// - /// Gets a value indicating if a line break found in a quote field should - /// be considered bad data. true to consider a line break bad data, otherwise false. - /// Defaults to false. - /// - bool LineBreakInQuotedFieldIsBadData { get; } - - /// - /// Gets the character used to denote - /// a line that is commented out. Default is '#'. - /// - char Comment { get; } - - /// - /// Gets a value indicating if comments are allowed. - /// true to allow commented out lines, otherwise false. - /// - bool AllowComments { get; } - - /// - /// Gets a value indicating if blank lines - /// should be ignored when reading. - /// true to ignore, otherwise false. Default is true. - /// - bool IgnoreBlankLines { get; } - - /// - /// Gets the character used to quote fields. - /// Default is '"'. - /// - char Quote { get; } - - /// - /// The delimiter used to separate fields. - /// Default is . - /// - string Delimiter { get; } - - /// - /// Detect the delimiter instead of using the delimiter from configuration. - /// Default is false. - /// - bool DetectDelimiter { get; } - - /// - /// Gets the function that is called when is enabled. - /// - GetDelimiter GetDelimiter { get; } - - /// - /// The possible delimiter values used when detecting the delimiter. - /// Default is [",", ";", "|", "\t"]. - /// - string[] DetectDelimiterValues { get; } - - /// - /// The character used to escape characters. - /// Default is '"'. - /// - char Escape { get; } - - /// - /// Gets the field trimming options. - /// - TrimOptions TrimOptions { get; } - - /// - /// Characters considered whitespace. - /// Used when trimming fields. - /// Default is [' ']. - /// - char[] WhiteSpaceChars { get; } - - /// - /// A value indicating if exception messages contain raw CSV data. - /// true if exception contain raw CSV data, otherwise false. - /// Default is true. - /// - bool ExceptionMessagesContainRawData { get; } - - /// - /// Validates the configuration. - /// - void Validate(); - } + /// Gets the culture info used to read an write CSV files. + /// + CultureInfo CultureInfo { get; } + + /// + /// Cache fields that are created when parsing. + /// Default is false. + /// + bool CacheFields { get; } + + /// + /// The newline string to use. Default is \r\n (CRLF). + /// When writing, this value is always used. + /// When reading, this value is only used if explicitly set. + /// If not set, the parser uses one of \r\n, \r, or \n. + /// + string NewLine { get; } + + /// + /// A value indicating if was set. + /// + /// + /// true if was set. false if is the default. + /// + bool IsNewLineSet { get; } + + /// + /// The mode. + /// See for more details. + /// + CsvMode Mode { get; } + + /// + /// Gets the size of the buffer + /// used for parsing and writing CSV files. + /// Default is 0x1000. + /// + int BufferSize { get; } + + /// + /// The size of the buffer used when processing fields. + /// Default is 1024. + /// + int ProcessFieldBufferSize { get; } + + /// + /// Gets a value indicating whether the number of bytes should + /// be counted while parsing. Default is false. This will slow down parsing + /// because it needs to get the byte count of every char for the given encoding. + /// The needs to be set correctly for this to be accurate. + /// + bool CountBytes { get; } + + /// + /// Gets the encoding used when counting bytes. + /// + Encoding Encoding { get; } + + /// + /// Gets the function that is called when bad field data is found. A field + /// has bad data if it contains a quote and the field is not quoted (escaped). + /// You can supply your own function to do other things like logging the issue + /// instead of throwing an exception. + /// + BadDataFound BadDataFound { get; } + + /// + /// Gets or sets the maximum size of a field. + /// Defaults to 0, indicating maximum field size is not checked. + /// + double MaxFieldSize { get; } + + /// + /// Gets a value indicating if a line break found in a quote field should + /// be considered bad data. true to consider a line break bad data, otherwise false. + /// Defaults to false. + /// + bool LineBreakInQuotedFieldIsBadData { get; } + + /// + /// Gets the character used to denote + /// a line that is commented out. Default is '#'. + /// + char Comment { get; } + + /// + /// Gets a value indicating if comments are allowed. + /// true to allow commented out lines, otherwise false. + /// + bool AllowComments { get; } + + /// + /// Gets a value indicating if blank lines + /// should be ignored when reading. + /// true to ignore, otherwise false. Default is true. + /// + bool IgnoreBlankLines { get; } + + /// + /// Gets the character used to quote fields. + /// Default is '"'. + /// + char Quote { get; } + + /// + /// The delimiter used to separate fields. + /// Default is . + /// + string Delimiter { get; } + + /// + /// Detect the delimiter instead of using the delimiter from configuration. + /// Default is false. + /// + bool DetectDelimiter { get; } + + /// + /// Gets the function that is called when is enabled. + /// + GetDelimiter GetDelimiter { get; } + + /// + /// The possible delimiter values used when detecting the delimiter. + /// Default is [",", ";", "|", "\t"]. + /// + string[] DetectDelimiterValues { get; } + + /// + /// The character used to escape characters. + /// Default is '"'. + /// + char Escape { get; } + + /// + /// Gets the field trimming options. + /// + TrimOptions TrimOptions { get; } + + /// + /// Characters considered whitespace. + /// Used when trimming fields. + /// Default is [' ']. + /// + char[] WhiteSpaceChars { get; } + + /// + /// A value indicating if exception messages contain raw CSV data. + /// true if exception contain raw CSV data, otherwise false. + /// Default is true. + /// + bool ExceptionMessagesContainRawData { get; } + + /// + /// Validates the configuration. + /// + void Validate(); } diff --git a/src/CsvHelper/Configuration/IReaderConfiguration.cs b/src/CsvHelper/Configuration/IReaderConfiguration.cs index e0462b68b..e02359099 100644 --- a/src/CsvHelper/Configuration/IReaderConfiguration.cs +++ b/src/CsvHelper/Configuration/IReaderConfiguration.cs @@ -2,110 +2,102 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Globalization; -using CsvHelper.TypeConversion; -using System.Reflection; -using System.Collections.Generic; -using System.IO; +namespace CsvHelper.Configuration; -namespace CsvHelper.Configuration +/// +/// Configuration used for the . +/// +public interface IReaderConfiguration : IParserConfiguration { /// - /// Configuration used for the . + /// Gets a value indicating if the + /// CSV file has a header record. + /// Default is true. /// - public interface IReaderConfiguration : IParserConfiguration - { - /// - /// Gets a value indicating if the - /// CSV file has a header record. - /// Default is true. - /// - bool HasHeaderRecord { get; } + bool HasHeaderRecord { get; } - /// - /// Gets the function that is called when a header validation check is ran. The default function - /// will throw a if there is no header for a given member mapping. - /// You can supply your own function to do other things like logging the issue instead of throwing an exception. - /// - HeaderValidated HeaderValidated { get; } + /// + /// Gets the function that is called when a header validation check is ran. The default function + /// will throw a if there is no header for a given member mapping. + /// You can supply your own function to do other things like logging the issue instead of throwing an exception. + /// + HeaderValidated HeaderValidated { get; } - /// - /// Gets the function that is called when a missing field is found. The default function will - /// throw a . You can supply your own function to do other things - /// like logging the issue instead of throwing an exception. - /// - MissingFieldFound MissingFieldFound { get; } + /// + /// Gets the function that is called when a missing field is found. The default function will + /// throw a . You can supply your own function to do other things + /// like logging the issue instead of throwing an exception. + /// + MissingFieldFound MissingFieldFound { get; } - /// - /// Gets the function that is called when a reading exception occurs. - /// The default function will re-throw the given exception. If you want to ignore - /// reading exceptions, you can supply your own function to do other things like - /// logging the issue. - /// - ReadingExceptionOccurred ReadingExceptionOccurred { get; } + /// + /// Gets the function that is called when a reading exception occurs. + /// The default function will re-throw the given exception. If you want to ignore + /// reading exceptions, you can supply your own function to do other things like + /// logging the issue. + /// + ReadingExceptionOccurred ReadingExceptionOccurred { get; } - /// - /// Prepares the header field for matching against a member name. - /// The header field and the member name are both ran through this function. - /// You should do things like trimming, removing whitespace, removing underscores, - /// and making casing changes to ignore case. - /// - PrepareHeaderForMatch PrepareHeaderForMatch { get; } + /// + /// Prepares the header field for matching against a member name. + /// The header field and the member name are both ran through this function. + /// You should do things like trimming, removing whitespace, removing underscores, + /// and making casing changes to ignore case. + /// + PrepareHeaderForMatch PrepareHeaderForMatch { get; } - /// - /// Determines if constructor parameters should be used to create - /// the class instead of the default constructor and members. - /// - ShouldUseConstructorParameters ShouldUseConstructorParameters { get; } + /// + /// Determines if constructor parameters should be used to create + /// the class instead of the default constructor and members. + /// + ShouldUseConstructorParameters ShouldUseConstructorParameters { get; } - /// - /// Chooses the constructor to use for constructor mapping. - /// - GetConstructor GetConstructor { get; } + /// + /// Chooses the constructor to use for constructor mapping. + /// + GetConstructor GetConstructor { get; } - /// - /// Gets the name to use for the property of the dynamic object. - /// - GetDynamicPropertyName GetDynamicPropertyName { get; } + /// + /// Gets the name to use for the property of the dynamic object. + /// + GetDynamicPropertyName GetDynamicPropertyName { get; } - /// - /// Gets a value indicating whether references - /// should be ignored when auto mapping. true to ignore - /// references, otherwise false. Default is false. - /// - bool IgnoreReferences { get; } + /// + /// Gets a value indicating whether references + /// should be ignored when auto mapping. true to ignore + /// references, otherwise false. Default is false. + /// + bool IgnoreReferences { get; } - /// - /// Gets the callback that will be called to - /// determine whether to skip the given record or not. - /// - ShouldSkipRecord ShouldSkipRecord { get; } + /// + /// Gets the callback that will be called to + /// determine whether to skip the given record or not. + /// + ShouldSkipRecord? ShouldSkipRecord { get; } - /// - /// Gets a value indicating if private - /// member should be read from and written to. - /// true to include private member, otherwise false. Default is false. - /// - bool IncludePrivateMembers { get; } + /// + /// Gets a value indicating if private + /// member should be read from and written to. + /// true to include private member, otherwise false. Default is false. + /// + bool IncludePrivateMembers { get; } - /// - /// Gets a callback that will return the prefix for a reference header. - /// - ReferenceHeaderPrefix ReferenceHeaderPrefix { get; } + /// + /// Gets a callback that will return the prefix for a reference header. + /// + ReferenceHeaderPrefix? ReferenceHeaderPrefix { get; } - /// - /// Gets a value indicating whether changes in the column - /// count should be detected. If true, a - /// will be thrown if a different column count is detected. - /// - bool DetectColumnCountChanges { get; } + /// + /// Gets a value indicating whether changes in the column + /// count should be detected. If true, a + /// will be thrown if a different column count is detected. + /// + bool DetectColumnCountChanges { get; } - /// - /// Gets the member types that are used when auto mapping. - /// MemberTypes are flags, so you can choose more than one. - /// Default is Properties. - /// - MemberTypes MemberTypes { get; } - } + /// + /// Gets the member types that are used when auto mapping. + /// MemberTypes are flags, so you can choose more than one. + /// Default is Properties. + /// + MemberTypes MemberTypes { get; } } diff --git a/src/CsvHelper/Configuration/IWriterConfiguration.cs b/src/CsvHelper/Configuration/IWriterConfiguration.cs index ef4ad0d98..36c36aabd 100644 --- a/src/CsvHelper/Configuration/IWriterConfiguration.cs +++ b/src/CsvHelper/Configuration/IWriterConfiguration.cs @@ -3,166 +3,163 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using System.Globalization; -using System.Collections.Generic; -using System.IO; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Configuration used for the . +/// +public interface IWriterConfiguration { /// - /// Configuration used for the . - /// - public interface IWriterConfiguration - { - /// - /// Gets the size of the buffer - /// used for parsing and writing CSV files. - /// Default is 0x1000. - /// - int BufferSize { get; } - - /// - /// The mode. - /// See for more details. - /// - CsvMode Mode { get; } - - /// - /// Gets the delimiter used to separate fields. - /// Default is ','; - /// - string Delimiter { get; } - - /// - /// Gets the character used to quote fields. - /// Default is '"'. - /// - char Quote { get; } - - /// - /// The character used to escape characters. - /// Default is '"'. - /// - char Escape { get; } - - /// - /// Gets the field trimming options. - /// - TrimOptions TrimOptions { get; } - - /// - /// Gets the injection options. - /// - InjectionOptions InjectionOptions { get; } - - /// - /// Gets the characters that are used for injection attacks. - /// - char[] InjectionCharacters { get; } - - /// - /// Gets the character used to escape a detected injection. - /// - char InjectionEscapeCharacter { get; } - - /// - /// The newline string to use. Default is \r\n (CRLF). - /// When writing, this value is always used. - /// When reading, this value is only used if explicitly set. If not set, - /// the parser uses one of \r\n, \r, or \n. - /// - string NewLine { get; } - - /// - /// A value indicating if was set. - /// - /// - /// true if was set. false if is the default. - /// - bool IsNewLineSet { get; } - - /// - /// Gets a function that is used to determine if a field should get quoted - /// when writing. - /// - ShouldQuote ShouldQuote { get; } - - /// - /// Gets the culture info used to read and write CSV files. - /// - CultureInfo CultureInfo { get; } - - /// - /// Gets a value indicating if comments are allowed. - /// True to allow commented out lines, otherwise false. - /// - bool AllowComments { get; } - - /// - /// Gets the character used to denote - /// a line that is commented out. Default is '#'. - /// - char Comment { get; } - - /// - /// Gets a value indicating if the - /// CSV file has a header record. - /// Default is true. - /// - bool HasHeaderRecord { get; } - - /// - /// Gets a value indicating whether references - /// should be ignored when auto mapping. True to ignore - /// references, otherwise false. Default is false. - /// - bool IgnoreReferences { get; } - - /// - /// Gets a value indicating if private - /// member should be read from and written to. - /// True to include private member, otherwise false. Default is false. - /// - bool IncludePrivateMembers { get; } - - /// - /// Gets a callback that will return the prefix for a reference header. - /// - ReferenceHeaderPrefix ReferenceHeaderPrefix { get; } - - /// - /// Gets the member types that are used when auto mapping. - /// MemberTypes are flags, so you can choose more than one. - /// Default is Properties. - /// - MemberTypes MemberTypes { get; } - - /// - /// Gets a value indicating that during writing if a new - /// object should be created when a reference member is null. - /// True to create a new object and use it's defaults for the - /// fields, or false to leave the fields empty for all the - /// reference member's member. - /// - bool UseNewObjectForNullReferenceMembers { get; } - - /// - /// Gets the comparer used to order the properties - /// of dynamic objects when writing. The default is null, - /// which will preserve the order the object properties - /// were created with. - /// - IComparer DynamicPropertySort { get; } - - /// - /// A value indicating if exception messages contain raw CSV data. - /// true if exception contain raw CSV data, otherwise false. - /// Default is true. - /// - bool ExceptionMessagesContainRawData { get; } - - /// - /// Validates the configuration. - /// - void Validate(); - } + /// Gets the size of the buffer + /// used for parsing and writing CSV files. + /// Default is 0x1000. + /// + int BufferSize { get; } + + /// + /// The mode. + /// See for more details. + /// + CsvMode Mode { get; } + + /// + /// Gets the delimiter used to separate fields. + /// Default is ','; + /// + string Delimiter { get; } + + /// + /// Gets the character used to quote fields. + /// Default is '"'. + /// + char Quote { get; } + + /// + /// The character used to escape characters. + /// Default is '"'. + /// + char Escape { get; } + + /// + /// Gets the field trimming options. + /// + TrimOptions TrimOptions { get; } + + /// + /// Gets the injection options. + /// + InjectionOptions InjectionOptions { get; } + + /// + /// Gets the characters that are used for injection attacks. + /// + char[] InjectionCharacters { get; } + + /// + /// Gets the character used to escape a detected injection. + /// + char InjectionEscapeCharacter { get; } + + /// + /// The newline string to use. Default is \r\n (CRLF). + /// When writing, this value is always used. + /// When reading, this value is only used if explicitly set. If not set, + /// the parser uses one of \r\n, \r, or \n. + /// + string NewLine { get; } + + /// + /// A value indicating if was set. + /// + /// + /// true if was set. false if is the default. + /// + bool IsNewLineSet { get; } + + /// + /// Gets a function that is used to determine if a field should get quoted + /// when writing. + /// + ShouldQuote ShouldQuote { get; } + + /// + /// Gets the culture info used to read and write CSV files. + /// + CultureInfo CultureInfo { get; } + + /// + /// Gets a value indicating if comments are allowed. + /// True to allow commented out lines, otherwise false. + /// + bool AllowComments { get; } + + /// + /// Gets the character used to denote + /// a line that is commented out. Default is '#'. + /// + char Comment { get; } + + /// + /// Gets a value indicating if the + /// CSV file has a header record. + /// Default is true. + /// + bool HasHeaderRecord { get; } + + /// + /// Gets a value indicating whether references + /// should be ignored when auto mapping. True to ignore + /// references, otherwise false. Default is false. + /// + bool IgnoreReferences { get; } + + /// + /// Gets a value indicating if private + /// member should be read from and written to. + /// True to include private member, otherwise false. Default is false. + /// + bool IncludePrivateMembers { get; } + + /// + /// Gets a callback that will return the prefix for a reference header. + /// + ReferenceHeaderPrefix? ReferenceHeaderPrefix { get; } + + /// + /// Gets the member types that are used when auto mapping. + /// MemberTypes are flags, so you can choose more than one. + /// Default is Properties. + /// + MemberTypes MemberTypes { get; } + + /// + /// Gets a value indicating that during writing if a new + /// object should be created when a reference member is null. + /// True to create a new object and use it's defaults for the + /// fields, or false to leave the fields empty for all the + /// reference member's member. + /// + bool UseNewObjectForNullReferenceMembers { get; } + + /// + /// Gets the comparer used to order the properties + /// of dynamic objects when writing. The default is null, + /// which will preserve the order the object properties + /// were created with. + /// + IComparer? DynamicPropertySort { get; } + + /// + /// A value indicating if exception messages contain raw CSV data. + /// true if exception contain raw CSV data, otherwise false. + /// Default is true. + /// + bool ExceptionMessagesContainRawData { get; } + + /// + /// Validates the configuration. + /// + void Validate(); } diff --git a/src/CsvHelper/Configuration/InjectionOptions.cs b/src/CsvHelper/Configuration/InjectionOptions.cs index 2d90c3555..7c1dc7ea4 100644 --- a/src/CsvHelper/Configuration/InjectionOptions.cs +++ b/src/CsvHelper/Configuration/InjectionOptions.cs @@ -2,34 +2,27 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper.Configuration; -namespace CsvHelper.Configuration +/// +/// Options for handling injection attacks. +/// +public enum InjectionOptions { /// - /// Options for handling injection attacks. + /// No injection protection. /// - public enum InjectionOptions - { - /// - /// No injection protection. - /// - None = 0, - /// - /// Escape injection characters. - /// - Escape, - /// - /// Strip injection characters. - /// - Strip, - /// - /// Throw an exception if injection characters are detected. - /// - Exception, - } + None = 0, + /// + /// Escape injection characters. + /// + Escape, + /// + /// Strip injection characters. + /// + Strip, + /// + /// Throw an exception if injection characters are detected. + /// + Exception, } diff --git a/src/CsvHelper/Configuration/MemberMap.cs b/src/CsvHelper/Configuration/MemberMap.cs index 095908bcd..08a104c67 100644 --- a/src/CsvHelper/Configuration/MemberMap.cs +++ b/src/CsvHelper/Configuration/MemberMap.cs @@ -3,242 +3,250 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.TypeConversion; -using System; using System.Collections; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Mapping info for a member to a CSV field. +/// +[DebuggerDisplay("Member = {Data.Member}, Names = {string.Join(\",\", Data.Names)}, Index = {Data.Index}, Ignore = {Data.Ignore}, Member = {Data.Member}, TypeConverter = {Data.TypeConverter}")] +public abstract class MemberMap { /// - /// Mapping info for a member to a CSV field. + /// Gets the member map data. + /// + public virtual MemberMapData Data { get; protected set; } = new MemberMapData(null); + + /// + /// Type converter options. /// - [DebuggerDisplay("Member = {Data.Member}, Names = {string.Join(\",\", Data.Names)}, Index = {Data.Index}, Ignore = {Data.Ignore}, Member = {Data.Member}, TypeConverter = {Data.TypeConverter}")] - public abstract class MemberMap + public abstract MemberMapTypeConverterOption TypeConverterOption { get; } + + /// + /// Creates an instance of using the given Type and . + /// + /// Type of the class the member being mapped belongs to. + /// The member being mapped. + public static MemberMap CreateGeneric(Type classType, MemberInfo member) { - /// - /// Gets the member map data. - /// - public virtual MemberMapData Data { get; protected set; } - - /// - /// Type converter options. - /// - public virtual MemberMapTypeConverterOption TypeConverterOption { get; protected set; } - - /// - /// Creates an instance of using the given Type and . - /// - /// Type of the class the member being mapped belongs to. - /// The member being mapped. - public static MemberMap CreateGeneric(Type classType, MemberInfo member) - { - var memberMapType = typeof(MemberMap<,>).MakeGenericType(classType, member.MemberType()); - var memberMap = (MemberMap)ObjectResolver.Current.Resolve(memberMapType, member); + var memberMapType = typeof(MemberMap<,>).MakeGenericType(classType, member.MemberType()); + var memberMap = (MemberMap)ObjectResolver.Current.Resolve(memberMapType, member); - return memberMap; - } + return memberMap; + } - /// - /// When reading, is used to get the field - /// at the index of the name if there was a - /// header specified. It will look for the - /// first name match in the order listed. - /// When writing, sets the name of the - /// field in the header record. - /// The first name will be used. - /// - /// The possible names of the CSV field. - public virtual MemberMap Name(params string[] names) + /// + /// When reading, is used to get the field + /// at the index of the name if there was a + /// header specified. It will look for the + /// first name match in the order listed. + /// When writing, sets the name of the + /// field in the header record. + /// The first name will be used. + /// + /// The possible names of the CSV field. + public virtual MemberMap Name(params string[] names) + { + if (names == null || names.Length == 0) { - if (names == null || names.Length == 0) - { - throw new ArgumentNullException(nameof(names)); - } + throw new ArgumentNullException(nameof(names)); + } - Data.Names.Clear(); - Data.Names.AddRange(names); - Data.IsNameSet = true; + Data.Names.Clear(); + Data.Names.AddRange(names); + Data.IsNameSet = true; - return this; - } + return this; + } - /// - /// When reading, is used to get the - /// index of the name used when there - /// are multiple names that are the same. - /// - /// The index of the name. - public virtual MemberMap NameIndex(int index) - { - Data.NameIndex = index; + /// + /// When reading, is used to get the + /// index of the name used when there + /// are multiple names that are the same. + /// + /// The index of the name. + public virtual MemberMap NameIndex(int index) + { + Data.NameIndex = index; - return this; - } + return this; + } - /// - /// When reading, is used to get the field at - /// the given index. When writing, the fields - /// will be written in the order of the field - /// indexes. - /// - /// The index of the CSV field. - /// The end index used when mapping to an member. - public virtual MemberMap Index(int index, int indexEnd = -1) - { - Data.Index = index; - Data.IsIndexSet = true; - Data.IndexEnd = indexEnd; + /// + /// When reading, is used to get the field at + /// the given index. When writing, the fields + /// will be written in the order of the field + /// indexes. + /// + /// The index of the CSV field. + /// The end index used when mapping to an member. + public virtual MemberMap Index(int index, int indexEnd = -1) + { + Data.Index = index; + Data.IsIndexSet = true; + Data.IndexEnd = indexEnd; - return this; - } + return this; + } - /// - /// Ignore the member when reading and writing. - /// If this member has already been mapped as a reference - /// member, either by a class map, or by automapping, calling - /// this method will not ignore all the child members down the - /// tree that have already been mapped. - /// - public virtual MemberMap Ignore() - { - Data.Ignore = true; + /// + /// Ignore the member when reading and writing. + /// If this member has already been mapped as a reference + /// member, either by a class map, or by automapping, calling + /// this method will not ignore all the child members down the + /// tree that have already been mapped. + /// + public virtual MemberMap Ignore() + { + Data.Ignore = true; - return this; - } + return this; + } + + /// + /// Ignore the member when reading and writing. + /// If this member has already been mapped as a reference + /// member, either by a class map, or by automapping, calling + /// this method will not ignore all the child members down the + /// tree that have already been mapped. + /// + /// True to ignore, otherwise false. + public virtual MemberMap Ignore(bool ignore) + { + Data.Ignore = ignore; - /// - /// Ignore the member when reading and writing. - /// If this member has already been mapped as a reference - /// member, either by a class map, or by automapping, calling - /// this method will not ignore all the child members down the - /// tree that have already been mapped. - /// - /// True to ignore, otherwise false. - public virtual MemberMap Ignore(bool ignore) + return this; + } + + /// + /// The default value that will be used when reading when + /// the CSV field is empty. + /// + /// The default value. + /// Use default on conversion failure. + public virtual MemberMap Default(object defaultValue, bool useOnConversionFailure = false) + { + if (Data.Member == null) { - Data.Ignore = ignore; + throw new InvalidOperationException($"{nameof(Data.Member)} cannot be null."); + } - return this; + if (defaultValue == null && Data.Member.MemberType().IsValueType) + { + throw new ArgumentException($"Member of type '{Data.Member.MemberType().FullName}' can't have a default value of null."); } - /// - /// The default value that will be used when reading when - /// the CSV field is empty. - /// - /// The default value. - /// Use default on conversion failure. - public virtual MemberMap Default(object defaultValue, bool useOnConversionFailure = false) + if (defaultValue != null && !Data.Member.MemberType().IsAssignableFrom(defaultValue.GetType())) { - if (defaultValue == null && Data.Member.MemberType().IsValueType) - { - throw new ArgumentException($"Member of type '{Data.Member.MemberType().FullName}' can't have a default value of null."); - } + throw new ArgumentException($"Default of type '{defaultValue.GetType().FullName}' is not assignable to '{Data.Member.MemberType().FullName}'."); + } - if (defaultValue != null && !Data.Member.MemberType().IsAssignableFrom(defaultValue.GetType())) - { - throw new ArgumentException($"Default of type '{defaultValue.GetType().FullName}' is not assignable to '{Data.Member.MemberType().FullName}'."); - } + Data.Default = defaultValue; + Data.IsDefaultSet = true; + Data.UseDefaultOnConversionFailure = useOnConversionFailure; - Data.Default = defaultValue; - Data.IsDefaultSet = true; - Data.UseDefaultOnConversionFailure = useOnConversionFailure; + return this; + } - return this; + /// + /// The constant value that will be used for every record when + /// reading and writing. This value will always be used no matter + /// what other mapping configurations are specified. + /// + /// The constant value. + public virtual MemberMap Constant(object constantValue) + { + if (Data.Member == null) + { + throw new InvalidOperationException($"{nameof(Data.Member)} cannot be null."); } - /// - /// The constant value that will be used for every record when - /// reading and writing. This value will always be used no matter - /// what other mapping configurations are specified. - /// - /// The constant value. - public virtual MemberMap Constant(object constantValue) + if (constantValue == null && Data.Member.MemberType().IsValueType) { - if (constantValue == null && Data.Member.MemberType().IsValueType) - { - throw new ArgumentException($"Member of type '{Data.Member.MemberType().FullName}' can't have a constant value of null."); - } + throw new ArgumentException($"Member of type '{Data.Member.MemberType().FullName}' can't have a constant value of null."); + } - if (constantValue != null && !Data.Member.MemberType().IsAssignableFrom(constantValue.GetType())) - { - throw new ArgumentException($"Constant of type '{constantValue.GetType().FullName}' is not assignable to '{Data.Member.MemberType().FullName}'."); - } + if (constantValue != null && !Data.Member.MemberType().IsAssignableFrom(constantValue.GetType())) + { + throw new ArgumentException($"Constant of type '{constantValue.GetType().FullName}' is not assignable to '{Data.Member.MemberType().FullName}'."); + } - Data.Constant = constantValue; - Data.IsConstantSet = true; + Data.Constant = constantValue; + Data.IsConstantSet = true; - return this; - } + return this; + } - /// - /// Specifies the to use - /// when converting the member to and from a CSV field. - /// - /// The TypeConverter to use. - public virtual MemberMap TypeConverter(ITypeConverter typeConverter) - { - Data.TypeConverter = typeConverter; + /// + /// Specifies the to use + /// when converting the member to and from a CSV field. + /// + /// The TypeConverter to use. + public virtual MemberMap TypeConverter(ITypeConverter typeConverter) + { + Data.TypeConverter = typeConverter; - return this; - } + return this; + } - /// - /// Specifies the to use - /// when converting the member to and from a CSV field. - /// - /// The of the - /// to use. - public virtual MemberMap TypeConverter() where TConverter : ITypeConverter - { - TypeConverter(ObjectResolver.Current.Resolve()); + /// + /// Specifies the to use + /// when converting the member to and from a CSV field. + /// + /// The of the + /// to use. + public virtual MemberMap TypeConverter() where TConverter : ITypeConverter + { + TypeConverter(ObjectResolver.Current.Resolve()); - return this; - } + return this; + } - /// - /// Ignore the member when reading if no matching field name can be found. - /// - public virtual MemberMap Optional() - { - Data.IsOptional = true; + /// + /// Ignore the member when reading if no matching field name can be found. + /// + public virtual MemberMap Optional() + { + Data.IsOptional = true; - return this; - } + return this; + } - /// - /// Specifies an expression to be used to validate a field when reading. - /// - /// - public virtual MemberMap Validate(Validate validateExpression) - { - return Validate(validateExpression, args => $"Field '{args.Field}' is not valid."); - } + /// + /// Specifies an expression to be used to validate a field when reading. + /// + /// + public virtual MemberMap Validate(Validate validateExpression) + { + return Validate(validateExpression, args => $"Field '{args.Field}' is not valid."); + } - /// - /// Specifies an expression to be used to validate a field when reading along with specified exception message. - /// - /// - /// - public virtual MemberMap Validate(Validate validateExpression, ValidateMessage validateMessageExpression) - { - var fieldParameter = Expression.Parameter(typeof(ValidateArgs), "field"); - var validateCallExpression = Expression.Call( - Expression.Constant(validateExpression.Target), - validateExpression.Method, - fieldParameter - ); - var messageCallExpression = Expression.Call( - Expression.Constant(validateMessageExpression.Target), - validateMessageExpression.Method, - fieldParameter - ); - - Data.ValidateExpression = Expression.Lambda(validateCallExpression, fieldParameter); - Data.ValidateMessageExpression = Expression.Lambda(messageCallExpression, fieldParameter); - - return this; - } + /// + /// Specifies an expression to be used to validate a field when reading along with specified exception message. + /// + /// + /// + public virtual MemberMap Validate(Validate validateExpression, ValidateMessage validateMessageExpression) + { + var fieldParameter = Expression.Parameter(typeof(ValidateArgs), "field"); + var validateCallExpression = Expression.Call( + Expression.Constant(validateExpression.Target), + validateExpression.Method, + fieldParameter + ); + var messageCallExpression = Expression.Call( + Expression.Constant(validateMessageExpression.Target), + validateMessageExpression.Method, + fieldParameter + ); + + Data.ValidateExpression = Expression.Lambda(validateCallExpression, fieldParameter); + Data.ValidateMessageExpression = Expression.Lambda(messageCallExpression, fieldParameter); + + return this; } } diff --git a/src/CsvHelper/Configuration/MemberMapCollection.cs b/src/CsvHelper/Configuration/MemberMapCollection.cs index 452ed71f2..6e9b82996 100644 --- a/src/CsvHelper/Configuration/MemberMapCollection.cs +++ b/src/CsvHelper/Configuration/MemberMapCollection.cs @@ -2,246 +2,242 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// A collection that holds 's. +/// +[DebuggerDisplay("Count = {list.Count}")] +public class MemberMapCollection : IList { + private readonly List list = new List(); + private readonly IComparer comparer; + /// - /// A collection that holds 's. - /// - [DebuggerDisplay("Count = {list.Count}")] - public class MemberMapCollection : IList - { - private readonly List list = new List(); - private readonly IComparer comparer; - - /// - /// Gets the number of elements contained in the . - /// - /// - /// The number of elements contained in the . - /// - public virtual int Count => list.Count; - - /// - /// Gets a value indicating whether the is read-only. - /// - /// - /// true if the is read-only; otherwise, false. - /// - public virtual bool IsReadOnly => false; - - /// - /// Initializes a new instance of the class. - /// - public MemberMapCollection() : this(new MemberMapComparer()) { } - - /// - /// Initializes a new instance of the class. - /// - /// The comparer to use when sorting the member maps. - public MemberMapCollection(IComparer comparer) - { - this.comparer = comparer; - } + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + public virtual int Count => list.Count; - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - /// 1 - public virtual IEnumerator GetEnumerator() - { - return list.GetEnumerator(); - } + /// + /// Gets a value indicating whether the is read-only. + /// + /// + /// true if the is read-only; otherwise, false. + /// + public virtual bool IsReadOnly => false; - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - /// 2 - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + /// Initializes a new instance of the class. + /// + public MemberMapCollection() : this(new MemberMapComparer()) { } - /// - /// Adds an item to the . - /// - /// The object to add to the . - /// The is read-only. - /// - public virtual void Add(MemberMap item) - { - list.Add(item); - list.Sort(comparer); - } + /// + /// Initializes a new instance of the class. + /// + /// The comparer to use when sorting the member maps. + public MemberMapCollection(IComparer comparer) + { + this.comparer = comparer; + } - /// - /// Adds a range of items to the . - /// - /// The collection to add. - public virtual void AddRange(ICollection collection) - { - list.AddRange(collection); - list.Sort(comparer); - } + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// 1 + public virtual IEnumerator GetEnumerator() + { + return list.GetEnumerator(); + } - /// - /// Removes all items from the . - /// - /// The is read-only. - /// - public virtual void Clear() - { - list.Clear(); - } + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + /// 2 + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - /// - /// Determines whether the contains a specific value. - /// - /// - /// true if is found in the ; otherwise, false. - /// - /// The object to locate in the . - /// - public virtual bool Contains(MemberMap item) - { - return list.Contains(item); - } + /// + /// Adds an item to the . + /// + /// The object to add to the . + /// The is read-only. + /// + public virtual void Add(MemberMap item) + { + list.Add(item); + list.Sort(comparer); + } - /// - /// Copies the elements of the to an , starting at a particular index. - /// - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.The zero-based index in at which copying begins. is null. is less than 0.The number of elements in the source is greater than the available space from to the end of the destination . - public virtual void CopyTo(MemberMap[] array, int arrayIndex) - { - list.CopyTo(array, arrayIndex); - } + /// + /// Adds a range of items to the . + /// + /// The collection to add. + public virtual void AddRange(ICollection collection) + { + list.AddRange(collection); + list.Sort(comparer); + } - /// - /// Removes the first occurrence of a specific object from the . - /// - /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - /// - /// The object to remove from the . - /// The is read-only. - /// - public virtual bool Remove(MemberMap item) - { - return list.Remove(item); - } + /// + /// Removes all items from the . + /// + /// The is read-only. + /// + public virtual void Clear() + { + list.Clear(); + } - /// - /// Determines the index of a specific item in the . - /// - /// - /// The index of if found in the list; otherwise, -1. - /// - /// The object to locate in the . - /// - public virtual int IndexOf(MemberMap item) - { - return list.IndexOf(item); - } + /// + /// Determines whether the contains a specific value. + /// + /// + /// true if is found in the ; otherwise, false. + /// + /// The object to locate in the . + /// + public virtual bool Contains(MemberMap item) + { + return list.Contains(item); + } - /// - /// Inserts an item to the at the specified index. - /// - /// The zero-based index at which should be inserted. - /// The object to insert into the . - /// is not a valid index in the . - /// The is read-only. - /// - public virtual void Insert(int index, MemberMap item) - { - list.Insert(index, item); - } + /// + /// Copies the elements of the to an , starting at a particular index. + /// + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.The zero-based index in at which copying begins. is null. is less than 0.The number of elements in the source is greater than the available space from to the end of the destination . + public virtual void CopyTo(MemberMap[] array, int arrayIndex) + { + list.CopyTo(array, arrayIndex); + } - /// - /// Removes the item at the specified index. - /// - /// The zero-based index of the item to remove. - /// is not a valid index in the . - /// The is read-only. - /// - public virtual void RemoveAt(int index) - { - list.RemoveAt(index); - } + /// + /// Removes the first occurrence of a specific object from the . + /// + /// + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// + /// The object to remove from the . + /// The is read-only. + /// + public virtual bool Remove(MemberMap item) + { + return list.Remove(item); + } - /// - /// Gets or sets the element at the specified index. - /// - /// - /// The element at the specified index. - /// - /// The zero-based index of the element to get or set. - /// is not a valid index in the . - /// The member is set and the is read-only. - /// - public virtual MemberMap this[int index] - { - get { return list[index]; } - set { list[index] = value; } - } + /// + /// Determines the index of a specific item in the . + /// + /// + /// The index of if found in the list; otherwise, -1. + /// + /// The object to locate in the . + /// + public virtual int IndexOf(MemberMap item) + { + return list.IndexOf(item); + } - /// - /// Finds the using the given member expression. - /// - /// The the member is on. - /// The member expression. - /// The for the given expression, or null if not found. - public virtual MemberMap Find(Expression> expression) - { - var member = ReflectionHelper.GetMember(expression); - return Find(member); - } + /// + /// Inserts an item to the at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The object to insert into the . + /// is not a valid index in the . + /// The is read-only. + /// + public virtual void Insert(int index, MemberMap item) + { + list.Insert(index, item); + } - /// - /// Finds the using the given member. - /// - /// The member. - /// The for the given expression, or null if not found. - public virtual MemberMap Find(MemberInfo member) - { - var existingMap = list.SingleOrDefault(m => - m.Data.Member == member || - m.Data.Member != null && - m.Data.Member.Name == member.Name && - ( - m.Data.Member.DeclaringType.IsAssignableFrom(member.DeclaringType) || - member.DeclaringType.IsAssignableFrom(m.Data.Member.DeclaringType) - ) - ); - - return existingMap; - } + /// + /// Removes the item at the specified index. + /// + /// The zero-based index of the item to remove. + /// is not a valid index in the . + /// The is read-only. + /// + public virtual void RemoveAt(int index) + { + list.RemoveAt(index); + } - /// - /// Adds the members from the mapping. This will recursively - /// traverse the mapping tree and add all members for - /// reference maps. - /// - /// The mapping where the members are added from. - public virtual void AddMembers(ClassMap mapping) + /// + /// Gets or sets the element at the specified index. + /// + /// + /// The element at the specified index. + /// + /// The zero-based index of the element to get or set. + /// is not a valid index in the . + /// The member is set and the is read-only. + /// + public virtual MemberMap this[int index] + { + get { return list[index]; } + set { list[index] = value; } + } + + /// + /// Finds the using the given member expression. + /// + /// The the member is on. + /// The member expression. + /// The for the given expression, or null if not found. + public virtual MemberMap? Find(Expression> expression) + { + var member = ReflectionHelper.GetMember(expression); + return Find(member); + } + + /// + /// Finds the using the given member. + /// + /// The member. + /// The for the given expression, or null if not found. + public virtual MemberMap? Find(MemberInfo member) + { + var existingMap = list.SingleOrDefault(m => + m.Data.Member == member || + m.Data.Member != null && + m.Data.Member.Name == member.Name && + ( + m.Data.Member.DeclaringType!.IsAssignableFrom(member.DeclaringType) || + member.DeclaringType!.IsAssignableFrom(m.Data.Member.DeclaringType) + ) + ); + + return existingMap; + } + + /// + /// Adds the members from the mapping. This will recursively + /// traverse the mapping tree and add all members for + /// reference maps. + /// + /// The mapping where the members are added from. + public virtual void AddMembers(ClassMap mapping) + { + AddRange(mapping.MemberMaps); + foreach (var refmap in mapping.ReferenceMaps) { - AddRange(mapping.MemberMaps); - foreach (var refmap in mapping.ReferenceMaps) - { - AddMembers(refmap.Data.Mapping); - } + AddMembers(refmap.Data.Mapping); } } } diff --git a/src/CsvHelper/Configuration/MemberMapComparer.cs b/src/CsvHelper/Configuration/MemberMapComparer.cs index 752a19e19..3449efe7a 100644 --- a/src/CsvHelper/Configuration/MemberMapComparer.cs +++ b/src/CsvHelper/Configuration/MemberMapComparer.cs @@ -2,73 +2,71 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; +namespace CsvHelper.Configuration; -namespace CsvHelper.Configuration +/// +/// Used to compare s. +/// The order is by field index ascending. Any +/// fields that don't have an index are pushed +/// to the bottom. +/// +internal class MemberMapComparer : IComparer { /// - /// Used to compare s. - /// The order is by field index ascending. Any - /// fields that don't have an index are pushed - /// to the bottom. + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. /// - internal class MemberMapComparer : IComparer + /// + /// Value + /// Condition + /// Less than zero + /// is less than . + /// Zero + /// equals . + /// Greater than zero + /// is greater than . + /// + /// The first object to compare. + /// The second object to compare. + /// Neither nor implements the interface. + /// -or- + /// and are of different types and neither one can handle comparisons with the other. + /// 2 + public virtual int Compare(object x, object y) { - /// - /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. - /// - /// - /// Value - /// Condition - /// Less than zero - /// is less than . - /// Zero - /// equals . - /// Greater than zero - /// is greater than . - /// - /// The first object to compare. - /// The second object to compare. - /// Neither nor implements the interface. - /// -or- - /// and are of different types and neither one can handle comparisons with the other. - /// 2 - public virtual int Compare( object x, object y ) + var xMember = x as MemberMap; + var yMember = y as MemberMap; + + return Compare(xMember, yMember); + } + + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// + /// Value + /// Condition + /// Less than zero + /// is less than . + /// Zero + /// equals . + /// Greater than zero + /// is greater than . + /// + /// The first object to compare. + /// The second object to compare. + /// + public virtual int Compare(MemberMap? x, MemberMap? y) + { + if (x == null) { - var xMember = x as MemberMap; - var yMember = y as MemberMap; - return Compare( xMember, yMember ); + throw new ArgumentNullException(nameof(x)); } - /// - /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. - /// - /// - /// Value - /// Condition - /// Less than zero - /// is less than . - /// Zero - /// equals . - /// Greater than zero - /// is greater than . - /// - /// The first object to compare. - /// The second object to compare. - /// - public virtual int Compare( MemberMap x, MemberMap y ) + if (y == null) { - if( x == null ) - { - throw new ArgumentNullException( nameof( x ) ); - } - if( y == null ) - { - throw new ArgumentNullException( nameof( y ) ); - } - - return x.Data.Index.CompareTo( y.Data.Index ); + throw new ArgumentNullException(nameof(y)); } + + return x.Data.Index.CompareTo(y.Data.Index); } } diff --git a/src/CsvHelper/Configuration/MemberMapData.cs b/src/CsvHelper/Configuration/MemberMapData.cs index 1473cda67..44780cbb8 100644 --- a/src/CsvHelper/Configuration/MemberMapData.cs +++ b/src/CsvHelper/Configuration/MemberMapData.cs @@ -2,166 +2,164 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Reflection; using CsvHelper.TypeConversion; using System.Linq.Expressions; -using System; +using System.Reflection; + +namespace CsvHelper.Configuration; -namespace CsvHelper.Configuration +/// +/// The configured data for the member map. +/// +public class MemberMapData { /// - /// The configured data for the member map. + /// Gets the member type. /// - public class MemberMapData + public virtual Type Type { - /// - /// Gets the member type. - /// - public virtual Type Type + get { - get + if (Member != null) { - if (Member != null) - { - return Member.MemberType(); - } - - if (IsConstantSet) - { - return Constant?.GetType() ?? typeof(string); - } - - if (IsDefaultSet) - { - return Default?.GetType() ?? typeof(string); - } - - return typeof(string); + return Member.MemberType(); } - } - /// - /// Gets the that the data - /// is associated with. - /// - public virtual MemberInfo Member { get; private set; } - - /// - /// Gets the list of column names. - /// - public virtual MemberNameCollection Names { get; } = new MemberNameCollection(); - - /// - /// Gets or sets the index of the name. - /// This is used if there are multiple - /// columns with the same names. - /// - public virtual int NameIndex { get; set; } - - /// - /// Gets or sets a value indicating if the name was - /// explicitly set. True if it was explicitly set, - /// otherwise false. - /// - public virtual bool IsNameSet { get; set; } - - /// - /// Gets or sets the column index. - /// - public virtual int Index { get; set; } = -1; - - /// - /// Gets or sets the index end. The Index end is used to specify a range for use - /// with a collection member. Index is used as the start of the range, and IndexEnd - /// is the end of the range. - /// - public virtual int IndexEnd { get; set; } = -1; - - /// - /// Gets or sets a value indicating if the index was - /// explicitly set. True if it was explicitly set, - /// otherwise false. - /// - public virtual bool IsIndexSet { get; set; } - - /// - /// Gets or sets the type converter. - /// - public virtual ITypeConverter TypeConverter { get; set; } - - /// - /// Gets or sets the type converter options. - /// - public virtual TypeConverterOptions TypeConverterOptions { get; set; } = new TypeConverterOptions(); - - /// - /// Gets or sets a value indicating whether the field should be ignored. - /// - public virtual bool Ignore { get; set; } - - /// - /// Gets or sets the default value used when a CSV field is empty. - /// - public virtual object Default { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is default value set. - /// the default value was explicitly set. True if it was - /// explicitly set, otherwise false. - /// - public virtual bool IsDefaultSet { get; set; } - - /// - /// Gets or setse a value indicating if the default value should be used when - /// a type conversion failure happens. true to use the default, otherwise - /// false. - /// - public virtual bool UseDefaultOnConversionFailure { get; set; } - - /// - /// Gets or sets the constant value used for every record. - /// - public virtual object Constant { get; set; } - - /// - /// Gets or sets a value indicating if a constant was explicitly set. - /// - public virtual bool IsConstantSet { get; set; } - - /// - /// Gets or sets the expression used to convert data in the - /// row to the member. - /// - public virtual Expression ReadingConvertExpression { get; set; } - - /// - /// Gets or sets the expression to be used to convert the object - /// to a field. - /// - public virtual Expression WritingConvertExpression { get; set; } - - /// - /// Gets or sets the expression use to validate a field. - /// - public virtual Expression ValidateExpression { get; set; } - - /// - /// Gets or sets the expression used to get the validation message when validation fails. - /// - public virtual Expression ValidateMessageExpression { get; set; } - - /// - /// Gets or sets a value indicating if a field is optional. - /// - public virtual bool IsOptional { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The member. - public MemberMapData(MemberInfo member) - { - Member = member; + if (IsConstantSet) + { + return Constant?.GetType() ?? typeof(string); + } + + if (IsDefaultSet) + { + return Default?.GetType() ?? typeof(string); + } + + return typeof(string); } } + + /// + /// Gets the that the data + /// is associated with. + /// + public virtual MemberInfo? Member { get; private set; } + + /// + /// Gets the list of column names. + /// + public virtual MemberNameCollection Names { get; } = new MemberNameCollection(); + + /// + /// Gets or sets the index of the name. + /// This is used if there are multiple + /// columns with the same names. + /// + public virtual int NameIndex { get; set; } + + /// + /// Gets or sets a value indicating if the name was + /// explicitly set. True if it was explicitly set, + /// otherwise false. + /// + public virtual bool IsNameSet { get; set; } + + /// + /// Gets or sets the column index. + /// + public virtual int Index { get; set; } = -1; + + /// + /// Gets or sets the index end. The Index end is used to specify a range for use + /// with a collection member. Index is used as the start of the range, and IndexEnd + /// is the end of the range. + /// + public virtual int IndexEnd { get; set; } = -1; + + /// + /// Gets or sets a value indicating if the index was + /// explicitly set. True if it was explicitly set, + /// otherwise false. + /// + public virtual bool IsIndexSet { get; set; } + + /// + /// Gets or sets the type converter. + /// + public virtual ITypeConverter? TypeConverter { get; set; } + + /// + /// Gets or sets the type converter options. + /// + public virtual TypeConverterOptions TypeConverterOptions { get; set; } = new TypeConverterOptions(); + + /// + /// Gets or sets a value indicating whether the field should be ignored. + /// + public virtual bool Ignore { get; set; } + + /// + /// Gets or sets the default value used when a CSV field is empty. + /// + public virtual object? Default { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is default value set. + /// the default value was explicitly set. True if it was + /// explicitly set, otherwise false. + /// + public virtual bool IsDefaultSet { get; set; } + + /// + /// Gets or setse a value indicating if the default value should be used when + /// a type conversion failure happens. true to use the default, otherwise + /// false. + /// + public virtual bool UseDefaultOnConversionFailure { get; set; } + + /// + /// Gets or sets the constant value used for every record. + /// + public virtual object? Constant { get; set; } + + /// + /// Gets or sets a value indicating if a constant was explicitly set. + /// + public virtual bool IsConstantSet { get; set; } + + /// + /// Gets or sets the expression used to convert data in the + /// row to the member. + /// + public virtual Expression? ReadingConvertExpression { get; set; } + + /// + /// Gets or sets the expression to be used to convert the object + /// to a field. + /// + public virtual Expression? WritingConvertExpression { get; set; } + + /// + /// Gets or sets the expression use to validate a field. + /// + public virtual Expression? ValidateExpression { get; set; } + + /// + /// Gets or sets the expression used to get the validation message when validation fails. + /// + public virtual Expression? ValidateMessageExpression { get; set; } + + /// + /// Gets or sets a value indicating if a field is optional. + /// + public virtual bool IsOptional { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The member. + public MemberMapData(MemberInfo? member) + { + Member = member; + } } diff --git a/src/CsvHelper/Configuration/MemberMapTypeConverterOption.cs b/src/CsvHelper/Configuration/MemberMapTypeConverterOption.cs index 0143b8944..4a2b022f5 100644 --- a/src/CsvHelper/Configuration/MemberMapTypeConverterOption.cs +++ b/src/CsvHelper/Configuration/MemberMapTypeConverterOption.cs @@ -2,166 +2,164 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Globalization; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Sets type converter options on a member map. +/// +public class MemberMapTypeConverterOption { + private readonly MemberMap memberMap; + /// - /// Sets type converter options on a member map. + /// Creates a new instance using the given . /// - public class MemberMapTypeConverterOption + /// The member map the options are being applied to. + public MemberMapTypeConverterOption(MemberMap memberMap) { - private readonly MemberMap memberMap; - - /// - /// Creates a new instance using the given . - /// - /// The member map the options are being applied to. - public MemberMapTypeConverterOption(MemberMap memberMap) - { - this.memberMap = memberMap; - } + this.memberMap = memberMap; + } - /// - /// The used when type converting. - /// This will override the global - /// setting. - /// - /// The culture info. - public virtual MemberMap CultureInfo(CultureInfo cultureInfo) - { - memberMap.Data.TypeConverterOptions.CultureInfo = cultureInfo; + /// + /// The used when type converting. + /// This will override the global + /// setting. + /// + /// The culture info. + public virtual MemberMap CultureInfo(CultureInfo cultureInfo) + { + memberMap.Data.TypeConverterOptions.CultureInfo = cultureInfo; - return memberMap; - } + return memberMap; + } - /// - /// The to use when type converting. - /// This is used when doing any conversions. - /// - /// The date time style. - public virtual MemberMap DateTimeStyles(DateTimeStyles dateTimeStyle) - { - memberMap.Data.TypeConverterOptions.DateTimeStyle = dateTimeStyle; + /// + /// The to use when type converting. + /// This is used when doing any conversions. + /// + /// The date time style. + public virtual MemberMap DateTimeStyles(DateTimeStyles dateTimeStyle) + { + memberMap.Data.TypeConverterOptions.DateTimeStyle = dateTimeStyle; - return memberMap; - } + return memberMap; + } - /// - /// The to use when type converting. - /// This is used when doing converting. - /// - /// The time span styles. - public virtual MemberMap TimespanStyles(TimeSpanStyles timeSpanStyles) - { - memberMap.Data.TypeConverterOptions.TimeSpanStyle = timeSpanStyles; + /// + /// The to use when type converting. + /// This is used when doing converting. + /// + /// The time span styles. + public virtual MemberMap TimespanStyles(TimeSpanStyles timeSpanStyles) + { + memberMap.Data.TypeConverterOptions.TimeSpanStyle = timeSpanStyles; - return memberMap; - } + return memberMap; + } - /// - /// The to use when type converting. - /// This is used when doing any number conversions. - /// - /// - public virtual MemberMap NumberStyles(NumberStyles numberStyle) - { - memberMap.Data.TypeConverterOptions.NumberStyles = numberStyle; + /// + /// The to use when type converting. + /// This is used when doing any number conversions. + /// + /// + public virtual MemberMap NumberStyles(NumberStyles numberStyle) + { + memberMap.Data.TypeConverterOptions.NumberStyles = numberStyle; - return memberMap; - } + return memberMap; + } - /// - /// The string format to be used when type converting. - /// - /// The format. - public virtual MemberMap Format(params string[] formats) - { - memberMap.Data.TypeConverterOptions.Formats = formats; + /// + /// The string format to be used when type converting. + /// + /// The format. + public virtual MemberMap Format(params string[] formats) + { + memberMap.Data.TypeConverterOptions.Formats = formats; - return memberMap; - } + return memberMap; + } - /// - /// The to use when converting. - /// This is used when doing conversions. - /// - /// Kind of the URI. - public virtual MemberMap UriKind(UriKind uriKind) - { - memberMap.Data.TypeConverterOptions.UriKind = uriKind; + /// + /// The to use when converting. + /// This is used when doing conversions. + /// + /// Kind of the URI. + public virtual MemberMap UriKind(UriKind uriKind) + { + memberMap.Data.TypeConverterOptions.UriKind = uriKind; - return memberMap; - } + return memberMap; + } - /// - /// The string values used to represent a boolean when converting. - /// - /// A value indicating whether true values or false values are being set. - /// A value indication if the current values should be cleared before adding the new ones. - /// The string boolean values. - public virtual MemberMap BooleanValues(bool isTrue, bool clearValues = true, params string[] booleanValues) + /// + /// The string values used to represent a boolean when converting. + /// + /// A value indicating whether true values or false values are being set. + /// A value indication if the current values should be cleared before adding the new ones. + /// The string boolean values. + public virtual MemberMap BooleanValues(bool isTrue, bool clearValues = true, params string[] booleanValues) + { + if (isTrue) { - if (isTrue) - { - if (clearValues) - { - memberMap.Data.TypeConverterOptions.BooleanTrueValues.Clear(); - } - - memberMap.Data.TypeConverterOptions.BooleanTrueValues.AddRange(booleanValues); - } - else + if (clearValues) { - if (clearValues) - { - memberMap.Data.TypeConverterOptions.BooleanFalseValues.Clear(); - } - - memberMap.Data.TypeConverterOptions.BooleanFalseValues.AddRange(booleanValues); + memberMap.Data.TypeConverterOptions.BooleanTrueValues.Clear(); } - return memberMap; + memberMap.Data.TypeConverterOptions.BooleanTrueValues.AddRange(booleanValues); } - - /// - /// The string values used to represent null when converting. - /// - /// The values that represent null. - /// - public virtual MemberMap NullValues(params string[] nullValues) - { - return NullValues(true, nullValues); - } - - /// - /// The string values used to represent null when converting. - /// - /// A value indication if the current values should be cleared before adding the new ones. - /// The values that represent null. - /// - public virtual MemberMap NullValues(bool clearValues, params string[] nullValues) + else { if (clearValues) { - memberMap.Data.TypeConverterOptions.NullValues.Clear(); + memberMap.Data.TypeConverterOptions.BooleanFalseValues.Clear(); } - memberMap.Data.TypeConverterOptions.NullValues.AddRange(nullValues); - - return memberMap; + memberMap.Data.TypeConverterOptions.BooleanFalseValues.AddRange(booleanValues); } - /// - /// Ignore case when parsing enums. - /// - /// true to ignore case, otherwise false. - public virtual MemberMap EnumIgnoreCase(bool ignoreCase = true) - { - memberMap.Data.TypeConverterOptions.EnumIgnoreCase = ignoreCase; + return memberMap; + } - return memberMap; + /// + /// The string values used to represent null when converting. + /// + /// The values that represent null. + /// + public virtual MemberMap NullValues(params string[] nullValues) + { + return NullValues(true, nullValues); + } + + /// + /// The string values used to represent null when converting. + /// + /// A value indication if the current values should be cleared before adding the new ones. + /// The values that represent null. + /// + public virtual MemberMap NullValues(bool clearValues, params string[] nullValues) + { + if (clearValues) + { + memberMap.Data.TypeConverterOptions.NullValues.Clear(); } + + memberMap.Data.TypeConverterOptions.NullValues.AddRange(nullValues); + + return memberMap; + } + + /// + /// Ignore case when parsing enums. + /// + /// true to ignore case, otherwise false. + public virtual MemberMap EnumIgnoreCase(bool ignoreCase = true) + { + memberMap.Data.TypeConverterOptions.EnumIgnoreCase = ignoreCase; + + return memberMap; } } diff --git a/src/CsvHelper/Configuration/MemberMap`1.cs b/src/CsvHelper/Configuration/MemberMap`1.cs index 720b5cbe1..32b514579 100644 --- a/src/CsvHelper/Configuration/MemberMap`1.cs +++ b/src/CsvHelper/Configuration/MemberMap`1.cs @@ -8,263 +8,267 @@ using System.Linq.Expressions; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Mapping info for a member to a CSV field. +/// +public class MemberMap : MemberMap { + private MemberMapTypeConverterOption typeConverterOption; + + /// + public override MemberMapTypeConverterOption TypeConverterOption => typeConverterOption; + /// - /// Mapping info for a member to a CSV field. + /// Creates a new instance using the specified member. /// - public class MemberMap : MemberMap + public MemberMap(MemberInfo? member) { - /// - /// Creates a new instance using the specified member. - /// - public MemberMap(MemberInfo member) - { - TypeConverterOption = new MemberMapTypeConverterOption(this); + typeConverterOption = new MemberMapTypeConverterOption(this); - Data = new MemberMapData(member); - } + Data = new MemberMapData(member); + } - /// - /// When reading, is used to get the field - /// at the index of the name if there was a - /// header specified. It will look for the - /// first name match in the order listed. - /// When writing, sets the name of the - /// field in the header record. - /// The first name will be used. - /// - /// The possible names of the CSV field. - public new virtual MemberMap Name(params string[] names) + /// + /// When reading, is used to get the field + /// at the index of the name if there was a + /// header specified. It will look for the + /// first name match in the order listed. + /// When writing, sets the name of the + /// field in the header record. + /// The first name will be used. + /// + /// The possible names of the CSV field. + public new virtual MemberMap Name(params string[] names) + { + if (names == null || names.Length == 0) { - if (names == null || names.Length == 0) - { - throw new ArgumentNullException(nameof(names)); - } + throw new ArgumentNullException(nameof(names)); + } - Data.Names.Clear(); - Data.Names.AddRange(names); - Data.IsNameSet = true; + Data.Names.Clear(); + Data.Names.AddRange(names); + Data.IsNameSet = true; - return this; - } + return this; + } - /// - /// When reading, is used to get the - /// index of the name used when there - /// are multiple names that are the same. - /// - /// The index of the name. - public new virtual MemberMap NameIndex(int index) - { - Data.NameIndex = index; + /// + /// When reading, is used to get the + /// index of the name used when there + /// are multiple names that are the same. + /// + /// The index of the name. + public new virtual MemberMap NameIndex(int index) + { + Data.NameIndex = index; - return this; - } + return this; + } - /// - /// When reading, is used to get the field at - /// the given index. When writing, the fields - /// will be written in the order of the field - /// indexes. - /// - /// The index of the CSV field. - /// The end index used when mapping to an member. - public new virtual MemberMap Index(int index, int indexEnd = -1) - { - Data.Index = index; - Data.IsIndexSet = true; - Data.IndexEnd = indexEnd; + /// + /// When reading, is used to get the field at + /// the given index. When writing, the fields + /// will be written in the order of the field + /// indexes. + /// + /// The index of the CSV field. + /// The end index used when mapping to an member. + public new virtual MemberMap Index(int index, int indexEnd = -1) + { + Data.Index = index; + Data.IsIndexSet = true; + Data.IndexEnd = indexEnd; - return this; - } + return this; + } - /// - /// Ignore the member when reading and writing. - /// If this member has already been mapped as a reference - /// member, either by a class map, or by automapping, calling - /// this method will not ignore all the child members down the - /// tree that have already been mapped. - /// - public new virtual MemberMap Ignore() - { - Data.Ignore = true; + /// + /// Ignore the member when reading and writing. + /// If this member has already been mapped as a reference + /// member, either by a class map, or by automapping, calling + /// this method will not ignore all the child members down the + /// tree that have already been mapped. + /// + public new virtual MemberMap Ignore() + { + Data.Ignore = true; - return this; - } + return this; + } - /// - /// Ignore the member when reading and writing. - /// If this member has already been mapped as a reference - /// member, either by a class map, or by automapping, calling - /// this method will not ignore all the child members down the - /// tree that have already been mapped. - /// - /// True to ignore, otherwise false. - public new virtual MemberMap Ignore(bool ignore) - { - Data.Ignore = ignore; + /// + /// Ignore the member when reading and writing. + /// If this member has already been mapped as a reference + /// member, either by a class map, or by automapping, calling + /// this method will not ignore all the child members down the + /// tree that have already been mapped. + /// + /// True to ignore, otherwise false. + public new virtual MemberMap Ignore(bool ignore) + { + Data.Ignore = ignore; - return this; - } + return this; + } - /// - /// The default value that will be used when reading when - /// the CSV field is empty. - /// - /// The default value. - /// Use default on conversion failure. - public virtual MemberMap Default(TMember defaultValue, bool useOnConversionFailure = false) - { - Data.Default = defaultValue; - Data.IsDefaultSet = true; - Data.UseDefaultOnConversionFailure = useOnConversionFailure; + /// + /// The default value that will be used when reading when + /// the CSV field is empty. + /// + /// The default value. + /// Use default on conversion failure. + public virtual MemberMap Default(TMember defaultValue, bool useOnConversionFailure = false) + { + Data.Default = defaultValue; + Data.IsDefaultSet = true; + Data.UseDefaultOnConversionFailure = useOnConversionFailure; - return this; - } + return this; + } - /// - /// The default value that will be used when reading when - /// the CSV field is empty. This value is not type checked - /// and will use a to convert - /// the field. This could potentially have runtime errors. - /// - /// The default value. - /// Use default on conversion failure. - public virtual MemberMap Default(string defaultValue, bool useOnConversionFailure = false) - { - Data.Default = defaultValue; - Data.IsDefaultSet = true; - Data.UseDefaultOnConversionFailure = useOnConversionFailure; + /// + /// The default value that will be used when reading when + /// the CSV field is empty. This value is not type checked + /// and will use a to convert + /// the field. This could potentially have runtime errors. + /// + /// The default value. + /// Use default on conversion failure. + public virtual MemberMap Default(string defaultValue, bool useOnConversionFailure = false) + { + Data.Default = defaultValue; + Data.IsDefaultSet = true; + Data.UseDefaultOnConversionFailure = useOnConversionFailure; - return this; - } + return this; + } - /// - /// The constant value that will be used for every record when - /// reading and writing. This value will always be used no matter - /// what other mapping configurations are specified. - /// - /// The constant value. - public virtual MemberMap Constant(TMember constantValue) - { - Data.Constant = constantValue; - Data.IsConstantSet = true; + /// + /// The constant value that will be used for every record when + /// reading and writing. This value will always be used no matter + /// what other mapping configurations are specified. + /// + /// The constant value. + public virtual MemberMap Constant(TMember constantValue) + { + Data.Constant = constantValue; + Data.IsConstantSet = true; - return this; - } + return this; + } - /// - /// Specifies the to use - /// when converting the member to and from a CSV field. - /// - /// The TypeConverter to use. - public new virtual MemberMap TypeConverter(ITypeConverter typeConverter) - { - Data.TypeConverter = typeConverter; + /// + /// Specifies the to use + /// when converting the member to and from a CSV field. + /// + /// The TypeConverter to use. + public new virtual MemberMap TypeConverter(ITypeConverter typeConverter) + { + Data.TypeConverter = typeConverter; - return this; - } + return this; + } - /// - /// Specifies the to use - /// when converting the member to and from a CSV field. - /// - /// The of the - /// to use. - public new virtual MemberMap TypeConverter() where TConverter : ITypeConverter - { - TypeConverter(ObjectResolver.Current.Resolve()); + /// + /// Specifies the to use + /// when converting the member to and from a CSV field. + /// + /// The of the + /// to use. + public new virtual MemberMap TypeConverter() where TConverter : ITypeConverter + { + TypeConverter(ObjectResolver.Current.Resolve()); - return this; - } + return this; + } - /// - /// Specifies an expression to be used to convert data in the - /// row to the member. - /// - /// The convert expression. - public virtual MemberMap Convert(ConvertFromString convertFromStringFunction) - { - var instance = convertFromStringFunction.Target != null ? Expression.Constant(convertFromStringFunction.Target) : null; - var fieldParameter = Expression.Parameter(typeof(ConvertFromStringArgs), "args"); - var methodExpression = Expression.Call - ( - instance, - convertFromStringFunction.Method, - fieldParameter - ); - var lambdaExpression = Expression.Lambda>(methodExpression, fieldParameter); - - Data.ReadingConvertExpression = lambdaExpression; - - return this; - } + /// + /// Specifies an expression to be used to convert data in the + /// row to the member. + /// + /// The convert expression. + public virtual MemberMap Convert(ConvertFromString convertFromStringFunction) + { + var instance = convertFromStringFunction.Target != null ? Expression.Constant(convertFromStringFunction.Target) : null; + var fieldParameter = Expression.Parameter(typeof(ConvertFromStringArgs), "args"); + var methodExpression = Expression.Call + ( + instance, + convertFromStringFunction.Method, + fieldParameter + ); + var lambdaExpression = Expression.Lambda>(methodExpression, fieldParameter); + + Data.ReadingConvertExpression = lambdaExpression; + + return this; + } - /// - /// Specifies an expression to be used to convert the object - /// to a field. - /// - /// The convert expression. - public virtual MemberMap Convert(ConvertToString convertToStringFunction) - { - var instance = convertToStringFunction.Target != null ? Expression.Constant(convertToStringFunction.Target) : null; - var fieldParameter = Expression.Parameter(typeof(ConvertToStringArgs), "args"); - var methodExpression = Expression.Call - ( - instance, - convertToStringFunction.Method, - fieldParameter - ); - var lambdaExpression = Expression.Lambda>(methodExpression, fieldParameter); - - Data.WritingConvertExpression = lambdaExpression; - - return this; - } + /// + /// Specifies an expression to be used to convert the object + /// to a field. + /// + /// The convert expression. + public virtual MemberMap Convert(ConvertToString convertToStringFunction) + { + var instance = convertToStringFunction.Target != null ? Expression.Constant(convertToStringFunction.Target) : null; + var fieldParameter = Expression.Parameter(typeof(ConvertToStringArgs), "args"); + var methodExpression = Expression.Call + ( + instance, + convertToStringFunction.Method, + fieldParameter + ); + var lambdaExpression = Expression.Lambda>(methodExpression, fieldParameter); + + Data.WritingConvertExpression = lambdaExpression; + + return this; + } - /// - /// Ignore the member when reading if no matching field name can be found. - /// - public new virtual MemberMap Optional() - { - Data.IsOptional = true; + /// + /// Ignore the member when reading if no matching field name can be found. + /// + public new virtual MemberMap Optional() + { + Data.IsOptional = true; - return this; - } + return this; + } - /// - /// Specifies an expression to be used to validate a field when reading. - /// - /// - public new virtual MemberMap Validate(Validate validateExpression) - { - return Validate(validateExpression, args => $"Field '{args.Field}' is not valid."); - } + /// + /// Specifies an expression to be used to validate a field when reading. + /// + /// + public new virtual MemberMap Validate(Validate validateExpression) + { + return Validate(validateExpression, args => $"Field '{args.Field}' is not valid."); + } - /// - /// Specifies an expression to be used to validate a field when reading along with specified exception message. - /// - /// - /// - public new virtual MemberMap Validate(Validate validateExpression, ValidateMessage validateMessageExpression) - { - var fieldParameter = Expression.Parameter(typeof(ValidateArgs), "args"); - var validateCallExpression = Expression.Call( - Expression.Constant(validateExpression.Target), - validateExpression.Method, - fieldParameter - ); - var messageCallExpression = Expression.Call( - Expression.Constant(validateMessageExpression.Target), - validateMessageExpression.Method, - fieldParameter - ); - - Data.ValidateExpression = Expression.Lambda(validateCallExpression, fieldParameter); - Data.ValidateMessageExpression = Expression.Lambda(messageCallExpression, fieldParameter); - - return this; - } + /// + /// Specifies an expression to be used to validate a field when reading along with specified exception message. + /// + /// + /// + public new virtual MemberMap Validate(Validate validateExpression, ValidateMessage validateMessageExpression) + { + var fieldParameter = Expression.Parameter(typeof(ValidateArgs), "args"); + var validateCallExpression = Expression.Call( + Expression.Constant(validateExpression.Target), + validateExpression.Method, + fieldParameter + ); + var messageCallExpression = Expression.Call( + Expression.Constant(validateMessageExpression.Target), + validateMessageExpression.Method, + fieldParameter + ); + + Data.ValidateExpression = Expression.Lambda(validateCallExpression, fieldParameter); + Data.ValidateMessageExpression = Expression.Lambda(messageCallExpression, fieldParameter); + + return this; } } diff --git a/src/CsvHelper/Configuration/MemberNameCollection.cs b/src/CsvHelper/Configuration/MemberNameCollection.cs index 7271ff1d8..39c8b6a13 100644 --- a/src/CsvHelper/Configuration/MemberNameCollection.cs +++ b/src/CsvHelper/Configuration/MemberNameCollection.cs @@ -3,96 +3,94 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using System.Collections; -using System.Collections.Generic; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// A collection that holds member names. +/// +public class MemberNameCollection : IEnumerable { + private readonly List names = new List(); + /// - /// A collection that holds member names. + /// Gets the name at the given index. If a prefix is set, + /// it will be prepended to the name. /// - public class MemberNameCollection : IEnumerable + /// + /// + public string this[int index] { - private readonly List names = new List(); - - /// - /// Gets the name at the given index. If a prefix is set, - /// it will be prepended to the name. - /// - /// - /// - public string this[int index] - { - get { return Prefix + names[index]; } - set { names[index] = value; } - } + get { return Prefix + names[index]; } + set { names[index] = value; } + } - /// - /// Gets the prefix to use for each name. - /// - public string Prefix { get; set; } + /// + /// Gets the prefix to use for each name. + /// + public string Prefix { get; set; } = string.Empty; - /// - /// Gets the raw list of names without - /// the prefix being prepended. - /// - public List Names => names; + /// + /// Gets the raw list of names without + /// the prefix being prepended. + /// + public List Names => names; - /// - /// Gets the count. - /// - public int Count => names.Count; + /// + /// Gets the count. + /// + public int Count => names.Count; - /// - /// Adds the given name to the collection. - /// - /// The name to add. - public void Add(string name) - { - names.Add(name); - } + /// + /// Adds the given name to the collection. + /// + /// The name to add. + public void Add(string name) + { + names.Add(name); + } - /// - /// Clears all names from the collection. - /// - public void Clear() - { - names.Clear(); - } + /// + /// Clears all names from the collection. + /// + public void Clear() + { + names.Clear(); + } - /// - /// Adds a range of names to the collection. - /// - /// The range to add. - public void AddRange(IEnumerable names) - { - this.names.AddRange(names); - } + /// + /// Adds a range of names to the collection. + /// + /// The range to add. + public void AddRange(IEnumerable names) + { + this.names.AddRange(names); + } - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - /// 1 - public IEnumerator GetEnumerator() + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// 1 + public IEnumerator GetEnumerator() + { + for (var i = 0; i < names.Count; i++) { - for (var i = 0; i < names.Count; i++) - { - yield return this[i]; - } + yield return this[i]; } + } - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - /// 2 - IEnumerator IEnumerable.GetEnumerator() - { - return names.GetEnumerator(); - } + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + /// 2 + IEnumerator IEnumerable.GetEnumerator() + { + return names.GetEnumerator(); } } diff --git a/src/CsvHelper/Configuration/MemberReferenceMap.cs b/src/CsvHelper/Configuration/MemberReferenceMap.cs index 86b2bd625..a117d0e4e 100644 --- a/src/CsvHelper/Configuration/MemberReferenceMap.cs +++ b/src/CsvHelper/Configuration/MemberReferenceMap.cs @@ -2,67 +2,65 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Diagnostics; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Mapping info for a reference member mapping to a class. +/// +[DebuggerDisplay("Member = {Data.Member}, Prefix = {Data.Prefix}")] +public class MemberReferenceMap { + private readonly MemberReferenceMapData data; + /// - /// Mapping info for a reference member mapping to a class. + /// Gets the member reference map data. /// - [DebuggerDisplay("Member = {Data.Member}, Prefix = {Data.Prefix}")] - public class MemberReferenceMap - { - private readonly MemberReferenceMapData data; + public MemberReferenceMapData Data => data; - /// - /// Gets the member reference map data. - /// - public MemberReferenceMapData Data => data; - - /// - /// Initializes a new instance of the class. - /// - /// The member. - /// The to use for the reference map. - public MemberReferenceMap(MemberInfo member, ClassMap mapping) + /// + /// Initializes a new instance of the class. + /// + /// The member. + /// The to use for the reference map. + public MemberReferenceMap(MemberInfo member, ClassMap mapping) + { + if (mapping == null) { - if (mapping == null) - { - throw new ArgumentNullException(nameof(mapping)); - } - - data = new MemberReferenceMapData(member, mapping); + throw new ArgumentNullException(nameof(mapping)); } - /// - /// Appends a prefix to the header of each field of the reference member. - /// - /// The prefix to be prepended to headers of each reference member. - /// Inherit parent prefixes. - /// The current - public MemberReferenceMap Prefix(string prefix = null, bool inherit = false) + data = new MemberReferenceMapData(member, mapping); + } + + /// + /// Appends a prefix to the header of each field of the reference member. + /// + /// The prefix to be prepended to headers of each reference member. + /// Inherit parent prefixes. + /// The current + public MemberReferenceMap Prefix(string? prefix = null, bool inherit = false) + { + if (string.IsNullOrEmpty(prefix)) { - if (string.IsNullOrEmpty(prefix)) - { - prefix = data.Member.Name + "."; - } + prefix = data.Member.Name + "."; + } - data.Inherit = inherit; - data.Prefix = prefix; + data.Inherit = inherit; + data.Prefix = prefix!; - return this; - } + return this; + } - /// - /// Get the largest index for the - /// members and references. - /// - /// The max index. - internal int GetMaxIndex() - { - return data.Mapping.GetMaxIndex(); - } + /// + /// Get the largest index for the + /// members and references. + /// + /// The max index. + internal int GetMaxIndex() + { + return data.Mapping.GetMaxIndex(); } } diff --git a/src/CsvHelper/Configuration/MemberReferenceMapCollection.cs b/src/CsvHelper/Configuration/MemberReferenceMapCollection.cs index a03dee8e1..8c78921b6 100644 --- a/src/CsvHelper/Configuration/MemberReferenceMapCollection.cs +++ b/src/CsvHelper/Configuration/MemberReferenceMapCollection.cs @@ -2,163 +2,159 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// A collection that holds 's. +/// +[DebuggerDisplay("Count = {list.Count}")] +public class MemberReferenceMapCollection : IList { + private readonly List list = new List(); + + /// Gets the number of elements contained in the . + /// The number of elements contained in the . + public virtual int Count => list.Count; + + /// Gets a value indicating whether the is read-only. + /// true if the is read-only; otherwise, false. + public virtual bool IsReadOnly => false; + + /// Gets or sets the element at the specified index. + /// The element at the specified index. + /// The zero-based index of the element to get or set. + /// + /// is not a valid index in the . + /// The member is set and the is read-only. + public virtual MemberReferenceMap this[int index] + { + get => list[index]; + set => list[index] = value; + } + + /// Returns an enumerator that iterates through the collection. + /// A that can be used to iterate through the collection. + /// 1 + public virtual IEnumerator GetEnumerator() + { + return list.GetEnumerator(); + } + + /// Returns an enumerator that iterates through a collection. + /// An object that can be used to iterate through the collection. + /// 2 + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// Adds an item to the . + /// The object to add to the . + /// The is read-only. + public virtual void Add(MemberReferenceMap item) + { + list.Add(item); + } + + /// Removes all items from the . + /// The is read-only. + public virtual void Clear() + { + list.Clear(); + } + + /// Determines whether the contains a specific value. + /// true if is found in the ; otherwise, false. + /// The object to locate in the . + public virtual bool Contains(MemberReferenceMap item) + { + return list.Contains(item); + } + + /// Copies the elements of the to an , starting at a particular index. + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + /// The zero-based index in at which copying begins. + /// + /// is null. + /// + /// is less than 0. + /// The number of elements in the source is greater than the available space from to the end of the destination . + public virtual void CopyTo(MemberReferenceMap[] array, int arrayIndex) + { + list.CopyTo(array, arrayIndex); + } + + /// Removes the first occurrence of a specific object from the . + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// The object to remove from the . + /// The is read-only. + public virtual bool Remove(MemberReferenceMap item) + { + return list.Remove(item); + } + + /// Determines the index of a specific item in the . + /// The index of if found in the list; otherwise, -1. + /// The object to locate in the . + public virtual int IndexOf(MemberReferenceMap item) + { + return list.IndexOf(item); + } + + /// Inserts an item to the at the specified index. + /// The zero-based index at which should be inserted. + /// The object to insert into the . + /// + /// is not a valid index in the . + /// The is read-only. + public virtual void Insert(int index, MemberReferenceMap item) + { + list.Insert(index, item); + } + + /// Removes the item at the specified index. + /// The zero-based index of the item to remove. + /// + /// is not a valid index in the . + /// The is read-only. + public virtual void RemoveAt(int index) + { + list.RemoveAt(index); + } + + /// + /// Finds the using the given member expression. + /// + /// The the member is on. + /// The member expression. + /// The for the given expression, or null if not found. + public virtual MemberReferenceMap? Find(Expression> expression) + { + var member = ReflectionHelper.GetMember(expression); + return Find(member); + } + /// - /// A collection that holds 's. + /// Finds the using the given member. /// - [DebuggerDisplay("Count = {list.Count}")] - public class MemberReferenceMapCollection : IList + /// The member. + /// The for the given expression, or null if not found. + public virtual MemberReferenceMap? Find(MemberInfo member) { - private readonly List list = new List(); - - /// Gets the number of elements contained in the . - /// The number of elements contained in the . - public virtual int Count => list.Count; - - /// Gets a value indicating whether the is read-only. - /// true if the is read-only; otherwise, false. - public virtual bool IsReadOnly => false; - - /// Gets or sets the element at the specified index. - /// The element at the specified index. - /// The zero-based index of the element to get or set. - /// - /// is not a valid index in the . - /// The member is set and the is read-only. - public virtual MemberReferenceMap this[int index] - { - get => list[index]; - set => list[index] = value; - } - - /// Returns an enumerator that iterates through the collection. - /// A that can be used to iterate through the collection. - /// 1 - public virtual IEnumerator GetEnumerator() - { - return list.GetEnumerator(); - } - - /// Returns an enumerator that iterates through a collection. - /// An object that can be used to iterate through the collection. - /// 2 - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// Adds an item to the . - /// The object to add to the . - /// The is read-only. - public virtual void Add(MemberReferenceMap item) - { - list.Add(item); - } - - /// Removes all items from the . - /// The is read-only. - public virtual void Clear() - { - list.Clear(); - } - - /// Determines whether the contains a specific value. - /// true if is found in the ; otherwise, false. - /// The object to locate in the . - public virtual bool Contains(MemberReferenceMap item) - { - return list.Contains(item); - } - - /// Copies the elements of the to an , starting at a particular index. - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. - /// The zero-based index in at which copying begins. - /// - /// is null. - /// - /// is less than 0. - /// The number of elements in the source is greater than the available space from to the end of the destination . - public virtual void CopyTo(MemberReferenceMap[] array, int arrayIndex) - { - list.CopyTo(array, arrayIndex); - } - - /// Removes the first occurrence of a specific object from the . - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - /// The object to remove from the . - /// The is read-only. - public virtual bool Remove(MemberReferenceMap item) - { - return list.Remove(item); - } - - /// Determines the index of a specific item in the . - /// The index of if found in the list; otherwise, -1. - /// The object to locate in the . - public virtual int IndexOf(MemberReferenceMap item) - { - return list.IndexOf(item); - } - - /// Inserts an item to the at the specified index. - /// The zero-based index at which should be inserted. - /// The object to insert into the . - /// - /// is not a valid index in the . - /// The is read-only. - public virtual void Insert(int index, MemberReferenceMap item) - { - list.Insert(index, item); - } - - /// Removes the item at the specified index. - /// The zero-based index of the item to remove. - /// - /// is not a valid index in the . - /// The is read-only. - public virtual void RemoveAt(int index) - { - list.RemoveAt(index); - } - - /// - /// Finds the using the given member expression. - /// - /// The the member is on. - /// The member expression. - /// The for the given expression, or null if not found. - public virtual MemberReferenceMap Find(Expression> expression) - { - var member = ReflectionHelper.GetMember(expression); - return Find(member); - } - - /// - /// Finds the using the given member. - /// - /// The member. - /// The for the given expression, or null if not found. - public virtual MemberReferenceMap Find(MemberInfo member) - { - var existingMap = list.SingleOrDefault(m => - m.Data.Member == member || - m.Data.Member.Name == member.Name && - ( - m.Data.Member.DeclaringType.IsAssignableFrom(member.DeclaringType) || - member.DeclaringType.IsAssignableFrom(m.Data.Member.DeclaringType) - ) - ); - - return existingMap; - } + var existingMap = list.SingleOrDefault(m => + m.Data.Member == member || + m.Data.Member.Name == member.Name && + ( + m.Data.Member.DeclaringType!.IsAssignableFrom(member.DeclaringType) || + member.DeclaringType!.IsAssignableFrom(m.Data.Member.DeclaringType) + ) + ); + + return existingMap; } } diff --git a/src/CsvHelper/Configuration/MemberReferenceMapData.cs b/src/CsvHelper/Configuration/MemberReferenceMapData.cs index 20e35a449..87723fff5 100644 --- a/src/CsvHelper/Configuration/MemberReferenceMapData.cs +++ b/src/CsvHelper/Configuration/MemberReferenceMapData.cs @@ -4,65 +4,64 @@ // https://github.com/JoshClose/CsvHelper using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// The configuration data for the reference map. +/// +public class MemberReferenceMapData { + private string prefix = string.Empty; + /// - /// The configuration data for the reference map. + /// Gets or sets the header prefix to use. /// - public class MemberReferenceMapData + public virtual string Prefix { - private string prefix; - - /// - /// Gets or sets the header prefix to use. - /// - public virtual string Prefix + get { return prefix; } + set { - get { return prefix; } - set + prefix = value; + foreach (var memberMap in Mapping.MemberMaps) { - prefix = value; - foreach (var memberMap in Mapping.MemberMaps) - { - memberMap.Data.Names.Prefix = value; - } + memberMap.Data.Names.Prefix = value; + } - if (Inherit) + if (Inherit) + { + foreach (var memberRef in Mapping.ReferenceMaps) { - foreach (var memberRef in Mapping.ReferenceMaps) - { - memberRef.Data.Prefix = memberRef.Data.Prefix == null ? value : string.Concat(value, memberRef.Data.Prefix); - } + memberRef.Data.Prefix = memberRef.Data.Prefix == null ? value : string.Concat(value, memberRef.Data.Prefix); } } } + } - /// - /// Gets or sets a value indicating if a prefix should inherit its parent. - /// true to inherit, otherwise false. - /// - public virtual bool Inherit { get; set; } + /// + /// Gets or sets a value indicating if a prefix should inherit its parent. + /// true to inherit, otherwise false. + /// + public virtual bool Inherit { get; set; } - /// - /// Gets the that the data - /// is associated with. - /// - public virtual MemberInfo Member { get; private set; } + /// + /// Gets the that the data + /// is associated with. + /// + public virtual MemberInfo Member { get; private set; } - /// - /// Gets the mapping this is a reference for. - /// - public ClassMap Mapping { get; private set; } + /// + /// Gets the mapping this is a reference for. + /// + public ClassMap Mapping { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The member. - /// The mapping this is a reference for. - public MemberReferenceMapData(MemberInfo member, ClassMap mapping) - { - Member = member; - Mapping = mapping; - } + /// + /// Initializes a new instance of the class. + /// + /// The member. + /// The mapping this is a reference for. + public MemberReferenceMapData(MemberInfo member, ClassMap mapping) + { + Member = member; + Mapping = mapping; } } diff --git a/src/CsvHelper/Configuration/MemberTypes.cs b/src/CsvHelper/Configuration/MemberTypes.cs index b512f6bba..03a22c77b 100644 --- a/src/CsvHelper/Configuration/MemberTypes.cs +++ b/src/CsvHelper/Configuration/MemberTypes.cs @@ -4,29 +4,28 @@ // https://github.com/JoshClose/CsvHelper using System; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Flags for the type of members that +/// can be used for auto mapping. +/// +[Flags] +public enum MemberTypes { /// - /// Flags for the type of members that - /// can be used for auto mapping. + /// No members. This is not a valid value + /// and will cause an exception if used. /// - [Flags] - public enum MemberTypes - { - /// - /// No members. This is not a valid value - /// and will cause an exception if used. - /// - None = 0, + None = 0, - /// - /// Properties on a class. - /// - Properties = 1, + /// + /// Properties on a class. + /// + Properties = 1, - /// - /// Fields on a class. - /// - Fields = 2 - } + /// + /// Fields on a class. + /// + Fields = 2 } diff --git a/src/CsvHelper/Configuration/ParameterMap.cs b/src/CsvHelper/Configuration/ParameterMap.cs index 673d9f78a..2578c372d 100644 --- a/src/CsvHelper/Configuration/ParameterMap.cs +++ b/src/CsvHelper/Configuration/ParameterMap.cs @@ -3,210 +3,208 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.TypeConversion; -using System; using System.Diagnostics; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Mapping for a constructor parameter. +/// This may contain value type data, a constructor type map, +/// or a reference map, depending on the type of the parameter. +/// +[DebuggerDisplay("Data = {Data}")] +public class ParameterMap { /// - /// Mapping for a constructor parameter. - /// This may contain value type data, a constructor type map, - /// or a reference map, depending on the type of the parameter. + /// Gets the parameter map data. /// - [DebuggerDisplay("Data = {Data}")] - public class ParameterMap - { - /// - /// Gets the parameter map data. - /// - public virtual ParameterMapData Data { get; protected set; } - - /// - /// Type converter options. - /// - public virtual ParameterMapTypeConverterOption TypeConverterOption { get; protected set; } - - /// - /// Gets or sets the map for a constructor type. - /// - public virtual ClassMap ConstructorTypeMap { get; set; } - - /// - /// Gets or sets the map for a reference type. - /// - public virtual ParameterReferenceMap ReferenceMap { get; set; } - - /// - /// Creates an instance of using - /// the given information. - /// - /// The parameter being mapped. - public ParameterMap(ParameterInfo parameter) - { - TypeConverterOption = new ParameterMapTypeConverterOption(this); + public virtual ParameterMapData Data { get; protected set; } - Data = new ParameterMapData(parameter); - } + /// + /// Type converter options. + /// + public virtual ParameterMapTypeConverterOption TypeConverterOption { get; protected set; } - /// - /// When reading, is used to get the field - /// at the index of the name if there was a - /// header specified. It will look for the - /// first name match in the order listed. - /// When writing, sets the name of the - /// field in the header record. - /// The first name will be used. - /// - /// The possible names of the CSV field. - public virtual ParameterMap Name(params string[] names) - { - if (names == null || names.Length == 0) - { - throw new ArgumentNullException(nameof(names)); - } + /// + /// Gets or sets the map for a constructor type. + /// + public virtual ClassMap? ConstructorTypeMap { get; set; } - Data.Names.Clear(); - Data.Names.AddRange(names); - Data.IsNameSet = true; + /// + /// Gets or sets the map for a reference type. + /// + public virtual ParameterReferenceMap? ReferenceMap { get; set; } - return this; - } + /// + /// Creates an instance of using + /// the given information. + /// + /// The parameter being mapped. + public ParameterMap(ParameterInfo parameter) + { + TypeConverterOption = new ParameterMapTypeConverterOption(this); - /// - /// When reading, is used to get the - /// index of the name used when there - /// are multiple names that are the same. - /// - /// The index of the name. - public virtual ParameterMap NameIndex(int index) - { - Data.NameIndex = index; + Data = new ParameterMapData(parameter); + } - return this; + /// + /// When reading, is used to get the field + /// at the index of the name if there was a + /// header specified. It will look for the + /// first name match in the order listed. + /// When writing, sets the name of the + /// field in the header record. + /// The first name will be used. + /// + /// The possible names of the CSV field. + public virtual ParameterMap Name(params string[] names) + { + if (names == null || names.Length == 0) + { + throw new ArgumentNullException(nameof(names)); } - /// - /// When reading, is used to get the field at - /// the given index. When writing, the fields - /// will be written in the order of the field - /// indexes. - /// - /// The index of the CSV field. - public virtual ParameterMap Index(int index) - { - Data.Index = index; - Data.IsIndexSet = true; + Data.Names.Clear(); + Data.Names.AddRange(names); + Data.IsNameSet = true; - return this; - } + return this; + } - /// - /// Ignore the parameter when reading and writing. - /// - public virtual ParameterMap Ignore() - { - Data.Ignore = true; + /// + /// When reading, is used to get the + /// index of the name used when there + /// are multiple names that are the same. + /// + /// The index of the name. + public virtual ParameterMap NameIndex(int index) + { + Data.NameIndex = index; - return this; - } + return this; + } - /// - /// Ignore the parameter when reading and writing. - /// - /// True to ignore, otherwise false. - public virtual ParameterMap Ignore(bool ignore) - { - Data.Ignore = ignore; + /// + /// When reading, is used to get the field at + /// the given index. When writing, the fields + /// will be written in the order of the field + /// indexes. + /// + /// The index of the CSV field. + public virtual ParameterMap Index(int index) + { + Data.Index = index; + Data.IsIndexSet = true; - return this; - } + return this; + } - /// - /// The default value that will be used when reading when - /// the CSV field is empty. - /// - /// The default value. - public virtual ParameterMap Default(object defaultValue) - { - if (defaultValue == null && Data.Parameter.ParameterType.IsValueType) - { - throw new ArgumentException($"Parameter of type '{Data.Parameter.ParameterType.FullName}' can't have a default value of null."); - } + /// + /// Ignore the parameter when reading and writing. + /// + public virtual ParameterMap Ignore() + { + Data.Ignore = true; - if (defaultValue != null && defaultValue.GetType() != Data.Parameter.ParameterType) - { - throw new ArgumentException($"Default of type '{defaultValue.GetType().FullName}' does not match parameter of type '{Data.Parameter.ParameterType.FullName}'."); - } + return this; + } - Data.Default = defaultValue; - Data.IsDefaultSet = true; + /// + /// Ignore the parameter when reading and writing. + /// + /// True to ignore, otherwise false. + public virtual ParameterMap Ignore(bool ignore) + { + Data.Ignore = ignore; + + return this; + } - return this; + /// + /// The default value that will be used when reading when + /// the CSV field is empty. + /// + /// The default value. + public virtual ParameterMap Default(object defaultValue) + { + if (defaultValue == null && Data.Parameter.ParameterType.IsValueType) + { + throw new ArgumentException($"Parameter of type '{Data.Parameter.ParameterType.FullName}' can't have a default value of null."); } - /// - /// The constant value that will be used for every record when - /// reading and writing. This value will always be used no matter - /// what other mapping configurations are specified. - /// - /// The constant value. - public virtual ParameterMap Constant(object constantValue) + if (defaultValue != null && defaultValue.GetType() != Data.Parameter.ParameterType) { - if (constantValue == null && Data.Parameter.ParameterType.IsValueType) - { - throw new ArgumentException($"Parameter of type '{Data.Parameter.ParameterType.FullName}' can't have a constant value of null."); - } + throw new ArgumentException($"Default of type '{defaultValue.GetType().FullName}' does not match parameter of type '{Data.Parameter.ParameterType.FullName}'."); + } - if (constantValue != null && constantValue.GetType() != Data.Parameter.ParameterType) - { - throw new ArgumentException($"Constant of type '{constantValue.GetType().FullName}' does not match parameter of type '{Data.Parameter.ParameterType.FullName}'."); - } + Data.Default = defaultValue; + Data.IsDefaultSet = true; - Data.Constant = constantValue; - Data.IsConstantSet = true; + return this; + } - return this; + /// + /// The constant value that will be used for every record when + /// reading and writing. This value will always be used no matter + /// what other mapping configurations are specified. + /// + /// The constant value. + public virtual ParameterMap Constant(object constantValue) + { + if (constantValue == null && Data.Parameter.ParameterType.IsValueType) + { + throw new ArgumentException($"Parameter of type '{Data.Parameter.ParameterType.FullName}' can't have a constant value of null."); } - /// - /// The field is optional. - /// - public virtual ParameterMap Optional() + if (constantValue != null && constantValue.GetType() != Data.Parameter.ParameterType) { - Data.IsOptional = true; - - return this; + throw new ArgumentException($"Constant of type '{constantValue.GetType().FullName}' does not match parameter of type '{Data.Parameter.ParameterType.FullName}'."); } - /// - /// Specifies the to use - /// when converting the parameter to and from a CSV field. - /// - /// The TypeConverter to use. - public virtual ParameterMap TypeConverter(ITypeConverter typeConverter) - { - Data.TypeConverter = typeConverter; + Data.Constant = constantValue; + Data.IsConstantSet = true; - return this; - } + return this; + } - /// - /// Specifies the to use - /// when converting the parameter to and from a CSV field. - /// - /// The of the - /// to use. - public virtual ParameterMap TypeConverter() where TConverter : ITypeConverter - { - TypeConverter(ObjectResolver.Current.Resolve()); + /// + /// The field is optional. + /// + public virtual ParameterMap Optional() + { + Data.IsOptional = true; - return this; - } + return this; + } - internal int GetMaxIndex() - { - return ReferenceMap?.GetMaxIndex() ?? Data.Index; - } + /// + /// Specifies the to use + /// when converting the parameter to and from a CSV field. + /// + /// The TypeConverter to use. + public virtual ParameterMap TypeConverter(ITypeConverter typeConverter) + { + Data.TypeConverter = typeConverter; + + return this; + } + + /// + /// Specifies the to use + /// when converting the parameter to and from a CSV field. + /// + /// The of the + /// to use. + public virtual ParameterMap TypeConverter() where TConverter : ITypeConverter + { + TypeConverter(ObjectResolver.Current.Resolve()); + + return this; + } + + internal int GetMaxIndex() + { + return ReferenceMap?.GetMaxIndex() ?? Data.Index; } } diff --git a/src/CsvHelper/Configuration/ParameterMapData.cs b/src/CsvHelper/Configuration/ParameterMapData.cs index 58595e7f2..e851d9255 100644 --- a/src/CsvHelper/Configuration/ParameterMapData.cs +++ b/src/CsvHelper/Configuration/ParameterMapData.cs @@ -3,104 +3,102 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.TypeConversion; -using System.Collections.Generic; using System.Diagnostics; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// The constructor parameter data for the map. +/// +[DebuggerDisplay("Index = {Index}, Names = {string.Join(\", \", Names)}, Parameter = {Parameter}")] +public class ParameterMapData { /// - /// The constructor parameter data for the map. + /// Gets the that the data + /// is associated with. + /// + public virtual ParameterInfo Parameter { get; private set; } + + /// + /// Gets the list of column names. + /// + public virtual MemberNameCollection Names { get; } = new MemberNameCollection(); + + /// + /// Gets or sets the index of the name. + /// This is used if there are multiple + /// columns with the same names. + /// + public virtual int NameIndex { get; set; } + + /// + /// Gets or sets a value indicating if the name was + /// explicitly set. True if it was explicitly set, + /// otherwise false. + /// + public virtual bool IsNameSet { get; set; } + + /// + /// Gets or sets the column index. + /// + public virtual int Index { get; set; } = -1; + + /// + /// Gets or sets a value indicating if the index was + /// explicitly set. True if it was explicitly set, + /// otherwise false. + /// + public virtual bool IsIndexSet { get; set; } + + /// + /// Gets or sets the type converter. + /// + public virtual ITypeConverter? TypeConverter { get; set; } + + /// + /// Gets or sets the type converter options. + /// + public virtual TypeConverterOptions TypeConverterOptions { get; set; } = new TypeConverterOptions(); + + /// + /// Gets or sets a value indicating whether the field should be ignored. + /// + public virtual bool Ignore { get; set; } + + /// + /// Gets or sets the default value used when a CSV field is empty. + /// + public virtual object? Default { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is default value set. + /// the default value was explicitly set. True if it was + /// explicitly set, otherwise false. + /// + public virtual bool IsDefaultSet { get; set; } + + /// + /// Gets or sets the constant value used for every record. + /// + public virtual object? Constant { get; set; } + + /// + /// Gets or sets a value indicating if a constant was explicitly set. + /// + public virtual bool IsConstantSet { get; set; } + + /// + /// Gets or sets a value indicating if a field is optional. + /// + public virtual bool IsOptional { get; set; } + + /// + /// Initializes a new instance of the class. /// - [DebuggerDisplay("Index = {Index}, Names = {string.Join(\", \", Names)}, Parameter = {Parameter}")] - public class ParameterMapData + /// The constructor parameter. + public ParameterMapData(ParameterInfo parameter) { - /// - /// Gets the that the data - /// is associated with. - /// - public virtual ParameterInfo Parameter { get; private set; } - - /// - /// Gets the list of column names. - /// - public virtual MemberNameCollection Names { get; } = new MemberNameCollection(); - - /// - /// Gets or sets the index of the name. - /// This is used if there are multiple - /// columns with the same names. - /// - public virtual int NameIndex { get; set; } - - /// - /// Gets or sets a value indicating if the name was - /// explicitly set. True if it was explicitly set, - /// otherwise false. - /// - public virtual bool IsNameSet { get; set; } - - /// - /// Gets or sets the column index. - /// - public virtual int Index { get; set; } = -1; - - /// - /// Gets or sets a value indicating if the index was - /// explicitly set. True if it was explicitly set, - /// otherwise false. - /// - public virtual bool IsIndexSet { get; set; } - - /// - /// Gets or sets the type converter. - /// - public virtual ITypeConverter TypeConverter { get; set; } - - /// - /// Gets or sets the type converter options. - /// - public virtual TypeConverterOptions TypeConverterOptions { get; set; } = new TypeConverterOptions(); - - /// - /// Gets or sets a value indicating whether the field should be ignored. - /// - public virtual bool Ignore { get; set; } - - /// - /// Gets or sets the default value used when a CSV field is empty. - /// - public virtual object Default { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is default value set. - /// the default value was explicitly set. True if it was - /// explicitly set, otherwise false. - /// - public virtual bool IsDefaultSet { get; set; } - - /// - /// Gets or sets the constant value used for every record. - /// - public virtual object Constant { get; set; } - - /// - /// Gets or sets a value indicating if a constant was explicitly set. - /// - public virtual bool IsConstantSet { get; set; } - - /// - /// Gets or sets a value indicating if a field is optional. - /// - public virtual bool IsOptional { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The constructor parameter. - public ParameterMapData(ParameterInfo parameter) - { - Parameter = parameter; - } + Parameter = parameter; } } diff --git a/src/CsvHelper/Configuration/ParameterMapTypeConverterOption.cs b/src/CsvHelper/Configuration/ParameterMapTypeConverterOption.cs index d2e308c7d..7dd489f73 100644 --- a/src/CsvHelper/Configuration/ParameterMapTypeConverterOption.cs +++ b/src/CsvHelper/Configuration/ParameterMapTypeConverterOption.cs @@ -2,159 +2,153 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Sets type converter options on a parameter map. +/// +public class ParameterMapTypeConverterOption { + private readonly ParameterMap parameterMap; + /// - /// Sets type converter options on a parameter map. + /// Creates a new instance using the given . /// - public class ParameterMapTypeConverterOption - { - private readonly ParameterMap parameterMap; - - /// - /// Creates a new instance using the given . - /// - /// The member map the options are being applied to. - public ParameterMapTypeConverterOption(ParameterMap parameterMap) - { - this.parameterMap = parameterMap; - } + /// The member map the options are being applied to. + public ParameterMapTypeConverterOption(ParameterMap parameterMap) + { + this.parameterMap = parameterMap; + } - /// - /// The used when type converting. - /// This will override the global - /// setting. - /// - /// The culture info. - public virtual ParameterMap CultureInfo(CultureInfo cultureInfo) - { - parameterMap.Data.TypeConverterOptions.CultureInfo = cultureInfo; + /// + /// The used when type converting. + /// This will override the global + /// setting. + /// + /// The culture info. + public virtual ParameterMap CultureInfo(CultureInfo cultureInfo) + { + parameterMap.Data.TypeConverterOptions.CultureInfo = cultureInfo; - return parameterMap; - } + return parameterMap; + } - /// - /// The to use when type converting. - /// This is used when doing any conversions. - /// - /// The date time style. - public virtual ParameterMap DateTimeStyles(DateTimeStyles dateTimeStyle) - { - parameterMap.Data.TypeConverterOptions.DateTimeStyle = dateTimeStyle; + /// + /// The to use when type converting. + /// This is used when doing any conversions. + /// + /// The date time style. + public virtual ParameterMap DateTimeStyles(DateTimeStyles dateTimeStyle) + { + parameterMap.Data.TypeConverterOptions.DateTimeStyle = dateTimeStyle; - return parameterMap; - } + return parameterMap; + } - /// - /// The to use when type converting. - /// This is used when doing converting. - /// - /// The time span styles. - public virtual ParameterMap TimespanStyles(TimeSpanStyles timeSpanStyles) - { - parameterMap.Data.TypeConverterOptions.TimeSpanStyle = timeSpanStyles; + /// + /// The to use when type converting. + /// This is used when doing converting. + /// + /// The time span styles. + public virtual ParameterMap TimespanStyles(TimeSpanStyles timeSpanStyles) + { + parameterMap.Data.TypeConverterOptions.TimeSpanStyle = timeSpanStyles; - return parameterMap; - } + return parameterMap; + } - /// - /// The to use when type converting. - /// This is used when doing any number conversions. - /// - /// - public virtual ParameterMap NumberStyles(NumberStyles numberStyle) - { - parameterMap.Data.TypeConverterOptions.NumberStyles = numberStyle; + /// + /// The to use when type converting. + /// This is used when doing any number conversions. + /// + /// + public virtual ParameterMap NumberStyles(NumberStyles numberStyle) + { + parameterMap.Data.TypeConverterOptions.NumberStyles = numberStyle; - return parameterMap; - } + return parameterMap; + } - /// - /// The string format to be used when type converting. - /// - /// The format. - public virtual ParameterMap Format(params string[] formats) - { - parameterMap.Data.TypeConverterOptions.Formats = formats; + /// + /// The string format to be used when type converting. + /// + /// The format. + public virtual ParameterMap Format(params string[] formats) + { + parameterMap.Data.TypeConverterOptions.Formats = formats; - return parameterMap; - } + return parameterMap; + } - /// - /// The to use when converting. - /// This is used when doing conversions. - /// - /// Kind of the URI. - public virtual ParameterMap UriKind(UriKind uriKind) - { - parameterMap.Data.TypeConverterOptions.UriKind = uriKind; + /// + /// The to use when converting. + /// This is used when doing conversions. + /// + /// Kind of the URI. + public virtual ParameterMap UriKind(UriKind uriKind) + { + parameterMap.Data.TypeConverterOptions.UriKind = uriKind; - return parameterMap; - } + return parameterMap; + } - /// - /// The string values used to represent a boolean when converting. - /// - /// A value indicating whether true values or false values are being set. - /// A value indication if the current values should be cleared before adding the new ones. - /// The string boolean values. - public virtual ParameterMap BooleanValues(bool isTrue, bool clearValues = true, params string[] booleanValues) + /// + /// The string values used to represent a boolean when converting. + /// + /// A value indicating whether true values or false values are being set. + /// A value indication if the current values should be cleared before adding the new ones. + /// The string boolean values. + public virtual ParameterMap BooleanValues(bool isTrue, bool clearValues = true, params string[] booleanValues) + { + if (isTrue) { - if (isTrue) + if (clearValues) { - if (clearValues) - { - parameterMap.Data.TypeConverterOptions.BooleanTrueValues.Clear(); - } - - parameterMap.Data.TypeConverterOptions.BooleanTrueValues.AddRange(booleanValues); + parameterMap.Data.TypeConverterOptions.BooleanTrueValues.Clear(); } - else - { - if (clearValues) - { - parameterMap.Data.TypeConverterOptions.BooleanFalseValues.Clear(); - } - parameterMap.Data.TypeConverterOptions.BooleanFalseValues.AddRange(booleanValues); - } - - return parameterMap; + parameterMap.Data.TypeConverterOptions.BooleanTrueValues.AddRange(booleanValues); } - - /// - /// The string values used to represent null when converting. - /// - /// The values that represent null. - /// - public virtual ParameterMap NullValues(params string[] nullValues) - { - return NullValues(true, nullValues); - } - - /// - /// The string values used to represent null when converting. - /// - /// A value indication if the current values should be cleared before adding the new ones. - /// The values that represent null. - /// - public virtual ParameterMap NullValues(bool clearValues, params string[] nullValues) + else { if (clearValues) { - parameterMap.Data.TypeConverterOptions.NullValues.Clear(); + parameterMap.Data.TypeConverterOptions.BooleanFalseValues.Clear(); } - parameterMap.Data.TypeConverterOptions.NullValues.AddRange(nullValues); + parameterMap.Data.TypeConverterOptions.BooleanFalseValues.AddRange(booleanValues); + } + + return parameterMap; + } + + /// + /// The string values used to represent null when converting. + /// + /// The values that represent null. + /// + public virtual ParameterMap NullValues(params string[] nullValues) + { + return NullValues(true, nullValues); + } - return parameterMap; + /// + /// The string values used to represent null when converting. + /// + /// A value indication if the current values should be cleared before adding the new ones. + /// The values that represent null. + /// + public virtual ParameterMap NullValues(bool clearValues, params string[] nullValues) + { + if (clearValues) + { + parameterMap.Data.TypeConverterOptions.NullValues.Clear(); } + + parameterMap.Data.TypeConverterOptions.NullValues.AddRange(nullValues); + + return parameterMap; } } diff --git a/src/CsvHelper/Configuration/ParameterReferenceMap.cs b/src/CsvHelper/Configuration/ParameterReferenceMap.cs index 3fd62fefb..d7648cb76 100644 --- a/src/CsvHelper/Configuration/ParameterReferenceMap.cs +++ b/src/CsvHelper/Configuration/ParameterReferenceMap.cs @@ -2,65 +2,63 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// Mapping info for a reference parameter mapping to a class. +/// +public class ParameterReferenceMap { + private readonly ParameterReferenceMapData data; + /// - /// Mapping info for a reference parameter mapping to a class. + /// Gets the parameter reference map data. /// - public class ParameterReferenceMap - { - private readonly ParameterReferenceMapData data; + public ParameterReferenceMapData Data => data; - /// - /// Gets the parameter reference map data. - /// - public ParameterReferenceMapData Data => data; - - /// - /// Initializes a new instance of the class. - /// - /// The parameter. - /// The to use for the reference map. - public ParameterReferenceMap(ParameterInfo parameter, ClassMap mapping) + /// + /// Initializes a new instance of the class. + /// + /// The parameter. + /// The to use for the reference map. + public ParameterReferenceMap(ParameterInfo parameter, ClassMap mapping) + { + if (mapping == null) { - if (mapping == null) - { - throw new ArgumentNullException(nameof(mapping)); - } - - data = new ParameterReferenceMapData(parameter, mapping); + throw new ArgumentNullException(nameof(mapping)); } - /// - /// Appends a prefix to the header of each field of the reference parameter. - /// - /// The prefix to be prepended to headers of each reference parameter. - /// Inherit parent prefixes. - /// The current - public ParameterReferenceMap Prefix(string prefix = null, bool inherit = false) + data = new ParameterReferenceMapData(parameter, mapping); + } + + /// + /// Appends a prefix to the header of each field of the reference parameter. + /// + /// The prefix to be prepended to headers of each reference parameter. + /// Inherit parent prefixes. + /// The current + public ParameterReferenceMap Prefix(string? prefix = null, bool inherit = false) + { + if (string.IsNullOrEmpty(prefix)) { - if (string.IsNullOrEmpty(prefix)) - { - prefix = data.Parameter.Name + "."; - } + prefix = data.Parameter.Name + "."; + } - data.Inherit = inherit; - data.Prefix = prefix; + data.Inherit = inherit; + data.Prefix = prefix!; - return this; - } + return this; + } - /// - /// Get the largest index for the - /// members and references. - /// - /// The max index. - internal int GetMaxIndex() - { - return data.Mapping.GetMaxIndex(); - } + /// + /// Get the largest index for the + /// members and references. + /// + /// The max index. + internal int GetMaxIndex() + { + return data.Mapping.GetMaxIndex(); } } diff --git a/src/CsvHelper/Configuration/ParameterReferenceMapData.cs b/src/CsvHelper/Configuration/ParameterReferenceMapData.cs index 009fc5f25..978b48fd2 100644 --- a/src/CsvHelper/Configuration/ParameterReferenceMapData.cs +++ b/src/CsvHelper/Configuration/ParameterReferenceMapData.cs @@ -5,66 +5,65 @@ using System.Diagnostics; using System.Reflection; -namespace CsvHelper.Configuration +namespace CsvHelper.Configuration; + +/// +/// The configuration data for the reference map. +/// +[DebuggerDisplay("Prefix = {Prefix}, Parameter = {Parameter}")] +public class ParameterReferenceMapData { + private string prefix = string.Empty; + /// - /// The configuration data for the reference map. + /// Gets or sets the header prefix to use. /// - [DebuggerDisplay( "Prefix = {Prefix}, Parameter = {Parameter}" )] - public class ParameterReferenceMapData + public virtual string Prefix { - private string prefix; - - /// - /// Gets or sets the header prefix to use. - /// - public virtual string Prefix + get { return prefix; } + set { - get { return prefix; } - set + prefix = value; + foreach (var memberMap in Mapping.MemberMaps) { - prefix = value; - foreach( var memberMap in Mapping.MemberMaps ) - { - memberMap.Data.Names.Prefix = value; - } + memberMap.Data.Names.Prefix = value; + } - if (Inherit) + if (Inherit) + { + foreach (var memberRef in Mapping.ReferenceMaps) { - foreach (var memberRef in Mapping.ReferenceMaps) - { - memberRef.Data.Prefix = memberRef.Data.Prefix == null ? value : string.Concat(value, memberRef.Data.Prefix); - } + memberRef.Data.Prefix = memberRef.Data.Prefix == null ? value : string.Concat(value, memberRef.Data.Prefix); } } } + } - /// - /// Gets or sets a value indicating if a prefix should inherit its parent. - /// true to inherit, otherwise false. - /// - public virtual bool Inherit { get; set; } + /// + /// Gets or sets a value indicating if a prefix should inherit its parent. + /// true to inherit, otherwise false. + /// + public virtual bool Inherit { get; set; } - /// - /// Gets the that the data - /// is associated with. - /// - public virtual ParameterInfo Parameter { get; private set; } + /// + /// Gets the that the data + /// is associated with. + /// + public virtual ParameterInfo Parameter { get; private set; } - /// - /// Gets the mapping this is a reference for. - /// - public ClassMap Mapping { get; private set; } + /// + /// Gets the mapping this is a reference for. + /// + public ClassMap Mapping { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The parameter. - /// The mapping this is a reference for. - public ParameterReferenceMapData( ParameterInfo parameter, ClassMap mapping ) - { - Parameter = parameter; - Mapping = mapping; - } + /// + /// Initializes a new instance of the class. + /// + /// The parameter. + /// The mapping this is a reference for. + public ParameterReferenceMapData(ParameterInfo parameter, ClassMap mapping) + { + Parameter = parameter; + Mapping = mapping; } } diff --git a/src/CsvHelper/Configuration/TrimOptions.cs b/src/CsvHelper/Configuration/TrimOptions.cs index 8bc33b07a..3686e31d9 100644 --- a/src/CsvHelper/Configuration/TrimOptions.cs +++ b/src/CsvHelper/Configuration/TrimOptions.cs @@ -2,29 +2,26 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Configuration; -namespace CsvHelper.Configuration +/// +/// Options for trimming of fields. +/// +[Flags] +public enum TrimOptions { /// - /// Options for trimming of fields. + /// No trimming. /// - [Flags] - public enum TrimOptions - { - /// - /// No trimming. - /// - None = 0, + None = 0, - /// - /// Trims the whitespace around a field. - /// - Trim = 1, + /// + /// Trims the whitespace around a field. + /// + Trim = 1, - /// - /// Trims the whitespace inside of quotes around a field. - /// - InsideQuotes = 2 - } + /// + /// Trims the whitespace inside of quotes around a field. + /// + InsideQuotes = 2 } diff --git a/src/CsvHelper/CsvContext.cs b/src/CsvHelper/CsvContext.cs index 29a921c32..1d37e693f 100644 --- a/src/CsvHelper/CsvContext.cs +++ b/src/CsvHelper/CsvContext.cs @@ -4,199 +4,193 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; using CsvHelper.TypeConversion; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Share state for CsvHelper. +/// +public class CsvContext { /// - /// Share state for CsvHelper. - /// - public class CsvContext - { - /// - /// Gets or sets the . - /// - public virtual TypeConverterOptionsCache TypeConverterOptionsCache { get; set; } = new TypeConverterOptionsCache(); - - /// - /// Gets or sets the . - /// - public virtual TypeConverterCache TypeConverterCache { get; set; } = new TypeConverterCache(); - - /// - /// The configured s. - /// - public virtual ClassMapCollection Maps { get; private set; } - - /// - /// Gets the parser. - /// - public IParser Parser { get; private set; } - - /// - /// Gets the reader. - /// - public IReader Reader { get; internal set; } - - /// - /// Gets the writer. - /// - public IWriter Writer { get; internal set; } - - /// - /// Gets the configuration. - /// - public CsvConfiguration Configuration { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The reader. - public CsvContext(IReader reader) - { - Reader = reader; - Parser = reader.Parser; - Configuration = reader.Configuration as CsvConfiguration ?? throw new InvalidOperationException($"{nameof(IReader)}.{nameof(IReader.Configuration)} must be of type {nameof(CsvConfiguration)} to be used in the context."); - Maps = new ClassMapCollection(this); - } + /// Gets or sets the . + /// + public virtual TypeConverterOptionsCache TypeConverterOptionsCache { get; set; } = new TypeConverterOptionsCache(); - /// - /// Initializes a new instance of the class. - /// - /// The parser. - public CsvContext(IParser parser) - { - Parser = parser; - Configuration = parser.Configuration as CsvConfiguration ?? throw new InvalidOperationException($"{nameof(IParser)}.{nameof(IParser.Configuration)} must be of type {nameof(CsvConfiguration)} to be used in the context."); - Maps = new ClassMapCollection(this); - } + /// + /// Gets or sets the . + /// + public virtual TypeConverterCache TypeConverterCache { get; set; } = new TypeConverterCache(); - /// - /// Initializes a new instance of the class. - /// - /// The writer. - public CsvContext(IWriter writer) - { - Writer = writer; - Configuration = writer.Configuration as CsvConfiguration ?? throw new InvalidOperationException($"{nameof(IWriter)}.{nameof(IWriter.Configuration)} must be of type {nameof(CsvConfiguration)} to be used in the context."); - Maps = new ClassMapCollection(this); - } + /// + /// The configured s. + /// + public virtual ClassMapCollection Maps { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - public CsvContext(CsvConfiguration configuration) - { - Configuration = configuration; - Maps = new ClassMapCollection(this); - } + /// + /// Gets the parser. + /// + public IParser? Parser { get; private set; } - /// - /// Use a to configure mappings. - /// When using a class map, no members are mapped by default. - /// Only member specified in the mapping are used. - /// - /// The type of mapping class to use. - public virtual TMap RegisterClassMap() where TMap : ClassMap - { - var map = ObjectResolver.Current.Resolve(); - RegisterClassMap(map); + /// + /// Gets the reader. + /// + public IReader? Reader { get; internal set; } - return map; - } + /// + /// Gets the writer. + /// + public IWriter? Writer { get; internal set; } - /// - /// Use a to configure mappings. - /// When using a class map, no members are mapped by default. - /// Only members specified in the mapping are used. - /// - /// The type of mapping class to use. - public virtual ClassMap RegisterClassMap(Type classMapType) - { - if (!typeof(ClassMap).IsAssignableFrom(classMapType)) - { - throw new ArgumentException("The class map type must inherit from CsvClassMap."); - } + /// + /// Gets the configuration. + /// + public CsvConfiguration Configuration { get; private set; } - var map = (ClassMap)ObjectResolver.Current.Resolve(classMapType); - RegisterClassMap(map); + /// + /// Initializes a new instance of the class. + /// + /// The reader. + public CsvContext(IReader reader) + { + Reader = reader; + Parser = reader.Parser; + Configuration = reader.Configuration as CsvConfiguration ?? throw new InvalidOperationException($"{nameof(IReader)}.{nameof(IReader.Configuration)} must be of type {nameof(CsvConfiguration)} to be used in the context."); + Maps = new ClassMapCollection(this); + } - return map; - } + /// + /// Initializes a new instance of the class. + /// + /// The parser. + public CsvContext(IParser parser) + { + Parser = parser; + Configuration = parser.Configuration as CsvConfiguration ?? throw new InvalidOperationException($"{nameof(IParser)}.{nameof(IParser.Configuration)} must be of type {nameof(CsvConfiguration)} to be used in the context."); + Maps = new ClassMapCollection(this); + } - /// - /// Registers the class map. - /// - /// The class map to register. - public virtual void RegisterClassMap(ClassMap map) - { - if (map.MemberMaps.Count == 0 && map.ReferenceMaps.Count == 0 && map.ParameterMaps.Count == 0) - { - throw new ConfigurationException("No mappings were specified in the CsvClassMap."); - } + /// + /// Initializes a new instance of the class. + /// + /// The writer. + public CsvContext(IWriter writer) + { + Writer = writer; + Configuration = writer.Configuration as CsvConfiguration ?? throw new InvalidOperationException($"{nameof(IWriter)}.{nameof(IWriter.Configuration)} must be of type {nameof(CsvConfiguration)} to be used in the context."); + Maps = new ClassMapCollection(this); + } - Maps.Add(map); - } + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public CsvContext(CsvConfiguration configuration) + { + Configuration = configuration; + Maps = new ClassMapCollection(this); + } - /// - /// Unregisters the class map. - /// - /// The map type to unregister. - public virtual void UnregisterClassMap() - where TMap : ClassMap - { - UnregisterClassMap(typeof(TMap)); - } + /// + /// Use a to configure mappings. + /// When using a class map, no members are mapped by default. + /// Only member specified in the mapping are used. + /// + /// The type of mapping class to use. + public virtual TMap RegisterClassMap() where TMap : ClassMap + { + var map = ObjectResolver.Current.Resolve(); + RegisterClassMap(map); - /// - /// Unregisters the class map. - /// - /// The map type to unregister. - public virtual void UnregisterClassMap(Type classMapType) + return map; + } + + /// + /// Use a to configure mappings. + /// When using a class map, no members are mapped by default. + /// Only members specified in the mapping are used. + /// + /// The type of mapping class to use. + public virtual ClassMap RegisterClassMap(Type classMapType) + { + if (!typeof(ClassMap).IsAssignableFrom(classMapType)) { - Maps.Remove(classMapType); + throw new ArgumentException("The class map type must inherit from CsvClassMap."); } - /// - /// Unregisters all class maps. - /// - public virtual void UnregisterClassMap() + var map = (ClassMap)ObjectResolver.Current.Resolve(classMapType); + RegisterClassMap(map); + + return map; + } + + /// + /// Registers the class map. + /// + /// The class map to register. + public virtual void RegisterClassMap(ClassMap map) + { + if (map.MemberMaps.Count == 0 && map.ReferenceMaps.Count == 0 && map.ParameterMaps.Count == 0) { - Maps.Clear(); + throw new ConfigurationException("No mappings were specified in the CsvClassMap."); } - /// - /// Generates a for the type. - /// - /// The type to generate the map for. - /// The generate map. - public virtual ClassMap AutoMap() - { - var map = ObjectResolver.Current.Resolve>(); - map.AutoMap(this); - Maps.Add(map); + Maps.Add(map); + } - return map; - } + /// + /// Unregisters the class map. + /// + /// The map type to unregister. + public virtual void UnregisterClassMap() + where TMap : ClassMap + { + UnregisterClassMap(typeof(TMap)); + } - /// - /// Generates a for the type. - /// - /// The type to generate for the map. - /// The generate map. - public virtual ClassMap AutoMap(Type type) - { - var mapType = typeof(DefaultClassMap<>).MakeGenericType(type); - var map = (ClassMap)ObjectResolver.Current.Resolve(mapType); - map.AutoMap(this); - Maps.Add(map); + /// + /// Unregisters the class map. + /// + /// The map type to unregister. + public virtual void UnregisterClassMap(Type classMapType) + { + Maps.Remove(classMapType); + } - return map; - } + /// + /// Unregisters all class maps. + /// + public virtual void UnregisterClassMap() + { + Maps.Clear(); + } + + /// + /// Generates a for the type. + /// + /// The type to generate the map for. + /// The generate map. + public virtual ClassMap AutoMap() + { + var map = ObjectResolver.Current.Resolve>(); + map.AutoMap(this); + Maps.Add(map); + + return map; + } + + /// + /// Generates a for the type. + /// + /// The type to generate for the map. + /// The generate map. + public virtual ClassMap AutoMap(Type type) + { + var mapType = typeof(DefaultClassMap<>).MakeGenericType(type); + var map = (ClassMap)ObjectResolver.Current.Resolve(mapType); + map.AutoMap(this); + Maps.Add(map); + + return map; } } diff --git a/src/CsvHelper/CsvDataReader.cs b/src/CsvHelper/CsvDataReader.cs index 752cbdbb8..2da66caca 100644 --- a/src/CsvHelper/CsvDataReader.cs +++ b/src/CsvHelper/CsvDataReader.cs @@ -2,382 +2,390 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Data; using System.Globalization; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Provides a means of reading a CSV file forward-only by using CsvReader. +/// +/// +public class CsvDataReader : IDataReader { - /// - /// Provides a means of reading a CSV file forward-only by using CsvReader. - /// - /// - public class CsvDataReader : IDataReader - { - private readonly CsvReader csv; - private readonly DataTable schemaTable; - private bool skipNextRead; + private readonly CsvReader csv; + private readonly DataTable schemaTable; + private bool skipNextRead; - /// - public object this[int i] + /// + public object this[int i] + { + get { - get - { - return csv[i] ?? string.Empty; - } + return csv[i] ?? string.Empty; } + } - /// - public object this[string name] + /// + public object this[string name] + { + get { - get - { - return csv[name] ?? string.Empty; - } + return csv[name] ?? string.Empty; } + } - /// - public int Depth + /// + public int Depth + { + get { - get - { - return 0; - } + return 0; } + } - /// - public bool IsClosed { get; private set; } + /// + public bool IsClosed { get; private set; } - /// - public int RecordsAffected + /// + public int RecordsAffected + { + get { - get - { - return 0; - } + return 0; } + } - /// - public int FieldCount + /// + public int FieldCount + { + get { - get - { - return csv?.Parser.Count ?? 0; - } + return csv?.Parser.Count ?? 0; } + } - /// - /// Initializes a new instance of the class. - /// - /// The CSV. - /// The DataTable representing the file schema. - public CsvDataReader(CsvReader csv, DataTable schemaTable = null) - { - this.csv = csv; - - csv.Read(); - - if (csv.Configuration.HasHeaderRecord && csv.HeaderRecord == null) - { - csv.ReadHeader(); - } - else - { - skipNextRead = true; - } + /// + /// Initializes a new instance of the class. + /// + /// The CSV. + /// The DataTable representing the file schema. + public CsvDataReader(CsvReader csv, DataTable? schemaTable = null) + { + this.csv = csv; - this.schemaTable = schemaTable ?? GetSchemaTable(); - } + csv.Read(); - /// - public void Close() + if (csv.Configuration.HasHeaderRecord && csv.HeaderRecord == null) { - Dispose(); + csv.ReadHeader(); } - - /// - public void Dispose() + else { - csv.Dispose(); - IsClosed = true; + skipNextRead = true; } - /// - public bool GetBoolean(int i) - { - return csv.GetField(i); - } + this.schemaTable = schemaTable ?? GetSchemaTable(); + } - /// - public byte GetByte(int i) - { - return csv.GetField(i); - } + /// + public void Close() + { + Dispose(); + } - /// - public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) - { - var bytes = csv.GetField(i); + /// + public void Dispose() + { + csv.Dispose(); + IsClosed = true; + } - buffer ??= new byte[bytes.Length]; + /// + public bool GetBoolean(int i) + { + return csv.GetField(i); + } - Array.Copy(bytes, fieldOffset, buffer, bufferoffset, length); + /// + public byte GetByte(int i) + { + return csv.GetField(i); + } - return bytes.Length; - } + /// + public long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) + { + var bytes = csv.GetField(i); - /// - public char GetChar(int i) + if (bytes == null) { - return csv.GetField(i); + return 0; } - /// - public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) - { - var chars = csv.GetField(i).ToCharArray(); + buffer ??= new byte[bytes.Length]; - buffer ??= new char[chars.Length]; + Array.Copy(bytes, fieldOffset, buffer, bufferoffset, length); - Array.Copy(chars, fieldoffset, buffer, bufferoffset, length); + return bytes.Length; + } - return chars.Length; - } + /// + public char GetChar(int i) + { + return csv.GetField(i); + } + + /// + public long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) + { + var chars = csv.GetField(i)?.ToCharArray(); - /// - public IDataReader GetData(int i) + if (chars == null) { - throw new NotSupportedException(); + return 0; } - /// - public string GetDataTypeName(int i) - { - if (i >= schemaTable.Rows.Count) - { - throw new IndexOutOfRangeException($"SchemaTable does not contain a definition for field '{i}'."); - } + buffer ??= new char[chars.Length]; - var row = schemaTable.Rows[i]; - var field = row["DataType"] as Type; + Array.Copy(chars, fieldoffset, buffer, bufferoffset, length); - if (field == null) - { - throw new InvalidOperationException($"SchemaTable does not contain a 'DataType' of type 'Type' for field '{i}'."); - } + return chars.Length; + } - return field.Name; - } + /// + public IDataReader GetData(int i) + { + throw new NotSupportedException(); + } - /// - public DateTime GetDateTime(int i) + /// + public string GetDataTypeName(int i) + { + if (i >= schemaTable.Rows.Count) { - return csv.GetField(i); + throw new IndexOutOfRangeException($"SchemaTable does not contain a definition for field '{i}'."); } - /// - public decimal GetDecimal(int i) - { - return csv.GetField(i); - } + var row = schemaTable.Rows[i]; + var field = row["DataType"] as Type; - /// - public double GetDouble(int i) + if (field == null) { - return csv.GetField(i); + throw new InvalidOperationException($"SchemaTable does not contain a 'DataType' of type 'Type' for field '{i}'."); } - /// - public Type GetFieldType(int i) - { - return typeof(string); - } + return field.Name; + } - /// - public float GetFloat(int i) - { - return csv.GetField(i); - } + /// + public DateTime GetDateTime(int i) + { + return csv.GetField(i); + } - /// - public Guid GetGuid(int i) - { - return csv.GetField(i); - } + /// + public decimal GetDecimal(int i) + { + return csv.GetField(i); + } - /// - public short GetInt16(int i) - { - return csv.GetField(i); - } + /// + public double GetDouble(int i) + { + return csv.GetField(i); + } - /// - public int GetInt32(int i) - { - return csv.GetField(i); - } + /// + public Type GetFieldType(int i) + { + return typeof(string); + } - /// - public long GetInt64(int i) - { - return csv.GetField(i); - } + /// + public float GetFloat(int i) + { + return csv.GetField(i); + } - /// - public string GetName(int i) - { - return csv.Configuration.HasHeaderRecord - ? csv.HeaderRecord[i] - : string.Empty; - } + /// + public Guid GetGuid(int i) + { + return csv.GetField(i); + } - /// - public int GetOrdinal(string name) - { - var index = csv.GetFieldIndex(name, isTryGet: true); - if (index >= 0) - { - return index; - } + /// + public short GetInt16(int i) + { + return csv.GetField(i); + } - var args = new PrepareHeaderForMatchArgs(name, 0); - var namePrepared = csv.Configuration.PrepareHeaderForMatch(args); + /// + public int GetInt32(int i) + { + return csv.GetField(i); + } - var headerRecord = csv.HeaderRecord; - for (var i = 0; i < headerRecord.Length; i++) - { - args = new PrepareHeaderForMatchArgs(headerRecord[i], i); - var headerPrepared = csv.Configuration.PrepareHeaderForMatch(args); - if (csv.Configuration.CultureInfo.CompareInfo.Compare(namePrepared, headerPrepared, CompareOptions.IgnoreCase) == 0) - { - return i; - } - } + /// + public long GetInt64(int i) + { + return csv.GetField(i); + } - throw new IndexOutOfRangeException($"Field with name '{name}' and prepared name '{namePrepared}' was not found."); + /// + public string GetName(int i) + { + return csv.Configuration.HasHeaderRecord + ? (csv.HeaderRecord?[i] ?? string.Empty) + : string.Empty; + } + + /// + public int GetOrdinal(string name) + { + var index = csv.GetFieldIndex(name, isTryGet: true); + if (index >= 0) + { + return index; } - /// - public DataTable GetSchemaTable() + var args = new PrepareHeaderForMatchArgs(name, 0); + var namePrepared = csv.Configuration.PrepareHeaderForMatch(args); + + var headerRecord = csv.HeaderRecord; + for (var i = 0; i < (headerRecord?.Length ?? 0); i++) { - if (schemaTable != null) + args = new PrepareHeaderForMatchArgs(headerRecord?[i], i); + var headerPrepared = csv.Configuration.PrepareHeaderForMatch(args); + if (csv.Configuration.CultureInfo.CompareInfo.Compare(namePrepared, headerPrepared, CompareOptions.IgnoreCase) == 0) { - return schemaTable; + return i; } + } - // https://docs.microsoft.com/en-us/dotnet/api/system.data.datatablereader.getschematable?view=netframework-4.7.2 - var dt = new DataTable("SchemaTable"); - dt.Columns.Add("AllowDBNull", typeof(bool)); - dt.Columns.Add("AutoIncrementSeed", typeof(long)); - dt.Columns.Add("AutoIncrementStep", typeof(long)); - dt.Columns.Add("BaseCatalogName"); - dt.Columns.Add("BaseColumnName"); - dt.Columns.Add("BaseColumnNamespace"); - dt.Columns.Add("BaseSchemaName"); - dt.Columns.Add("BaseTableName"); - dt.Columns.Add("BaseTableNamespace"); - dt.Columns.Add("ColumnName"); - dt.Columns.Add("ColumnMapping", typeof(MappingType)); - dt.Columns.Add("ColumnOrdinal", typeof(int)); - dt.Columns.Add("ColumnSize", typeof(int)); - dt.Columns.Add("DataType", typeof(Type)); - dt.Columns.Add("DefaultValue", typeof(object)); - dt.Columns.Add("Expression"); - dt.Columns.Add("IsAutoIncrement", typeof(bool)); - dt.Columns.Add("IsKey", typeof(bool)); - dt.Columns.Add("IsLong", typeof(bool)); - dt.Columns.Add("IsReadOnly", typeof(bool)); - dt.Columns.Add("IsRowVersion", typeof(bool)); - dt.Columns.Add("IsUnique", typeof(bool)); - dt.Columns.Add("NumericPrecision", typeof(short)); - dt.Columns.Add("NumericScale", typeof(short)); - dt.Columns.Add("ProviderType", typeof(int)); - - for (var i = 0; i < csv.ColumnCount; i++) - { - object columnName = csv.Configuration.HasHeaderRecord ? csv.HeaderRecord[i] : i; - - var row = dt.NewRow(); - row["AllowDBNull"] = true; - row["AutoIncrementSeed"] = DBNull.Value; - row["AutoIncrementStep"] = DBNull.Value; - row["BaseCatalogName"] = null; - row["BaseColumnName"] = columnName; - row["BaseColumnNamespace"] = null; - row["BaseSchemaName"] = null; - row["BaseTableName"] = null; - row["BaseTableNamespace"] = null; - row["ColumnName"] = columnName; - row["ColumnMapping"] = MappingType.Element; - row["ColumnOrdinal"] = i; - row["ColumnSize"] = int.MaxValue; - row["DataType"] = typeof(string); - row["DefaultValue"] = null; - row["Expression"] = null; - row["IsAutoIncrement"] = false; - row["IsKey"] = false; - row["IsLong"] = false; - row["IsReadOnly"] = true; - row["IsRowVersion"] = false; - row["IsUnique"] = false; - row["NumericPrecision"] = DBNull.Value; - row["NumericScale"] = DBNull.Value; - row["ProviderType"] = DbType.String; - - dt.Rows.Add(row); - } + throw new IndexOutOfRangeException($"Field with name '{name}' and prepared name '{namePrepared}' was not found."); + } - return dt; - } + /// + public DataTable GetSchemaTable() + { + if (schemaTable != null) + { + return schemaTable; + } + + // https://docs.microsoft.com/en-us/dotnet/api/system.data.datatablereader.getschematable?view=netframework-4.7.2 + var dt = new DataTable("SchemaTable"); + dt.Columns.Add("AllowDBNull", typeof(bool)); + dt.Columns.Add("AutoIncrementSeed", typeof(long)); + dt.Columns.Add("AutoIncrementStep", typeof(long)); + dt.Columns.Add("BaseCatalogName"); + dt.Columns.Add("BaseColumnName"); + dt.Columns.Add("BaseColumnNamespace"); + dt.Columns.Add("BaseSchemaName"); + dt.Columns.Add("BaseTableName"); + dt.Columns.Add("BaseTableNamespace"); + dt.Columns.Add("ColumnName"); + dt.Columns.Add("ColumnMapping", typeof(MappingType)); + dt.Columns.Add("ColumnOrdinal", typeof(int)); + dt.Columns.Add("ColumnSize", typeof(int)); + dt.Columns.Add("DataType", typeof(Type)); + dt.Columns.Add("DefaultValue", typeof(object)); + dt.Columns.Add("Expression"); + dt.Columns.Add("IsAutoIncrement", typeof(bool)); + dt.Columns.Add("IsKey", typeof(bool)); + dt.Columns.Add("IsLong", typeof(bool)); + dt.Columns.Add("IsReadOnly", typeof(bool)); + dt.Columns.Add("IsRowVersion", typeof(bool)); + dt.Columns.Add("IsUnique", typeof(bool)); + dt.Columns.Add("NumericPrecision", typeof(short)); + dt.Columns.Add("NumericScale", typeof(short)); + dt.Columns.Add("ProviderType", typeof(int)); + + for (var i = 0; i < csv.ColumnCount; i++) + { + object? columnName = csv.Configuration.HasHeaderRecord ? csv.HeaderRecord?[i] : i; + + var row = dt.NewRow(); + row["AllowDBNull"] = true; + row["AutoIncrementSeed"] = DBNull.Value; + row["AutoIncrementStep"] = DBNull.Value; + row["BaseCatalogName"] = null; + row["BaseColumnName"] = columnName; + row["BaseColumnNamespace"] = null; + row["BaseSchemaName"] = null; + row["BaseTableName"] = null; + row["BaseTableNamespace"] = null; + row["ColumnName"] = columnName; + row["ColumnMapping"] = MappingType.Element; + row["ColumnOrdinal"] = i; + row["ColumnSize"] = int.MaxValue; + row["DataType"] = typeof(string); + row["DefaultValue"] = null; + row["Expression"] = null; + row["IsAutoIncrement"] = false; + row["IsKey"] = false; + row["IsLong"] = false; + row["IsReadOnly"] = true; + row["IsRowVersion"] = false; + row["IsUnique"] = false; + row["NumericPrecision"] = DBNull.Value; + row["NumericScale"] = DBNull.Value; + row["ProviderType"] = DbType.String; + + dt.Rows.Add(row); + } + + return dt; + } - /// - public string GetString(int i) - { - return csv.GetField(i) ?? string.Empty; - } + /// + public string GetString(int i) + { + return csv.GetField(i) ?? string.Empty; + } + + /// + public object GetValue(int i) + { + return IsDBNull(i) ? DBNull.Value : (csv.GetField(i) ?? string.Empty); + } - /// - public object GetValue(int i) + /// + public int GetValues(object[] values) + { + for (var i = 0; i < values.Length; i++) { - return IsDBNull(i) ? DBNull.Value : (csv.GetField(i) ?? string.Empty); + values[i] = IsDBNull(i) ? DBNull.Value : (csv.GetField(i) ?? string.Empty); } - /// - public int GetValues(object[] values) - { - for (var i = 0; i < values.Length; i++) - { - values[i] = IsDBNull(i) ? DBNull.Value : (csv.GetField(i) ?? string.Empty); - } + return csv.Parser.Count; + } - return csv.Parser.Count; - } + /// + public bool IsDBNull(int i) + { + var field = csv.GetField(i); + var nullValues = csv.Context.TypeConverterOptionsCache.GetOptions().NullValues; - /// - public bool IsDBNull(int i) - { - var field = csv.GetField(i); - var nullValues = csv.Context.TypeConverterOptionsCache.GetOptions().NullValues; + return field == null || nullValues.Contains(field); + } - return field == null || nullValues.Contains(field); - } + /// + public bool NextResult() + { + return false; + } - /// - public bool NextResult() + /// + public bool Read() + { + if (skipNextRead) { - return false; + skipNextRead = false; + return true; } - /// - public bool Read() - { - if (skipNextRead) - { - skipNextRead = false; - return true; - } - - return csv.Read(); - } + return csv.Read(); } } diff --git a/src/CsvHelper/CsvHelper.csproj b/src/CsvHelper/CsvHelper.csproj index 3d821abd2..363ceac9e 100644 --- a/src/CsvHelper/CsvHelper.csproj +++ b/src/CsvHelper/CsvHelper.csproj @@ -15,8 +15,9 @@ en-US true true - + enable + enable + nullable true diff --git a/src/CsvHelper/CsvHelperException.cs b/src/CsvHelper/CsvHelperException.cs index 71e5beed1..fe8b90f3f 100644 --- a/src/CsvHelper/CsvHelperException.cs +++ b/src/CsvHelper/CsvHelperException.cs @@ -3,142 +3,140 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; using System.Text; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Represents errors that occur in CsvHelper. +/// +[Serializable] +public class CsvHelperException : Exception { + [NonSerialized] + private readonly CsvContext? context; + + /// + /// Gets the context. + /// + public CsvContext? Context => context; + + /// + /// Initializes a new instance of the CsvHelperException class. + /// + internal protected CsvHelperException() : base() { } + + /// + /// Initializes a new instance of the CsvHelperException class. + /// + /// The message that describes the error. + internal protected CsvHelperException(string message) : base(message) { } + /// - /// Represents errors that occur in CsvHelper. + /// Initializes a new instance of the CsvHelperException class. /// - [Serializable] - public class CsvHelperException : Exception + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + internal protected CsvHelperException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Initializes a new instance of the class. + /// + public CsvHelperException(CsvContext context) { - [NonSerialized] - private readonly CsvContext context; - - /// - /// Gets the context. - /// - public CsvContext Context => context; - - /// - /// Initializes a new instance of the CsvHelperException class. - /// - internal protected CsvHelperException() : base() { } - - /// - /// Initializes a new instance of the CsvHelperException class. - /// - /// The message that describes the error. - internal protected CsvHelperException(string message) : base(message) { } - - /// - /// Initializes a new instance of the CsvHelperException class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - internal protected CsvHelperException(string message, Exception innerException) : base(message, innerException) { } - - /// - /// Initializes a new instance of the class. - /// - public CsvHelperException(CsvContext context) - { - this.context = context; - } + this.context = context; + } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The context. - /// The message that describes the error. - public CsvHelperException(CsvContext context, string message) : base(AddDetails(message, context)) - { - this.context = context; - } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The context. + /// The message that describes the error. + public CsvHelperException(CsvContext context, string message) : base(AddDetails(message, context)) + { + this.context = context; + } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public CsvHelperException(CsvContext context, string message, Exception innerException) : base(AddDetails(message, context), innerException) - { - this.context = context; - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public CsvHelperException(CsvContext context, string message, Exception innerException) : base(AddDetails(message, context), innerException) + { + this.context = context; + } - private static string AddDetails(string message, CsvContext context) - { - var indent = new string(' ', 3); + private static string AddDetails(string message, CsvContext context) + { + var indent = new string(' ', 3); - var details = new StringBuilder(); + var details = new StringBuilder(); - if (context.Reader != null) + if (context.Reader != null) + { + details.AppendLine($"{nameof(IReader)} state:"); + details.AppendLine($"{indent}{nameof(IReader.ColumnCount)}: {context.Reader.ColumnCount}"); + details.AppendLine($"{indent}{nameof(IReader.CurrentIndex)}: {context.Reader.CurrentIndex}"); + try { - details.AppendLine($"{nameof(IReader)} state:"); - details.AppendLine($"{indent}{nameof(IReader.ColumnCount)}: {context.Reader.ColumnCount}"); - details.AppendLine($"{indent}{nameof(IReader.CurrentIndex)}: {context.Reader.CurrentIndex}"); - try + var record = new StringBuilder(); + if (context.Reader.HeaderRecord != null) { - var record = new StringBuilder(); - if (context.Reader.HeaderRecord != null) - { - record.Append("[\""); - record.Append(string.Join("\",\"", context.Reader.HeaderRecord)); - record.Append("\"]"); - } - - details.AppendLine($"{indent}{nameof(IReader.HeaderRecord)}:{Environment.NewLine}{record}"); + record.Append("[\""); + record.Append(string.Join("\",\"", context.Reader.HeaderRecord)); + record.Append("\"]"); } - catch { } + + details.AppendLine($"{indent}{nameof(IReader.HeaderRecord)}:{Environment.NewLine}{record}"); } + catch { } + } - if (context.Parser != null) + if (context.Parser != null) + { + details.AppendLine($"{nameof(IParser)} state:"); + details.AppendLine($"{indent}{nameof(IParser.ByteCount)}: {context.Parser.ByteCount}"); + details.AppendLine($"{indent}{nameof(IParser.CharCount)}: {context.Parser.CharCount}"); + details.AppendLine($"{indent}{nameof(IParser.Row)}: {context.Parser.Row}"); + details.AppendLine($"{indent}{nameof(IParser.RawRow)}: {context.Parser.RawRow}"); + details.AppendLine($"{indent}{nameof(IParser.Count)}: {context.Parser.Count}"); + + try { - details.AppendLine($"{nameof(IParser)} state:"); - details.AppendLine($"{indent}{nameof(IParser.ByteCount)}: {context.Parser.ByteCount}"); - details.AppendLine($"{indent}{nameof(IParser.CharCount)}: {context.Parser.CharCount}"); - details.AppendLine($"{indent}{nameof(IParser.Row)}: {context.Parser.Row}"); - details.AppendLine($"{indent}{nameof(IParser.RawRow)}: {context.Parser.RawRow}"); - details.AppendLine($"{indent}{nameof(IParser.Count)}: {context.Parser.Count}"); - - try - { - var rawRecord = context.Configuration.ExceptionMessagesContainRawData - ? context.Parser.RawRecord - : $"Hidden because {nameof(IParserConfiguration.ExceptionMessagesContainRawData)} is false."; - details.AppendLine($"{indent}{nameof(IParser.RawRecord)}:{Environment.NewLine}{rawRecord}"); - } - catch { } + var rawRecord = context.Configuration.ExceptionMessagesContainRawData + ? context.Parser.RawRecord + : $"Hidden because {nameof(IParserConfiguration.ExceptionMessagesContainRawData)} is false."; + details.AppendLine($"{indent}{nameof(IParser.RawRecord)}:{Environment.NewLine}{rawRecord}"); } + catch { } + } - if (context.Writer != null) - { - details.AppendLine($"{nameof(IWriter)} state:"); - details.AppendLine($"{indent}{nameof(IWriter.Row)}: {context.Writer.Row}"); - details.AppendLine($"{indent}{nameof(IWriter.Index)}: {context.Writer.Index}"); + if (context.Writer != null) + { + details.AppendLine($"{nameof(IWriter)} state:"); + details.AppendLine($"{indent}{nameof(IWriter.Row)}: {context.Writer.Row}"); + details.AppendLine($"{indent}{nameof(IWriter.Index)}: {context.Writer.Index}"); - var record = new StringBuilder(); - if (context.Writer.HeaderRecord != null) + var record = new StringBuilder(); + if (context.Writer.HeaderRecord != null) + { + record.Append("["); + if (context.Writer.HeaderRecord.Length > 0) { - record.Append("["); - if (context.Writer.HeaderRecord.Length > 0) - { - record.Append("\""); - record.Append(string.Join("\",\"", context.Writer.HeaderRecord)); - record.Append("\""); - } - record.Append("]"); + record.Append("\""); + record.Append(string.Join("\",\"", context.Writer.HeaderRecord)); + record.Append("\""); } - details.AppendLine($"{indent}{nameof(IWriter.HeaderRecord)}:{Environment.NewLine}{context.Writer.Row}"); + record.Append("]"); } - - return $"{message}{Environment.NewLine}{details}"; + details.AppendLine($"{indent}{nameof(IWriter.HeaderRecord)}:{Environment.NewLine}{context.Writer.Row}"); } + + return $"{message}{Environment.NewLine}{details}"; } } diff --git a/src/CsvHelper/CsvMode.cs b/src/CsvHelper/CsvMode.cs index 3870b8515..5b07e9cf6 100644 --- a/src/CsvHelper/CsvMode.cs +++ b/src/CsvHelper/CsvMode.cs @@ -3,41 +3,35 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Mode to use when parsing and writing. +/// +public enum CsvMode { /// - /// Mode to use when parsing and writing. + /// Uses RFC 4180 format (default). + /// If a field contains a or , + /// it is wrapped in s. + /// If quoted field contains a , it is preceded by . /// - public enum CsvMode - { - /// - /// Uses RFC 4180 format (default). - /// If a field contains a or , - /// it is wrapped in s. - /// If quoted field contains a , it is preceded by . - /// - RFC4180 = 0, + RFC4180 = 0, - /// - /// Uses escapes. - /// If a field contains a , , - /// or , it is preceded by . - /// Newline defaults to \n. - /// - Escape, + /// + /// Uses escapes. + /// If a field contains a , , + /// or , it is preceded by . + /// Newline defaults to \n. + /// + Escape, - /// - /// Doesn't use quotes or escapes. - /// This will ignore quoting and escape characters. This means a field cannot contain a - /// , , or - /// , as they cannot be escaped. - /// - NoEscape - } + /// + /// Doesn't use quotes or escapes. + /// This will ignore quoting and escape characters. This means a field cannot contain a + /// , , or + /// , as they cannot be escaped. + /// + NoEscape } diff --git a/src/CsvHelper/CsvParser.cs b/src/CsvHelper/CsvParser.cs index 5c62e5352..1c7112c3f 100644 --- a/src/CsvHelper/CsvParser.cs +++ b/src/CsvHelper/CsvParser.cs @@ -4,1202 +4,1194 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; using CsvHelper.Delegates; -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Parses a CSV file. +/// +public class CsvParser : IParser, IDisposable { - /// - /// Parses a CSV file. - /// - public class CsvParser : IParser, IDisposable + private readonly IParserConfiguration configuration; + private readonly FieldCache fieldCache = new FieldCache(); + private readonly TextReader reader; + private readonly char quote; + private readonly char escape; + private readonly bool countBytes; + private readonly Encoding encoding; + private readonly bool ignoreBlankLines; + private readonly char comment; + private readonly bool allowComments; + private readonly BadDataFound badDataFound; + private readonly bool lineBreakInQuotedFieldIsBadData; + private readonly TrimOptions trimOptions; + private readonly char[] whiteSpaceChars; + private readonly bool leaveOpen; + private readonly CsvMode mode; + private readonly string newLine; + private readonly char newLineFirstChar; + private readonly bool isNewLineSet; + private readonly bool cacheFields; + private readonly string[] delimiterValues; + private readonly bool detectDelimiter; + private readonly double maxFieldSize; + + private string delimiter; + private char delimiterFirstChar; + private char[] buffer; + private int bufferSize; + private int charsRead; + private int bufferPosition; + private int rowStartPosition; + private int fieldStartPosition; + private int row; + private int rawRow; + private long charCount; + private long byteCount; + private bool inQuotes; + private bool inEscape; + private Field[] fields; + private string[] processedFields; + private int fieldsPosition; + private bool disposed; + private int quoteCount; + private char[] processFieldBuffer; + private int processFieldBufferSize; + private ParserState state; + private int delimiterPosition = 1; + private int newLinePosition = 1; + private bool fieldIsBadData; + private bool fieldIsQuoted; + private bool isProcessingField; + private bool isRecordProcessed; + private string[] record = []; + + /// + public long CharCount => charCount; + + /// + public long ByteCount => byteCount; + + /// + public int Row => row; + + /// + public string[]? Record { - private readonly IParserConfiguration configuration; - private readonly FieldCache fieldCache = new FieldCache(); - private readonly TextReader reader; - private readonly char quote; - private readonly char escape; - private readonly bool countBytes; - private readonly Encoding encoding; - private readonly bool ignoreBlankLines; - private readonly char comment; - private readonly bool allowComments; - private readonly BadDataFound badDataFound; - private readonly bool lineBreakInQuotedFieldIsBadData; - private readonly TrimOptions trimOptions; - private readonly char[] whiteSpaceChars; - private readonly bool leaveOpen; - private readonly CsvMode mode; - private readonly string newLine; - private readonly char newLineFirstChar; - private readonly bool isNewLineSet; - private readonly bool cacheFields; - private readonly string[] delimiterValues; - private readonly bool detectDelimiter; - private readonly double maxFieldSize; - - private string delimiter; - private char delimiterFirstChar; - private char[] buffer; - private int bufferSize; - private int charsRead; - private int bufferPosition; - private int rowStartPosition; - private int fieldStartPosition; - private int row; - private int rawRow; - private long charCount; - private long byteCount; - private bool inQuotes; - private bool inEscape; - private Field[] fields; - private string[] processedFields; - private int fieldsPosition; - private bool disposed; - private int quoteCount; - private char[] processFieldBuffer; - private int processFieldBufferSize; - private ParserState state; - private int delimiterPosition = 1; - private int newLinePosition = 1; - private bool fieldIsBadData; - private bool fieldIsQuoted; - private bool isProcessingField; - private bool isRecordProcessed; - private string[] record; - - /// - public long CharCount => charCount; - - /// - public long ByteCount => byteCount; - - /// - public int Row => row; - - /// - public string[] Record + get { - get + if (isRecordProcessed == true) { - if (isRecordProcessed == true) - { - return this.record; - } + return this.record; + } - if (fieldsPosition == 0) - { - return null; - } + if (fieldsPosition == 0) + { + return null; + } - var record = new string[fieldsPosition]; + var record = new string[fieldsPosition]; - for (var i = 0; i < record.Length; i++) - { - record[i] = this[i]; - } + for (var i = 0; i < record.Length; i++) + { + record[i] = this[i]; + } - this.record = record; - isRecordProcessed = true; + this.record = record; + isRecordProcessed = true; - return this.record; - } + return this.record; } + } - /// - public string RawRecord => new string(buffer, rowStartPosition, bufferPosition - rowStartPosition); + /// + public string RawRecord => new string(buffer, rowStartPosition, bufferPosition - rowStartPosition); - /// - public int Count => fieldsPosition; + /// + public int Count => fieldsPosition; - /// - public int RawRow => rawRow; + /// + public int RawRow => rawRow; - /// - public string Delimiter => delimiter; + /// + public string Delimiter => delimiter; - /// - public CsvContext Context { get; private set; } + /// + public CsvContext Context { get; private set; } - /// - public IParserConfiguration Configuration => configuration; + /// + public IParserConfiguration Configuration => configuration; - /// - public string this[int index] + /// + public string this[int index] + { + get { - get + if (isProcessingField) { - if (isProcessingField) - { - var message = - $"You can't access {nameof(IParser)}[int] or {nameof(IParser)}.{nameof(IParser.Record)} inside of the {nameof(BadDataFound)} callback. " + - $"Use {nameof(BadDataFoundArgs)}.{nameof(BadDataFoundArgs.Field)} and {nameof(BadDataFoundArgs)}.{nameof(BadDataFoundArgs.RawRecord)} instead." - ; + var message = + $"You can't access {nameof(IParser)}[int] or {nameof(IParser)}.{nameof(IParser.Record)} inside of the {nameof(BadDataFound)} callback. " + + $"Use {nameof(BadDataFoundArgs)}.{nameof(BadDataFoundArgs.Field)} and {nameof(BadDataFoundArgs)}.{nameof(BadDataFoundArgs.RawRecord)} instead." + ; - throw new ParserException(Context, message); - } + throw new ParserException(Context, message); + } - isProcessingField = true; + isProcessingField = true; - var field = GetField(index); + var field = GetField(index); - isProcessingField = false; + isProcessingField = false; - return field; - } + return field; } + } - /// - /// Initializes a new instance of the class. - /// - /// The reader. - /// The culture. - /// if set to true [leave open]. - public CsvParser(TextReader reader, CultureInfo culture, bool leaveOpen = false) : this(reader, new CsvConfiguration(culture), leaveOpen) { } - - /// - /// Initializes a new instance of the class. - /// - /// The reader. - /// The configuration. - /// if set to true [leave open]. - public CsvParser(TextReader reader, IParserConfiguration configuration, bool leaveOpen = false) - { - this.reader = reader ?? throw new ArgumentNullException(nameof(reader)); - this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - - configuration.Validate(); + /// + /// Initializes a new instance of the class. + /// + /// The reader. + /// The culture. + /// if set to true [leave open]. + public CsvParser(TextReader reader, CultureInfo culture, bool leaveOpen = false) : this(reader, new CsvConfiguration(culture), leaveOpen) { } - Context = new CsvContext(this); - - allowComments = configuration.AllowComments; - badDataFound = configuration.BadDataFound; - bufferSize = configuration.BufferSize; - cacheFields = configuration.CacheFields; - comment = configuration.Comment; - countBytes = configuration.CountBytes; - delimiter = configuration.Delimiter; - delimiterFirstChar = configuration.Delimiter[0]; - delimiterValues = configuration.DetectDelimiterValues; - detectDelimiter = configuration.DetectDelimiter; - encoding = configuration.Encoding; - escape = configuration.Escape; - ignoreBlankLines = configuration.IgnoreBlankLines; - isNewLineSet = configuration.IsNewLineSet; - this.leaveOpen = leaveOpen; - lineBreakInQuotedFieldIsBadData = configuration.LineBreakInQuotedFieldIsBadData; - maxFieldSize = configuration.MaxFieldSize; - newLine = configuration.NewLine; - newLineFirstChar = configuration.NewLine[0]; - mode = configuration.Mode; - processFieldBufferSize = configuration.ProcessFieldBufferSize; - quote = configuration.Quote; - whiteSpaceChars = configuration.WhiteSpaceChars; - trimOptions = configuration.TrimOptions; - - buffer = new char[bufferSize]; - processFieldBuffer = new char[processFieldBufferSize]; - fields = new Field[128]; - processedFields = new string[128]; - } + /// + /// Initializes a new instance of the class. + /// + /// The reader. + /// The configuration. + /// if set to true [leave open]. + public CsvParser(TextReader reader, IParserConfiguration configuration, bool leaveOpen = false) + { + this.reader = reader ?? throw new ArgumentNullException(nameof(reader)); + this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + + configuration.Validate(); + + Context = new CsvContext(this); + + allowComments = configuration.AllowComments; + badDataFound = configuration.BadDataFound; + bufferSize = configuration.BufferSize; + cacheFields = configuration.CacheFields; + comment = configuration.Comment; + countBytes = configuration.CountBytes; + delimiter = configuration.Delimiter; + delimiterFirstChar = configuration.Delimiter[0]; + delimiterValues = configuration.DetectDelimiterValues; + detectDelimiter = configuration.DetectDelimiter; + encoding = configuration.Encoding; + escape = configuration.Escape; + ignoreBlankLines = configuration.IgnoreBlankLines; + isNewLineSet = configuration.IsNewLineSet; + this.leaveOpen = leaveOpen; + lineBreakInQuotedFieldIsBadData = configuration.LineBreakInQuotedFieldIsBadData; + maxFieldSize = configuration.MaxFieldSize; + newLine = configuration.NewLine; + newLineFirstChar = configuration.NewLine[0]; + mode = configuration.Mode; + processFieldBufferSize = configuration.ProcessFieldBufferSize; + quote = configuration.Quote; + whiteSpaceChars = configuration.WhiteSpaceChars; + trimOptions = configuration.TrimOptions; + + buffer = new char[bufferSize]; + processFieldBuffer = new char[processFieldBufferSize]; + fields = new Field[128]; + processedFields = new string[128]; + } - /// - public bool Read() + /// + public bool Read() + { + isRecordProcessed = false; + rowStartPosition = bufferPosition; + fieldStartPosition = rowStartPosition; + fieldsPosition = 0; + quoteCount = 0; + row++; + rawRow++; + var c = '\0'; + var cPrev = c; + + while (true) { - isRecordProcessed = false; - rowStartPosition = bufferPosition; - fieldStartPosition = rowStartPosition; - fieldsPosition = 0; - quoteCount = 0; - row++; - rawRow++; - var c = '\0'; - var cPrev = c; - - while (true) + if (bufferPosition >= charsRead) { - if (bufferPosition >= charsRead) + if (!FillBuffer()) { - if (!FillBuffer()) - { - return ReadEndOfFile(); - } - - if (row == 1 && detectDelimiter) - { - DetectDelimiter(); - } + return ReadEndOfFile(); } - if (ReadLine(ref c, ref cPrev) == ReadLineResult.Complete) + if (row == 1 && detectDelimiter) { - return true; + DetectDelimiter(); } } + + if (ReadLine(ref c, ref cPrev) == ReadLineResult.Complete) + { + return true; + } } + } - /// - public async Task ReadAsync() + /// + public async Task ReadAsync() + { + isRecordProcessed = false; + rowStartPosition = bufferPosition; + fieldStartPosition = rowStartPosition; + fieldsPosition = 0; + quoteCount = 0; + row++; + rawRow++; + var c = '\0'; + var cPrev = c; + + while (true) { - isRecordProcessed = false; - rowStartPosition = bufferPosition; - fieldStartPosition = rowStartPosition; - fieldsPosition = 0; - quoteCount = 0; - row++; - rawRow++; - var c = '\0'; - var cPrev = c; - - while (true) + if (bufferPosition >= charsRead) { - if (bufferPosition >= charsRead) + if (!await FillBufferAsync().ConfigureAwait(false)) { - if (!await FillBufferAsync().ConfigureAwait(false)) - { - return ReadEndOfFile(); - } - - if (row == 1 && detectDelimiter) - { - DetectDelimiter(); - } + return ReadEndOfFile(); } - if (ReadLine(ref c, ref cPrev) == ReadLineResult.Complete) + if (row == 1 && detectDelimiter) { - return true; + DetectDelimiter(); } } - } - private void DetectDelimiter() - { - var text = new string(buffer, 0, charsRead); - var newDelimiter = configuration.GetDelimiter(new GetDelimiterArgs(text, configuration)); - if (newDelimiter != null) + if (ReadLine(ref c, ref cPrev) == ReadLineResult.Complete) { - delimiter = newDelimiter; - delimiterFirstChar = newDelimiter[0]; - configuration.Validate(); + return true; } } + } + + private void DetectDelimiter() + { + var text = new string(buffer, 0, charsRead); + var newDelimiter = configuration.GetDelimiter(new GetDelimiterArgs(text, configuration)); + if (newDelimiter != null) + { + delimiter = newDelimiter; + delimiterFirstChar = newDelimiter[0]; + configuration.Validate(); + } + } - private ReadLineResult ReadLine(ref char c, ref char cPrev) + private ReadLineResult ReadLine(ref char c, ref char cPrev) + { + while (bufferPosition < charsRead) { - while (bufferPosition < charsRead) + if (state != ParserState.None) { - if (state != ParserState.None) + // Continue the state before doing anything else. + ReadLineResult result; + switch (state) { - // Continue the state before doing anything else. - ReadLineResult result; - switch (state) - { - case ParserState.Spaces: - result = ReadSpaces(ref c); - break; - case ParserState.BlankLine: - result = ReadBlankLine(ref c); - break; - case ParserState.Delimiter: - result = ReadDelimiter(ref c); - break; - case ParserState.LineEnding: - result = ReadLineEnding(ref c); - break; - case ParserState.NewLine: - result = ReadNewLine(ref c); - break; - default: - throw new InvalidOperationException($"Parser state '{state}' is not valid."); - } + case ParserState.Spaces: + result = ReadSpaces(ref c); + break; + case ParserState.BlankLine: + result = ReadBlankLine(ref c); + break; + case ParserState.Delimiter: + result = ReadDelimiter(ref c); + break; + case ParserState.LineEnding: + result = ReadLineEnding(ref c); + break; + case ParserState.NewLine: + result = ReadNewLine(ref c); + break; + default: + throw new InvalidOperationException($"Parser state '{state}' is not valid."); + } - var shouldReturn = - // Buffer needs to be filled. - result == ReadLineResult.Incomplete || - // Done reading row. - result == ReadLineResult.Complete && (state == ParserState.LineEnding || state == ParserState.NewLine) - ; + var shouldReturn = + // Buffer needs to be filled. + result == ReadLineResult.Incomplete || + // Done reading row. + result == ReadLineResult.Complete && (state == ParserState.LineEnding || state == ParserState.NewLine) + ; - if (result == ReadLineResult.Complete) - { - state = ParserState.None; - } + if (result == ReadLineResult.Complete) + { + state = ParserState.None; + } - if (shouldReturn) - { - return result; - } + if (shouldReturn) + { + return result; } + } - cPrev = c; - c = buffer[bufferPosition]; - bufferPosition++; - charCount++; + cPrev = c; + c = buffer[bufferPosition]; + bufferPosition++; + charCount++; - if (countBytes) + if (countBytes) + { + byteCount += encoding.GetByteCount(new char[] { c }); + } + + if (maxFieldSize > 0 && bufferPosition - fieldStartPosition - 1 > maxFieldSize) + { + throw new MaxFieldSizeException(Context); + } + + var isFirstCharOfRow = rowStartPosition == bufferPosition - 1; + if (isFirstCharOfRow && (allowComments && c == comment || ignoreBlankLines && ((c == '\r' || c == '\n') && !isNewLineSet || c == newLineFirstChar && isNewLineSet))) + { + state = ParserState.BlankLine; + var result = ReadBlankLine(ref c); + if (result == ReadLineResult.Complete) { - byteCount += encoding.GetByteCount(new char[] { c }); - } + state = ParserState.None; - if (maxFieldSize > 0 && bufferPosition - fieldStartPosition - 1 > maxFieldSize) + continue; + } + else { - throw new MaxFieldSizeException(Context); + return ReadLineResult.Incomplete; } + } - var isFirstCharOfRow = rowStartPosition == bufferPosition - 1; - if (isFirstCharOfRow && (allowComments && c == comment || ignoreBlankLines && ((c == '\r' || c == '\n') && !isNewLineSet || c == newLineFirstChar && isNewLineSet))) + if (mode == CsvMode.RFC4180) + { + var isFirstCharOfField = fieldStartPosition == bufferPosition - 1; + if (isFirstCharOfField) { - state = ParserState.BlankLine; - var result = ReadBlankLine(ref c); - if (result == ReadLineResult.Complete) + if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim && ArrayHelper.Contains(whiteSpaceChars, c)) { - state = ParserState.None; - - continue; - } - else - { - return ReadLineResult.Incomplete; + // Skip through whitespace. This is so we can process the field later. + var result = ReadSpaces(ref c); + if (result == ReadLineResult.Incomplete) + { + fieldStartPosition = bufferPosition; + return result; + } } + + // Fields are only quoted if the first character is a quote. + // If not, read until a delimiter or newline is found. + fieldIsQuoted = c == quote; } - if (mode == CsvMode.RFC4180) + if (fieldIsQuoted) { - var isFirstCharOfField = fieldStartPosition == bufferPosition - 1; - if (isFirstCharOfField) + if (c == quote || c == escape) { - if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim && ArrayHelper.Contains(whiteSpaceChars, c)) + quoteCount++; + + if (!inQuotes && !isFirstCharOfField && cPrev != escape) + { + fieldIsBadData = true; + } + else if (!fieldIsBadData) { - // Skip through whitespace. This is so we can process the field later. - var result = ReadSpaces(ref c); - if (result == ReadLineResult.Incomplete) - { - fieldStartPosition = bufferPosition; - return result; - } + // Don't process field quotes after bad data has been detected. + inQuotes = !inQuotes; } - - // Fields are only quoted if the first character is a quote. - // If not, read until a delimiter or newline is found. - fieldIsQuoted = c == quote; } - if (fieldIsQuoted) + if (inQuotes) { - if (c == quote || c == escape) + // If we are in quotes we don't want to do any special + // processing (e.g. of delimiters) until we hit the ending + // quote. But the newline logic may vary. + + if (!(c == '\r' || (c == '\n' && cPrev != '\r'))) { - quoteCount++; - - if (!inQuotes && !isFirstCharOfField && cPrev != escape) - { - fieldIsBadData = true; - } - else if (!fieldIsBadData) - { - // Don't process field quotes after bad data has been detected. - inQuotes = !inQuotes; - } + // We are not at (the beginning of) a newline, + // so just keep reading. + continue; } - if (inQuotes) + rawRow++; + + if (lineBreakInQuotedFieldIsBadData) { - // If we are in quotes we don't want to do any special - // processing (e.g. of delimiters) until we hit the ending - // quote. But the newline logic may vary. - - if (!(c == '\r' || (c == '\n' && cPrev != '\r'))) - { - // We are not at (the beginning of) a newline, - // so just keep reading. - continue; - } - - rawRow++; - - if (lineBreakInQuotedFieldIsBadData) - { - // This newline is not valid within the field. - // We will consume the newline and then end the - // field (and the row). - // This avoids growing the field (and the buffer) - // until another quote is found. - fieldIsBadData = true; - } - else - { - // We are at a newline but it is considered valid - // within a (quoted) field. We keep reading until - // we find the closing quote. - continue; - } + // This newline is not valid within the field. + // We will consume the newline and then end the + // field (and the row). + // This avoids growing the field (and the buffer) + // until another quote is found. + fieldIsBadData = true; } - } - else - { - if (c == quote || c == escape) + else { - // If the field isn't quoted but contains a - // quote or escape, it's has bad data. - fieldIsBadData = true; + // We are at a newline but it is considered valid + // within a (quoted) field. We keep reading until + // we find the closing quote. + continue; } } } - else if (mode == CsvMode.Escape) + else { - if (inEscape) - { - inEscape = false; - - continue; - } - - if (c == escape) + if (c == quote || c == escape) { - inEscape = true; - - continue; + // If the field isn't quoted but contains a + // quote or escape, it's has bad data. + fieldIsBadData = true; } } - - if (c == delimiterFirstChar) + } + else if (mode == CsvMode.Escape) + { + if (inEscape) { - state = ParserState.Delimiter; - var result = ReadDelimiter(ref c); - if (result == ReadLineResult.Incomplete) - { - return result; - } - - state = ParserState.None; + inEscape = false; continue; } - if (!isNewLineSet && (c == '\r' || c == '\n')) + if (c == escape) { - state = ParserState.LineEnding; - var result = ReadLineEnding(ref c); - if (result == ReadLineResult.Complete) - { - state = ParserState.None; - } + inEscape = true; - return result; + continue; } + } - if (isNewLineSet && c == newLineFirstChar) + if (c == delimiterFirstChar) + { + state = ParserState.Delimiter; + var result = ReadDelimiter(ref c); + if (result == ReadLineResult.Incomplete) { - state = ParserState.NewLine; - var result = ReadNewLine(ref c); - if (result == ReadLineResult.Complete) - { - state = ParserState.None; - } - return result; } - } - return ReadLineResult.Incomplete; - } + state = ParserState.None; - private ReadLineResult ReadSpaces(ref char c) - { - while (ArrayHelper.Contains(whiteSpaceChars, c)) + continue; + } + + if (!isNewLineSet && (c == '\r' || c == '\n')) { - if (bufferPosition >= charsRead) + state = ParserState.LineEnding; + var result = ReadLineEnding(ref c); + if (result == ReadLineResult.Complete) { - return ReadLineResult.Incomplete; + state = ParserState.None; } - c = buffer[bufferPosition]; - bufferPosition++; - charCount++; - if (countBytes) - { - byteCount += encoding.GetByteCount(new char[] { c }); - } + return result; } - return ReadLineResult.Complete; - } - - private ReadLineResult ReadBlankLine(ref char c) - { - while (bufferPosition < charsRead) + if (isNewLineSet && c == newLineFirstChar) { - if (c == '\r' || c == '\n') + state = ParserState.NewLine; + var result = ReadNewLine(ref c); + if (result == ReadLineResult.Complete) { - var result = ReadLineEnding(ref c); - if (result == ReadLineResult.Complete) - { - rowStartPosition = bufferPosition; - fieldStartPosition = rowStartPosition; - row++; - rawRow++; - } - - return result; + state = ParserState.None; } - c = buffer[bufferPosition]; - bufferPosition++; - charCount++; - if (countBytes) - { - byteCount += encoding.GetByteCount(new char[] { c }); - } + return result; } - - return ReadLineResult.Incomplete; } - private ReadLineResult ReadDelimiter(ref char c) + return ReadLineResult.Incomplete; + } + + private ReadLineResult ReadSpaces(ref char c) + { + while (ArrayHelper.Contains(whiteSpaceChars, c)) { - for (var i = delimiterPosition; i < delimiter.Length; i++) + if (bufferPosition >= charsRead) { - if (bufferPosition >= charsRead) - { - return ReadLineResult.Incomplete; - } - - delimiterPosition++; + return ReadLineResult.Incomplete; + } - c = buffer[bufferPosition]; - if (c != delimiter[i]) - { - c = buffer[bufferPosition - 1]; - delimiterPosition = 1; + c = buffer[bufferPosition]; + bufferPosition++; + charCount++; + if (countBytes) + { + byteCount += encoding.GetByteCount(new char[] { c }); + } + } - return ReadLineResult.Complete; - } + return ReadLineResult.Complete; + } - bufferPosition++; - charCount++; - if (countBytes) + private ReadLineResult ReadBlankLine(ref char c) + { + while (bufferPosition < charsRead) + { + if (c == '\r' || c == '\n') + { + var result = ReadLineEnding(ref c); + if (result == ReadLineResult.Complete) { - byteCount += encoding.GetByteCount(new[] { c }); + rowStartPosition = bufferPosition; + fieldStartPosition = rowStartPosition; + row++; + rawRow++; } - if (bufferPosition >= charsRead) - { - return ReadLineResult.Incomplete; - } + return result; } - AddField(fieldStartPosition, bufferPosition - fieldStartPosition - delimiter.Length); - - fieldStartPosition = bufferPosition; - delimiterPosition = 1; - fieldIsBadData = false; - - return ReadLineResult.Complete; + c = buffer[bufferPosition]; + bufferPosition++; + charCount++; + if (countBytes) + { + byteCount += encoding.GetByteCount(new char[] { c }); + } } - private ReadLineResult ReadLineEnding(ref char c) + return ReadLineResult.Incomplete; + } + + private ReadLineResult ReadDelimiter(ref char c) + { + for (var i = delimiterPosition; i < delimiter.Length; i++) { - var lessChars = 1; + if (bufferPosition >= charsRead) + { + return ReadLineResult.Incomplete; + } + + delimiterPosition++; - if (c == '\r') + c = buffer[bufferPosition]; + if (c != delimiter[i]) { - if (bufferPosition >= charsRead) - { - return ReadLineResult.Incomplete; - } + c = buffer[bufferPosition - 1]; + delimiterPosition = 1; - c = buffer[bufferPosition]; + return ReadLineResult.Complete; + } - if (c == '\n') - { - lessChars++; - bufferPosition++; - charCount++; - if (countBytes) - { - byteCount += encoding.GetByteCount(new char[] { c }); - } - } + bufferPosition++; + charCount++; + if (countBytes) + { + byteCount += encoding.GetByteCount(new[] { c }); } - if (state == ParserState.LineEnding) + if (bufferPosition >= charsRead) { - AddField(fieldStartPosition, bufferPosition - fieldStartPosition - lessChars); + return ReadLineResult.Incomplete; } + } - fieldIsBadData = false; + AddField(fieldStartPosition, bufferPosition - fieldStartPosition - delimiter.Length); - return ReadLineResult.Complete; - } + fieldStartPosition = bufferPosition; + delimiterPosition = 1; + fieldIsBadData = false; - private ReadLineResult ReadNewLine(ref char c) - { - for (var i = newLinePosition; i < newLine.Length; i++) - { - if (bufferPosition >= charsRead) - { - return ReadLineResult.Incomplete; - } + return ReadLineResult.Complete; + } - newLinePosition++; + private ReadLineResult ReadLineEnding(ref char c) + { + var lessChars = 1; - c = buffer[bufferPosition]; - if (c != newLine[i]) - { - c = buffer[bufferPosition - 1]; - newLinePosition = 1; + if (c == '\r') + { + if (bufferPosition >= charsRead) + { + return ReadLineResult.Incomplete; + } - return ReadLineResult.Complete; - } + c = buffer[bufferPosition]; + if (c == '\n') + { + lessChars++; bufferPosition++; charCount++; if (countBytes) { - byteCount += encoding.GetByteCount(new[] { c }); - } - - if (bufferPosition >= charsRead) - { - return ReadLineResult.Incomplete; + byteCount += encoding.GetByteCount(new char[] { c }); } } + } - AddField(fieldStartPosition, bufferPosition - fieldStartPosition - newLine.Length); + if (state == ParserState.LineEnding) + { + AddField(fieldStartPosition, bufferPosition - fieldStartPosition - lessChars); + } - fieldStartPosition = bufferPosition; - newLinePosition = 1; - fieldIsBadData = false; + fieldIsBadData = false; - return ReadLineResult.Complete; - } + return ReadLineResult.Complete; + } - private bool ReadEndOfFile() + private ReadLineResult ReadNewLine(ref char c) + { + for (var i = newLinePosition; i < newLine.Length; i++) { - var state = this.state; - this.state = ParserState.None; - - if (state == ParserState.BlankLine) + if (bufferPosition >= charsRead) { - return false; + return ReadLineResult.Incomplete; } - if (state == ParserState.Delimiter) - { - AddField(fieldStartPosition, bufferPosition - fieldStartPosition - delimiter.Length); - - fieldStartPosition = bufferPosition; - - AddField(fieldStartPosition, bufferPosition - fieldStartPosition); - - return true; - } + newLinePosition++; - if (state == ParserState.LineEnding) + c = buffer[bufferPosition]; + if (c != newLine[i]) { - AddField(fieldStartPosition, bufferPosition - fieldStartPosition - 1); + c = buffer[bufferPosition - 1]; + newLinePosition = 1; - return true; + return ReadLineResult.Complete; } - if (state == ParserState.NewLine) + bufferPosition++; + charCount++; + if (countBytes) { - AddField(fieldStartPosition, bufferPosition - fieldStartPosition - newLine.Length); - - return true; + byteCount += encoding.GetByteCount(new[] { c }); } - if (rowStartPosition < bufferPosition) + if (bufferPosition >= charsRead) { - AddField(fieldStartPosition, bufferPosition - fieldStartPosition); + return ReadLineResult.Incomplete; } - - return fieldsPosition > 0; } - private void AddField(int start, int length) - { - if (fieldsPosition >= fields.Length) - { - var newSize = fields.Length * 2; - Array.Resize(ref fields, newSize); - Array.Resize(ref processedFields, newSize); - } + AddField(fieldStartPosition, bufferPosition - fieldStartPosition - newLine.Length); - ref var field = ref fields[fieldsPosition]; - field.Start = start - rowStartPosition; - field.Length = length; - field.QuoteCount = quoteCount; - field.IsBad = fieldIsBadData; - field.IsProcessed = false; + fieldStartPosition = bufferPosition; + newLinePosition = 1; + fieldIsBadData = false; - fieldsPosition++; - quoteCount = 0; + return ReadLineResult.Complete; + } + + private bool ReadEndOfFile() + { + var state = this.state; + this.state = ParserState.None; + + if (state == ParserState.BlankLine) + { + return false; } - private bool FillBuffer() + if (state == ParserState.Delimiter) { - // Don't forget the async method below. + AddField(fieldStartPosition, bufferPosition - fieldStartPosition - delimiter.Length); - if (rowStartPosition == 0 && charCount > 0 && charsRead == bufferSize) - { - // The record is longer than the memory buffer. Increase the buffer. - bufferSize *= 2; - var tempBuffer = new char[bufferSize]; - buffer.CopyTo(tempBuffer, 0); - buffer = tempBuffer; - } + fieldStartPosition = bufferPosition; - var charsLeft = Math.Max(charsRead - rowStartPosition, 0); + AddField(fieldStartPosition, bufferPosition - fieldStartPosition); - Array.Copy(buffer, rowStartPosition, buffer, 0, charsLeft); + return true; + } - fieldStartPosition -= rowStartPosition; - rowStartPosition = 0; - bufferPosition = charsLeft; + if (state == ParserState.LineEnding) + { + AddField(fieldStartPosition, bufferPosition - fieldStartPosition - 1); - charsRead = reader.Read(buffer, charsLeft, buffer.Length - charsLeft); - if (charsRead == 0) - { - return false; - } + return true; + } - charsRead += charsLeft; + if (state == ParserState.NewLine) + { + AddField(fieldStartPosition, bufferPosition - fieldStartPosition - newLine.Length); return true; } - private async Task FillBufferAsync() + if (rowStartPosition < bufferPosition) { - if (rowStartPosition == 0 && charCount > 0 && charsRead == bufferSize) - { - // The record is longer than the memory buffer. Increase the buffer. - bufferSize *= 2; - var tempBuffer = new char[bufferSize]; - buffer.CopyTo(tempBuffer, 0); - buffer = tempBuffer; - } + AddField(fieldStartPosition, bufferPosition - fieldStartPosition); + } - var charsLeft = Math.Max(charsRead - rowStartPosition, 0); + return fieldsPosition > 0; + } - Array.Copy(buffer, rowStartPosition, buffer, 0, charsLeft); + private void AddField(int start, int length) + { + if (fieldsPosition >= fields.Length) + { + var newSize = fields.Length * 2; + Array.Resize(ref fields, newSize); + Array.Resize(ref processedFields, newSize); + } - fieldStartPosition -= rowStartPosition; - rowStartPosition = 0; - bufferPosition = charsLeft; + ref var field = ref fields[fieldsPosition]; + field.Start = start - rowStartPosition; + field.Length = length; + field.QuoteCount = quoteCount; + field.IsBad = fieldIsBadData; + field.IsProcessed = false; - charsRead = await reader.ReadAsync(buffer, charsLeft, buffer.Length - charsLeft).ConfigureAwait(false); - if (charsRead == 0) - { - return false; - } + fieldsPosition++; + quoteCount = 0; + } - charsRead += charsLeft; + private bool FillBuffer() + { + // Don't forget the async method below. - return true; + if (rowStartPosition == 0 && charCount > 0 && charsRead == bufferSize) + { + // The record is longer than the memory buffer. Increase the buffer. + bufferSize *= 2; + var tempBuffer = new char[bufferSize]; + buffer.CopyTo(tempBuffer, 0); + buffer = tempBuffer; } - private string GetField(int index) - { - if (index > fieldsPosition) - { - throw new IndexOutOfRangeException(); - } + var charsLeft = Math.Max(charsRead - rowStartPosition, 0); - ref var field = ref fields[index]; + Array.Copy(buffer, rowStartPosition, buffer, 0, charsLeft); - if (field.Length == 0) - { - return string.Empty; - } + fieldStartPosition -= rowStartPosition; + rowStartPosition = 0; + bufferPosition = charsLeft; - if (field.IsProcessed) - { - return processedFields[index]; - } + charsRead = reader.Read(buffer, charsLeft, buffer.Length - charsLeft); + if (charsRead == 0) + { + return false; + } - var start = field.Start + rowStartPosition; - var length = field.Length; - var quoteCount = field.QuoteCount; + charsRead += charsLeft; - ProcessedField processedField; - switch (mode) - { - case CsvMode.RFC4180: - processedField = field.IsBad - ? ProcessRFC4180BadField(start, length) - : ProcessRFC4180Field(start, length, quoteCount); - break; - case CsvMode.Escape: - processedField = ProcessEscapeField(start, length); - break; - case CsvMode.NoEscape: - processedField = ProcessNoEscapeField(start, length); - break; - default: - throw new InvalidOperationException($"ParseMode '{mode}' is not handled."); - } + return true; + } + + private async Task FillBufferAsync() + { + if (rowStartPosition == 0 && charCount > 0 && charsRead == bufferSize) + { + // The record is longer than the memory buffer. Increase the buffer. + bufferSize *= 2; + var tempBuffer = new char[bufferSize]; + buffer.CopyTo(tempBuffer, 0); + buffer = tempBuffer; + } - var value = cacheFields - ? fieldCache.GetField(processedField.Buffer, processedField.Start, processedField.Length) - : new string(processedField.Buffer, processedField.Start, processedField.Length); + var charsLeft = Math.Max(charsRead - rowStartPosition, 0); - processedFields[index] = value; - field.IsProcessed = true; + Array.Copy(buffer, rowStartPosition, buffer, 0, charsLeft); - return value; - } + fieldStartPosition -= rowStartPosition; + rowStartPosition = 0; + bufferPosition = charsLeft; - /// - /// Processes a field that complies with RFC4180. - /// - /// The start index of the field. - /// The length of the field. - /// The number of counted quotes. - /// The processed field. - protected ProcessedField ProcessRFC4180Field(int start, int length, int quoteCount) + charsRead = await reader.ReadAsync(buffer, charsLeft, buffer.Length - charsLeft).ConfigureAwait(false); + if (charsRead == 0) { - var newStart = start; - var newLength = length; + return false; + } - if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim) - { - ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); - } + charsRead += charsLeft; - if (quoteCount == 0) - { - // Not quoted. - // No processing needed. + return true; + } - return new ProcessedField(newStart, newLength, buffer); - } + private string GetField(int index) + { + if (index > fieldsPosition) + { + throw new IndexOutOfRangeException(); + } - if (buffer[newStart] != quote || buffer[newStart + newLength - 1] != quote || newLength == 1 && buffer[newStart] == quote) - { - // If the field doesn't have quotes on the ends, or the field is a single quote char, it's bad data. - return ProcessRFC4180BadField(start, length); - } + ref var field = ref fields[index]; - // Remove the quotes from the ends. - newStart += 1; - newLength -= 2; + if (field.Length == 0) + { + return string.Empty; + } - if ((trimOptions & TrimOptions.InsideQuotes) == TrimOptions.InsideQuotes) - { - ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); - } + if (field.IsProcessed) + { + return processedFields[index]; + } - if (quoteCount == 2) - { - // The only quotes are the ends of the field. - // No more processing is needed. - return new ProcessedField(newStart, newLength, buffer); - } + var start = field.Start + rowStartPosition; + var length = field.Length; + var quoteCount = field.QuoteCount; - if (newLength > processFieldBuffer.Length) - { - // Make sure the field processing buffer is large engough. - while (newLength > processFieldBufferSize) - { - processFieldBufferSize *= 2; - } + ProcessedField processedField; + switch (mode) + { + case CsvMode.RFC4180: + processedField = field.IsBad + ? ProcessRFC4180BadField(start, length) + : ProcessRFC4180Field(start, length, quoteCount); + break; + case CsvMode.Escape: + processedField = ProcessEscapeField(start, length); + break; + case CsvMode.NoEscape: + processedField = ProcessNoEscapeField(start, length); + break; + default: + throw new InvalidOperationException($"ParseMode '{mode}' is not handled."); + } - processFieldBuffer = new char[processFieldBufferSize]; - } + var value = cacheFields + ? fieldCache.GetField(processedField.Buffer, processedField.Start, processedField.Length) + : new string(processedField.Buffer, processedField.Start, processedField.Length); - // Remove escapes. - var inEscape = false; - var position = 0; - for (var i = newStart; i < newStart + newLength; i++) - { - var c = buffer[i]; + processedFields[index] = value; + field.IsProcessed = true; - if (inEscape) - { - inEscape = false; - } - else if (c == escape) - { - inEscape = true; + return value; + } - continue; - } + /// + /// Processes a field that complies with RFC4180. + /// + /// The start index of the field. + /// The length of the field. + /// The number of counted quotes. + /// The processed field. + protected ProcessedField ProcessRFC4180Field(int start, int length, int quoteCount) + { + var newStart = start; + var newLength = length; - processFieldBuffer[position] = c; - position++; - } + if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim) + { + ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); + } - return new ProcessedField(0, position, processFieldBuffer); + if (quoteCount == 0) + { + // Not quoted. + // No processing needed. + + return new ProcessedField(newStart, newLength, buffer); } - /// - /// Processes a field that does not comply with RFC4180. - /// - /// The start index of the field. - /// The length of the field. - /// The processed field. - protected ProcessedField ProcessRFC4180BadField(int start, int length) + if (buffer[newStart] != quote || buffer[newStart + newLength - 1] != quote || newLength == 1 && buffer[newStart] == quote) { - // If field is already known to be bad, different rules can be applied. + // If the field doesn't have quotes on the ends, or the field is a single quote char, it's bad data. + return ProcessRFC4180BadField(start, length); + } - var args = new BadDataFoundArgs(new string(buffer, start, length), RawRecord, Context); - badDataFound?.Invoke(args); + // Remove the quotes from the ends. + newStart += 1; + newLength -= 2; - var newStart = start; - var newLength = length; + if ((trimOptions & TrimOptions.InsideQuotes) == TrimOptions.InsideQuotes) + { + ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); + } - if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim) - { - ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); - } + if (quoteCount == 2) + { + // The only quotes are the ends of the field. + // No more processing is needed. + return new ProcessedField(newStart, newLength, buffer); + } - if (buffer[newStart] != quote) + if (newLength > processFieldBuffer.Length) + { + // Make sure the field processing buffer is large engough. + while (newLength > processFieldBufferSize) { - // If the field doesn't start with a quote, don't process it. - return new ProcessedField(newStart, newLength, buffer); + processFieldBufferSize *= 2; } - if (newLength > processFieldBuffer.Length) - { - // Make sure the field processing buffer is large engough. - while (newLength > processFieldBufferSize) - { - processFieldBufferSize *= 2; - } + processFieldBuffer = new char[processFieldBufferSize]; + } - processFieldBuffer = new char[processFieldBufferSize]; - } + // Remove escapes. + var inEscape = false; + var position = 0; + for (var i = newStart; i < newStart + newLength; i++) + { + var c = buffer[i]; - // Remove escapes until the last quote is found. - var inEscape = false; - var position = 0; - var c = '\0'; - var doneProcessing = false; - for (var i = newStart + 1; i < newStart + newLength; i++) + if (inEscape) + { + inEscape = false; + } + else if (c == escape) { - var cPrev = c; - c = buffer[i]; + inEscape = true; - // a,"b",c - // a,"b "" c",d - // a,"b "c d",e + continue; + } - if (inEscape) - { - inEscape = false; + processFieldBuffer[position] = c; + position++; + } - if (c == quote) - { - // Ignore the quote after an escape. - continue; - } - else if (cPrev == quote) - { - // The escape and quote are the same character. - // This is the end of the field. - // Don't process escapes for the rest of the field. - doneProcessing = true; - } - } + return new ProcessedField(0, position, processFieldBuffer); + } - if (c == escape && !doneProcessing) - { - inEscape = true; + /// + /// Processes a field that does not comply with RFC4180. + /// + /// The start index of the field. + /// The length of the field. + /// The processed field. + protected ProcessedField ProcessRFC4180BadField(int start, int length) + { + // If field is already known to be bad, different rules can be applied. - continue; - } + var args = new BadDataFoundArgs(new string(buffer, start, length), RawRecord, Context); + badDataFound?.Invoke(args); - processFieldBuffer[position] = c; - position++; - } + var newStart = start; + var newLength = length; - return new ProcessedField(0, position, processFieldBuffer); + if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim) + { + ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); } - /// - /// Processes an escaped field. - /// - /// The start index of the field. - /// The length of the field. - /// The processed field. - protected ProcessedField ProcessEscapeField(int start, int length) + if (buffer[newStart] != quote) { - var newStart = start; - var newLength = length; + // If the field doesn't start with a quote, don't process it. + return new ProcessedField(newStart, newLength, buffer); + } - if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim) + if (newLength > processFieldBuffer.Length) + { + // Make sure the field processing buffer is large engough. + while (newLength > processFieldBufferSize) { - ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); + processFieldBufferSize *= 2; } - if (newLength > processFieldBuffer.Length) - { - // Make sure the field processing buffer is large engough. - while (newLength > processFieldBufferSize) - { - processFieldBufferSize *= 2; - } + processFieldBuffer = new char[processFieldBufferSize]; + } - processFieldBuffer = new char[processFieldBufferSize]; - } + // Remove escapes until the last quote is found. + var inEscape = false; + var position = 0; + var c = '\0'; + var doneProcessing = false; + for (var i = newStart + 1; i < newStart + newLength; i++) + { + var cPrev = c; + c = buffer[i]; - // Remove escapes. - var inEscape = false; - var position = 0; - for (var i = newStart; i < newStart + newLength; i++) + // a,"b",c + // a,"b "" c",d + // a,"b "c d",e + + if (inEscape) { - var c = buffer[i]; + inEscape = false; - if (inEscape) + if (c == quote) { - inEscape = false; + // Ignore the quote after an escape. + continue; } - else if (c == escape) + else if (cPrev == quote) { - inEscape = true; - continue; + // The escape and quote are the same character. + // This is the end of the field. + // Don't process escapes for the rest of the field. + doneProcessing = true; } + } + + if (c == escape && !doneProcessing) + { + inEscape = true; - processFieldBuffer[position] = c; - position++; + continue; } - return new ProcessedField(0, position, processFieldBuffer); + processFieldBuffer[position] = c; + position++; } - /// - /// - /// Processes an non-escaped field. - /// - /// The start index of the field. - /// The length of the field. - /// The processed field. - protected ProcessedField ProcessNoEscapeField(int start, int length) + return new ProcessedField(0, position, processFieldBuffer); + } + + /// + /// Processes an escaped field. + /// + /// The start index of the field. + /// The length of the field. + /// The processed field. + protected ProcessedField ProcessEscapeField(int start, int length) + { + var newStart = start; + var newLength = length; + + if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim) { - var newStart = start; - var newLength = length; + ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); + } - if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim) + if (newLength > processFieldBuffer.Length) + { + // Make sure the field processing buffer is large engough. + while (newLength > processFieldBufferSize) { - ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); + processFieldBufferSize *= 2; } - return new ProcessedField(newStart, newLength, buffer); + processFieldBuffer = new char[processFieldBufferSize]; } - /// - public void Dispose() + // Remove escapes. + var inEscape = false; + var position = 0; + for (var i = newStart; i < newStart + newLength; i++) { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } + var c = buffer[i]; - /// - /// Disposes the object. - /// - /// Indicates if the object is being disposed. - protected virtual void Dispose(bool disposing) - { - if (disposed) + if (inEscape) { - return; + inEscape = false; } - - if (disposing) + else if (c == escape) { - // Dispose managed state (managed objects) - - if (!leaveOpen) - { - reader?.Dispose(); - } + inEscape = true; + continue; } - // Free unmanaged resources (unmanaged objects) and override finalizer - // Set large fields to null - - disposed = true; + processFieldBuffer[position] = c; + position++; } - /// - /// Processes a raw field based on configuration. - /// This will remove quotes, remove escapes, and trim if configured to. - /// - [DebuggerDisplay("Start = {Start}, Length = {Length}, Buffer.Length = {Buffer.Length}")] - protected readonly struct ProcessedField + return new ProcessedField(0, position, processFieldBuffer); + } + + /// + /// + /// Processes an non-escaped field. + /// + /// The start index of the field. + /// The length of the field. + /// The processed field. + protected ProcessedField ProcessNoEscapeField(int start, int length) + { + var newStart = start; + var newLength = length; + + if ((trimOptions & TrimOptions.Trim) == TrimOptions.Trim) { - /// - /// The start of the field in the buffer. - /// - public readonly int Start; - - /// - /// The length of the field in the buffer. - /// - public readonly int Length; - - /// - /// The buffer that contains the field. - /// - public readonly char[] Buffer; - - /// - /// Creates a new instance of ProcessedField. - /// - /// The start of the field in the buffer. - /// The length of the field in the buffer. - /// The buffer that contains the field. - public ProcessedField(int start, int length, char[] buffer) - { - Start = start; - Length = length; - Buffer = buffer; - } + ArrayHelper.Trim(buffer, ref newStart, ref newLength, whiteSpaceChars); } - private enum ReadLineResult + return new ProcessedField(newStart, newLength, buffer); + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the object. + /// + /// Indicates if the object is being disposed. + protected virtual void Dispose(bool disposing) + { + if (disposed) { - None = 0, - Complete, - Incomplete, + return; } - private enum ParserState + if (disposing) { - None = 0, - Spaces, - BlankLine, - Delimiter, - LineEnding, - NewLine, + // Dispose managed state (managed objects) + + if (!leaveOpen) + { + reader?.Dispose(); + } } - [DebuggerDisplay("Start = {Start}, Length = {Length}, QuoteCount = {QuoteCount}, IsBad = {IsBad}")] - private struct Field - { - /// - /// Starting position of the field. - /// This is an offset from . - /// - public int Start; + // Free unmanaged resources (unmanaged objects) and override finalizer + // Set large fields to null - public int Length; + disposed = true; + } - public int QuoteCount; + /// + /// Processes a raw field based on configuration. + /// This will remove quotes, remove escapes, and trim if configured to. + /// + [DebuggerDisplay("Start = {Start}, Length = {Length}, Buffer.Length = {Buffer.Length}")] + protected readonly struct ProcessedField + { + /// + /// The start of the field in the buffer. + /// + public readonly int Start; - public bool IsBad; + /// + /// The length of the field in the buffer. + /// + public readonly int Length; + + /// + /// The buffer that contains the field. + /// + public readonly char[] Buffer; - public bool IsProcessed; + /// + /// Creates a new instance of ProcessedField. + /// + /// The start of the field in the buffer. + /// The length of the field in the buffer. + /// The buffer that contains the field. + public ProcessedField(int start, int length, char[] buffer) + { + Start = start; + Length = length; + Buffer = buffer; } } + + private enum ReadLineResult + { + None = 0, + Complete, + Incomplete, + } + + private enum ParserState + { + None = 0, + Spaces, + BlankLine, + Delimiter, + LineEnding, + NewLine, + } + + [DebuggerDisplay("Start = {Start}, Length = {Length}, QuoteCount = {QuoteCount}, IsBad = {IsBad}")] + private struct Field + { + /// + /// Starting position of the field. + /// This is an offset from . + /// + public int Start; + + public int Length; + + public int QuoteCount; + + public bool IsBad; + + public bool IsProcessed; + } } diff --git a/src/CsvHelper/CsvReader.cs b/src/CsvHelper/CsvReader.cs index 6020d468f..4820340b3 100644 --- a/src/CsvHelper/CsvReader.cs +++ b/src/CsvHelper/CsvReader.cs @@ -5,747 +5,946 @@ using CsvHelper.Configuration; using CsvHelper.Expressions; using CsvHelper.TypeConversion; -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Reads data that was parsed from . +/// +public class CsvReader : IReader { + private readonly Lazy recordManager; + private readonly bool detectColumnCountChanges; + private readonly Dictionary> namedIndexes = new Dictionary>(); + private readonly Dictionary namedIndexCache = new Dictionary(); + private readonly Dictionary typeConverterOptionsCache = new Dictionary(); + private readonly MemberMapData reusableMemberMapData = new MemberMapData(null); + private readonly bool hasHeaderRecord; + private readonly HeaderValidated headerValidated; + private readonly ShouldSkipRecord? shouldSkipRecord; + private readonly ReadingExceptionOccurred readingExceptionOccurred; + private readonly CultureInfo cultureInfo; + private readonly bool ignoreBlankLines; + private readonly MissingFieldFound missingFieldFound; + private readonly bool includePrivateMembers; + private readonly PrepareHeaderForMatch prepareHeaderForMatch; + + private CsvContext context; + private bool disposed; + private IParser parser; + private int prevColumnCount; + private int currentIndex = -1; + private bool hasBeenRead; + private string[]? headerRecord; + + /// + public virtual int ColumnCount => parser.Count; + + /// + public virtual int CurrentIndex => currentIndex; + + /// + public virtual string[]? HeaderRecord => headerRecord; + + /// + public virtual CsvContext Context => context; + + /// + public virtual IReaderConfiguration Configuration { get; private set; } + + /// + public virtual IParser Parser => parser; + /// - /// Reads data that was parsed from . + /// Creates a new CSV reader using the given . /// - public class CsvReader : IReader - { - private readonly Lazy recordManager; - private readonly bool detectColumnCountChanges; - private readonly Dictionary> namedIndexes = new Dictionary>(); - private readonly Dictionary namedIndexCache = new Dictionary(); - private readonly Dictionary typeConverterOptionsCache = new Dictionary(); - private readonly MemberMapData reusableMemberMapData = new MemberMapData(null); - private readonly bool hasHeaderRecord; - private readonly HeaderValidated headerValidated; - private readonly ShouldSkipRecord shouldSkipRecord; - private readonly ReadingExceptionOccurred readingExceptionOccurred; - private readonly CultureInfo cultureInfo; - private readonly bool ignoreBlankLines; - private readonly MissingFieldFound missingFieldFound; - private readonly bool includePrivateMembers; - private readonly PrepareHeaderForMatch prepareHeaderForMatch; - - private CsvContext context; - private bool disposed; - private IParser parser; - private int prevColumnCount; - private int currentIndex = -1; - private bool hasBeenRead; - private string[] headerRecord; - - /// - public virtual int ColumnCount => parser.Count; - - /// - public virtual int CurrentIndex => currentIndex; - - /// - public virtual string[] HeaderRecord => headerRecord; - - /// - public virtual CsvContext Context => context; - - /// - public virtual IReaderConfiguration Configuration { get; private set; } - - /// - public virtual IParser Parser => parser; - - /// - /// Creates a new CSV reader using the given . - /// - /// The reader. - /// The culture. - /// true to leave the open after the object is disposed, otherwise false. - public CsvReader(TextReader reader, CultureInfo culture, bool leaveOpen = false) : this(new CsvParser(reader, culture, leaveOpen)) { } - - /// - /// Creates a new CSV reader using the given and - /// and as the default parser. - /// - /// The reader. - /// The configuration. - /// true to leave the open after the object is disposed, otherwise false. - public CsvReader(TextReader reader, IReaderConfiguration configuration, bool leaveOpen = false) : this(new CsvParser(reader, configuration, leaveOpen)) { } - - /// - /// Creates a new CSV reader using the given . - /// - /// The used to parse the CSV file. - public CsvReader(IParser parser) - { - Configuration = parser.Configuration as IReaderConfiguration ?? throw new ConfigurationException($"The {nameof(IParser)} configuration must implement {nameof(IReaderConfiguration)} to be used in {nameof(CsvReader)}."); - - this.parser = parser ?? throw new ArgumentNullException(nameof(parser)); - context = parser.Context ?? throw new InvalidOperationException($"For {nameof(IParser)} to be used in {nameof(CsvReader)}, {nameof(IParser.Context)} must also implement {nameof(CsvContext)}."); - context.Reader = this; - recordManager = new Lazy(() => ObjectResolver.Current.Resolve(this)); - - cultureInfo = Configuration.CultureInfo; - detectColumnCountChanges = Configuration.DetectColumnCountChanges; - hasHeaderRecord = Configuration.HasHeaderRecord; - headerValidated = Configuration.HeaderValidated; - ignoreBlankLines = Configuration.IgnoreBlankLines; - includePrivateMembers = Configuration.IncludePrivateMembers; - missingFieldFound = Configuration.MissingFieldFound; - prepareHeaderForMatch = Configuration.PrepareHeaderForMatch; - readingExceptionOccurred = Configuration.ReadingExceptionOccurred; - shouldSkipRecord = Configuration.ShouldSkipRecord; - } - - /// - public virtual bool ReadHeader() - { - if (!hasHeaderRecord) - { - throw new ReaderException(context, "Configuration.HasHeaderRecord is false."); - } + /// The reader. + /// The culture. + /// true to leave the open after the object is disposed, otherwise false. + public CsvReader(TextReader reader, CultureInfo culture, bool leaveOpen = false) : this(new CsvParser(reader, culture, leaveOpen)) { } - headerRecord = parser.Record; - ParseNamedIndexes(); + /// + /// Creates a new CSV reader using the given and + /// and as the default parser. + /// + /// The reader. + /// The configuration. + /// true to leave the open after the object is disposed, otherwise false. + public CsvReader(TextReader reader, IReaderConfiguration configuration, bool leaveOpen = false) : this(new CsvParser(reader, configuration, leaveOpen)) { } - return headerRecord != null; - } + /// + /// Creates a new CSV reader using the given . + /// + /// The used to parse the CSV file. + public CsvReader(IParser parser) + { + Configuration = parser.Configuration as IReaderConfiguration ?? throw new ConfigurationException($"The {nameof(IParser)} configuration must implement {nameof(IReaderConfiguration)} to be used in {nameof(CsvReader)}."); + + this.parser = parser ?? throw new ArgumentNullException(nameof(parser)); + context = parser.Context ?? throw new InvalidOperationException($"For {nameof(IParser)} to be used in {nameof(CsvReader)}, {nameof(IParser.Context)} must also implement {nameof(CsvContext)}."); + context.Reader = this; + recordManager = new Lazy(() => ObjectResolver.Current.Resolve(this)); + + cultureInfo = Configuration.CultureInfo; + detectColumnCountChanges = Configuration.DetectColumnCountChanges; + hasHeaderRecord = Configuration.HasHeaderRecord; + headerValidated = Configuration.HeaderValidated; + ignoreBlankLines = Configuration.IgnoreBlankLines; + includePrivateMembers = Configuration.IncludePrivateMembers; + missingFieldFound = Configuration.MissingFieldFound; + prepareHeaderForMatch = Configuration.PrepareHeaderForMatch; + readingExceptionOccurred = Configuration.ReadingExceptionOccurred; + shouldSkipRecord = Configuration.ShouldSkipRecord; + } - /// - /// Validates the header to be of the given type. - /// - /// The expected type of the header - public virtual void ValidateHeader() + /// + public virtual bool ReadHeader() + { + if (!hasHeaderRecord) { - ValidateHeader(typeof(T)); + throw new ReaderException(context, "Configuration.HasHeaderRecord is false."); } - /// - /// Validates the header to be of the given type. - /// - /// The expected type of the header. - public virtual void ValidateHeader(Type type) - { - if (hasHeaderRecord == false) - { - throw new InvalidOperationException($"Validation can't be performed on a the header if no header exists. {nameof(Configuration.HasHeaderRecord)} can't be false."); - } + headerRecord = parser.Record; + ParseNamedIndexes(); - CheckHasBeenRead(); + return headerRecord != null; + } - if (headerRecord == null) - { - throw new InvalidOperationException($"The header must be read before it can be validated."); - } + /// + /// Validates the header to be of the given type. + /// + /// The expected type of the header + public virtual void ValidateHeader() + { + ValidateHeader(typeof(T)); + } - if (context.Maps[type] == null) - { - context.Maps.Add(context.AutoMap(type)); - } + /// + /// Validates the header to be of the given type. + /// + /// The expected type of the header. + public virtual void ValidateHeader(Type type) + { + if (hasHeaderRecord == false) + { + throw new InvalidOperationException($"Validation can't be performed on a the header if no header exists. {nameof(Configuration.HasHeaderRecord)} can't be false."); + } - var map = context.Maps[type]; - var invalidHeaders = new List(); - ValidateHeader(map, invalidHeaders); + CheckHasBeenRead(); - var args = new HeaderValidatedArgs(invalidHeaders.ToArray(), context); - headerValidated?.Invoke(args); + if (headerRecord == null) + { + throw new InvalidOperationException($"The header must be read before it can be validated."); } - /// - /// Validates the header to be of the given type. - /// - /// The mapped classes. - /// The invalid headers. - protected virtual void ValidateHeader(ClassMap map, List invalidHeaders) + if (context.Maps[type] == null) { - foreach (var parameter in map.ParameterMaps) - { - if (parameter.Data.Ignore) - { - continue; - } + context.Maps.Add(context.AutoMap(type)); + } - if (parameter.Data.IsConstantSet) - { - // If ConvertUsing and Constant don't require a header. - continue; - } + var map = context.Maps[type]!; // The map was added above if null. + var invalidHeaders = new List(); + ValidateHeader(map, invalidHeaders); - if (parameter.Data.IsIndexSet && !parameter.Data.IsNameSet) - { - // If there is only an index set, we don't want to validate the header name. - continue; - } + var args = new HeaderValidatedArgs(invalidHeaders.ToArray(), context); + headerValidated?.Invoke(args); + } - if (parameter.ConstructorTypeMap != null) - { - ValidateHeader(parameter.ConstructorTypeMap, invalidHeaders); - } - else if (parameter.ReferenceMap != null) - { - ValidateHeader(parameter.ReferenceMap.Data.Mapping, invalidHeaders); - } - else - { - var index = GetFieldIndex(parameter.Data.Names, parameter.Data.NameIndex, true); - var isValid = index != -1 || parameter.Data.IsOptional; - if (!isValid) - { - invalidHeaders.Add(new InvalidHeader { Index = parameter.Data.NameIndex, Names = parameter.Data.Names.ToList() }); - } - } + /// + /// Validates the header to be of the given type. + /// + /// The mapped classes. + /// The invalid headers. + protected virtual void ValidateHeader(ClassMap map, List invalidHeaders) + { + foreach (var parameter in map.ParameterMaps) + { + if (parameter.Data.Ignore) + { + continue; } - foreach (var memberMap in map.MemberMaps) + if (parameter.Data.IsConstantSet) { - if (memberMap.Data.Ignore || !CanRead(memberMap)) - { - continue; - } - - if (memberMap.Data.ReadingConvertExpression != null || memberMap.Data.IsConstantSet) - { - // If ConvertUsing and Constant don't require a header. - continue; - } - - if (memberMap.Data.IsIndexSet && !memberMap.Data.IsNameSet) - { - // If there is only an index set, we don't want to validate the header name. - continue; - } - - var index = GetFieldIndex(memberMap.Data.Names, memberMap.Data.NameIndex, true); - var isValid = index != -1 || memberMap.Data.IsOptional; - if (!isValid) - { - invalidHeaders.Add(new InvalidHeader { Index = memberMap.Data.NameIndex, Names = memberMap.Data.Names.ToList() }); - } + // If ConvertUsing and Constant don't require a header. + continue; } - foreach (var referenceMap in map.ReferenceMaps) + if (parameter.Data.IsIndexSet && !parameter.Data.IsNameSet) { - if (!CanRead(referenceMap)) - { - continue; - } - - ValidateHeader(referenceMap.Data.Mapping, invalidHeaders); + // If there is only an index set, we don't want to validate the header name. + continue; } - } - - /// - public virtual bool Read() - { - // Don't forget about the async method below! - bool hasMoreRecords; - do + if (parameter.ConstructorTypeMap != null) { - hasMoreRecords = parser.Read(); - hasBeenRead = true; + ValidateHeader(parameter.ConstructorTypeMap, invalidHeaders); } - while (hasMoreRecords && (shouldSkipRecord?.Invoke(new ShouldSkipRecordArgs(this)) ?? false)); - - currentIndex = -1; - - if (detectColumnCountChanges && hasMoreRecords) + else if (parameter.ReferenceMap != null) { - if (prevColumnCount > 0 && prevColumnCount != parser.Count) + ValidateHeader(parameter.ReferenceMap.Data.Mapping, invalidHeaders); + } + else + { + var index = GetFieldIndex(parameter.Data.Names, parameter.Data.NameIndex, true); + var isValid = index != -1 || parameter.Data.IsOptional; + if (!isValid) { - var csvException = new BadDataException(string.Empty, parser.RawRecord, context, "An inconsistent number of columns has been detected."); - - var args = new ReadingExceptionOccurredArgs(csvException); - if (readingExceptionOccurred?.Invoke(args) ?? true) - { - throw csvException; - } + invalidHeaders.Add(new InvalidHeader { Index = parameter.Data.NameIndex, Names = parameter.Data.Names.ToList() }); } - - prevColumnCount = parser.Count; } - - return hasMoreRecords; } - /// - public virtual async Task ReadAsync() + foreach (var memberMap in map.MemberMaps) { - bool hasMoreRecords; - do + if (memberMap.Data.Ignore || !CanRead(memberMap)) { - hasMoreRecords = await parser.ReadAsync().ConfigureAwait(false); - hasBeenRead = true; + continue; } - while (hasMoreRecords && (shouldSkipRecord?.Invoke(new ShouldSkipRecordArgs(this)) ?? false)); - - currentIndex = -1; - if (detectColumnCountChanges && hasMoreRecords) + if (memberMap.Data.ReadingConvertExpression != null || memberMap.Data.IsConstantSet) { - if (prevColumnCount > 0 && prevColumnCount != parser.Count) - { - var csvException = new BadDataException(string.Empty, parser.RawRecord, context, "An inconsistent number of columns has been detected."); - - var args = new ReadingExceptionOccurredArgs(csvException); - if (readingExceptionOccurred?.Invoke(args) ?? true) - { - throw csvException; - } - } + // If ConvertUsing and Constant don't require a header. + continue; + } - prevColumnCount = parser.Count; + if (memberMap.Data.IsIndexSet && !memberMap.Data.IsNameSet) + { + // If there is only an index set, we don't want to validate the header name. + continue; } - return hasMoreRecords; + var index = GetFieldIndex(memberMap.Data.Names, memberMap.Data.NameIndex, true); + var isValid = index != -1 || memberMap.Data.IsOptional; + if (!isValid) + { + invalidHeaders.Add(new InvalidHeader { Index = memberMap.Data.NameIndex, Names = memberMap.Data.Names.ToList() }); + } } - /// - public virtual string this[int index] + foreach (var referenceMap in map.ReferenceMaps) { - get + if (!CanRead(referenceMap)) { - CheckHasBeenRead(); - - return GetField(index); + continue; } + + ValidateHeader(referenceMap.Data.Mapping, invalidHeaders); } + } - /// - public virtual string this[string name] - { - get - { - CheckHasBeenRead(); + /// + public virtual bool Read() + { + // Don't forget about the async method below! - return GetField(name); - } + bool hasMoreRecords; + do + { + hasMoreRecords = parser.Read(); + hasBeenRead = true; } + while (hasMoreRecords && (shouldSkipRecord?.Invoke(new ShouldSkipRecordArgs(this)) ?? false)); + + currentIndex = -1; - /// - public virtual string this[string name, int index] + if (detectColumnCountChanges && hasMoreRecords) { - get + if (prevColumnCount > 0 && prevColumnCount != parser.Count) { - CheckHasBeenRead(); + var csvException = new BadDataException(string.Empty, parser.RawRecord, context, "An inconsistent number of columns has been detected."); - return GetField(name, index); + var args = new ReadingExceptionOccurredArgs(csvException); + if (readingExceptionOccurred?.Invoke(args) ?? true) + { + throw csvException; + } } + + prevColumnCount = parser.Count; } - /// - public virtual string GetField(int index) + return hasMoreRecords; + } + + /// + public virtual async Task ReadAsync() + { + bool hasMoreRecords; + do { - CheckHasBeenRead(); + hasMoreRecords = await parser.ReadAsync().ConfigureAwait(false); + hasBeenRead = true; + } + while (hasMoreRecords && (shouldSkipRecord?.Invoke(new ShouldSkipRecordArgs(this)) ?? false)); - // Set the current index being used so we - // have more information if an error occurs - // when reading records. - currentIndex = index; + currentIndex = -1; - if (index >= parser.Count || index < 0) + if (detectColumnCountChanges && hasMoreRecords) + { + if (prevColumnCount > 0 && prevColumnCount != parser.Count) { - var args = new MissingFieldFoundArgs(null, index, context); - missingFieldFound?.Invoke(args); - return default; - } + var csvException = new BadDataException(string.Empty, parser.RawRecord, context, "An inconsistent number of columns has been detected."); - var field = parser[index]; + var args = new ReadingExceptionOccurredArgs(csvException); + if (readingExceptionOccurred?.Invoke(args) ?? true) + { + throw csvException; + } + } - return field; + prevColumnCount = parser.Count; } - /// - public virtual string GetField(string name) + return hasMoreRecords; + } + + /// + public virtual string? this[int index] + { + get { CheckHasBeenRead(); - var index = GetFieldIndex(name); - if (index < 0) - { - return null; - } - return GetField(index); } + } - /// - public virtual string GetField(string name, int index) + /// + public virtual string? this[string name] + { + get { CheckHasBeenRead(); - var fieldIndex = GetFieldIndex(name, index); - if (fieldIndex < 0) - { - return null; - } - - return GetField(fieldIndex); + return GetField(name); } + } - /// - public virtual object GetField(Type type, int index) + /// + public virtual string? this[string name, int index] + { + get { CheckHasBeenRead(); - var converter = context.TypeConverterCache.GetConverter(type); - return GetField(type, index, converter); + return GetField(name, index); } + } - /// - public virtual object GetField(Type type, string name) - { - CheckHasBeenRead(); + /// + public virtual string? GetField(int index) + { + CheckHasBeenRead(); - var converter = context.TypeConverterCache.GetConverter(type); - return GetField(type, name, converter); - } + // Set the current index being used so we + // have more information if an error occurs + // when reading records. + currentIndex = index; - /// - public virtual object GetField(Type type, string name, int index) + if (index >= parser.Count || index < 0) { - CheckHasBeenRead(); - - var converter = context.TypeConverterCache.GetConverter(type); - return GetField(type, name, index, converter); + var args = new MissingFieldFoundArgs(null, index, context); + missingFieldFound?.Invoke(args); + return default; } - /// - public virtual object GetField(Type type, int index, ITypeConverter converter) - { - CheckHasBeenRead(); + var field = parser[index]; - reusableMemberMapData.Index = index; - reusableMemberMapData.TypeConverter = converter; - if (!typeConverterOptionsCache.TryGetValue(type, out TypeConverterOptions typeConverterOptions)) - { - typeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = cultureInfo }, context.TypeConverterOptionsCache.GetOptions(type)); - typeConverterOptionsCache.Add(type, typeConverterOptions); - } + return field; + } - reusableMemberMapData.TypeConverterOptions = typeConverterOptions; + /// + public virtual string? GetField(string name) + { + CheckHasBeenRead(); - var field = GetField(index); - return converter.ConvertFromString(field, this, reusableMemberMapData); + var index = GetFieldIndex(name); + if (index < 0) + { + return null; } - /// - public virtual object GetField(Type type, string name, ITypeConverter converter) - { - CheckHasBeenRead(); + return GetField(index); + } - var index = GetFieldIndex(name); - return GetField(type, index, converter); - } + /// + public virtual string? GetField(string name, int index) + { + CheckHasBeenRead(); - /// - public virtual object GetField(Type type, string name, int index, ITypeConverter converter) + var fieldIndex = GetFieldIndex(name, index); + if (fieldIndex < 0) { - CheckHasBeenRead(); - - var fieldIndex = GetFieldIndex(name, index); - return GetField(type, fieldIndex, converter); + return null; } - /// - public virtual T GetField(int index) - { - CheckHasBeenRead(); + return GetField(fieldIndex); + } - var converter = context.TypeConverterCache.GetConverter(); - return GetField(index, converter); - } + /// + public virtual object? GetField(Type type, int index) + { + CheckHasBeenRead(); - /// - public virtual T GetField(string name) - { - CheckHasBeenRead(); + var converter = context.TypeConverterCache.GetConverter(type); + return GetField(type, index, converter); + } - var converter = context.TypeConverterCache.GetConverter(); - return GetField(name, converter); - } + /// + public virtual object? GetField(Type type, string name) + { + CheckHasBeenRead(); - /// - public virtual T GetField(string name, int index) - { - CheckHasBeenRead(); + var converter = context.TypeConverterCache.GetConverter(type); + return GetField(type, name, converter); + } - var converter = context.TypeConverterCache.GetConverter(); - return GetField(name, index, converter); - } + /// + public virtual object? GetField(Type type, string name, int index) + { + CheckHasBeenRead(); + + var converter = context.TypeConverterCache.GetConverter(type); + return GetField(type, name, index, converter); + } + + /// + public virtual object? GetField(Type type, int index, ITypeConverter converter) + { + CheckHasBeenRead(); - /// - public virtual T GetField(int index, ITypeConverter converter) + reusableMemberMapData.Index = index; + reusableMemberMapData.TypeConverter = converter; + if (!typeConverterOptionsCache.TryGetValue(type, out TypeConverterOptions? typeConverterOptions)) { - CheckHasBeenRead(); + typeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = cultureInfo }, context.TypeConverterOptionsCache.GetOptions(type)); + typeConverterOptionsCache.Add(type, typeConverterOptions); + } - if (index >= parser.Count || index < 0) - { - currentIndex = index; - var args = new MissingFieldFoundArgs(null, index, context); - missingFieldFound?.Invoke(args); + reusableMemberMapData.TypeConverterOptions = typeConverterOptions; - return default; - } + var field = GetField(index); + return converter.ConvertFromString(field, this, reusableMemberMapData); + } - return (T)GetField(typeof(T), index, converter); - } + /// + public virtual object? GetField(Type type, string name, ITypeConverter converter) + { + CheckHasBeenRead(); - /// - public virtual T GetField(string name, ITypeConverter converter) - { - CheckHasBeenRead(); + var index = GetFieldIndex(name); + return GetField(type, index, converter); + } - var index = GetFieldIndex(name); - return GetField(index, converter); - } + /// + public virtual object? GetField(Type type, string name, int index, ITypeConverter converter) + { + CheckHasBeenRead(); - /// - public virtual T GetField(string name, int index, ITypeConverter converter) - { - CheckHasBeenRead(); + var fieldIndex = GetFieldIndex(name, index); + return GetField(type, fieldIndex, converter); + } - var fieldIndex = GetFieldIndex(name, index); - return GetField(fieldIndex, converter); - } + /// + public virtual T? GetField(int index) + { + CheckHasBeenRead(); - /// - public virtual T GetField(int index) where TConverter : ITypeConverter - { - CheckHasBeenRead(); + var converter = context.TypeConverterCache.GetConverter(); + return GetField(index, converter); + } - var converter = ObjectResolver.Current.Resolve(); - return GetField(index, converter); - } + /// + public virtual T? GetField(string name) + { + CheckHasBeenRead(); - /// - public virtual T GetField(string name) where TConverter : ITypeConverter - { - CheckHasBeenRead(); + var converter = context.TypeConverterCache.GetConverter(); + return GetField(name, converter); + } - var converter = ObjectResolver.Current.Resolve(); - return GetField(name, converter); - } + /// + public virtual T? GetField(string name, int index) + { + CheckHasBeenRead(); - /// - public virtual T GetField(string name, int index) where TConverter : ITypeConverter - { - CheckHasBeenRead(); + var converter = context.TypeConverterCache.GetConverter(); + return GetField(name, index, converter); + } - var converter = ObjectResolver.Current.Resolve(); - return GetField(name, index, converter); - } + /// + public virtual T? GetField(int index, ITypeConverter converter) + { + CheckHasBeenRead(); - /// - public virtual bool TryGetField(Type type, int index, out object field) + if (index >= parser.Count || index < 0) { - CheckHasBeenRead(); + currentIndex = index; + var args = new MissingFieldFoundArgs(null, index, context); + missingFieldFound?.Invoke(args); - var converter = context.TypeConverterCache.GetConverter(type); - return TryGetField(type, index, converter, out field); + return default; } - /// - public virtual bool TryGetField(Type type, string name, out object field) + return (T?)GetField(typeof(T), index, converter); + } + + /// + public virtual T? GetField(string name, ITypeConverter converter) + { + CheckHasBeenRead(); + + var index = GetFieldIndex(name); + return GetField(index, converter); + } + + /// + public virtual T? GetField(string name, int index, ITypeConverter converter) + { + CheckHasBeenRead(); + + var fieldIndex = GetFieldIndex(name, index); + return GetField(fieldIndex, converter); + } + + /// + public virtual T? GetField(int index) where TConverter : ITypeConverter + { + CheckHasBeenRead(); + + var converter = ObjectResolver.Current.Resolve(); + return GetField(index, converter); + } + + /// + public virtual T? GetField(string name) where TConverter : ITypeConverter + { + CheckHasBeenRead(); + + var converter = ObjectResolver.Current.Resolve(); + return GetField(name, converter); + } + + /// + public virtual T? GetField(string name, int index) where TConverter : ITypeConverter + { + CheckHasBeenRead(); + + var converter = ObjectResolver.Current.Resolve(); + return GetField(name, index, converter); + } + + /// + public virtual bool TryGetField(Type type, int index, out object? field) + { + CheckHasBeenRead(); + + var converter = context.TypeConverterCache.GetConverter(type); + return TryGetField(type, index, converter, out field); + } + + /// + public virtual bool TryGetField(Type type, string name, out object? field) + { + CheckHasBeenRead(); + + var converter = context.TypeConverterCache.GetConverter(type); + return TryGetField(type, name, converter, out field); + } + + /// + public virtual bool TryGetField(Type type, string name, int index, out object? field) + { + CheckHasBeenRead(); + + var converter = context.TypeConverterCache.GetConverter(type); + return TryGetField(type, name, index, converter, out field); + } + + /// + public virtual bool TryGetField(Type type, int index, ITypeConverter converter, out object? field) + { + CheckHasBeenRead(); + + // TypeConverter.IsValid() just wraps a + // ConvertFrom() call in a try/catch, so lets not + // do it twice and just do it ourselves. + try { - CheckHasBeenRead(); + field = GetField(type, index, converter); + return true; + } + catch + { + field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null; + return false; + } + } + + /// + public virtual bool TryGetField(Type type, string name, ITypeConverter converter, out object? field) + { + CheckHasBeenRead(); - var converter = context.TypeConverterCache.GetConverter(type); - return TryGetField(type, name, converter, out field); + var index = GetFieldIndex(name, isTryGet: true); + if (index == -1) + { + field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null; + return false; } - /// - public virtual bool TryGetField(Type type, string name, int index, out object field) + return TryGetField(type, index, converter, out field); + } + + /// + public virtual bool TryGetField(Type type, string name, int index, ITypeConverter converter, out object? field) + { + CheckHasBeenRead(); + + var fieldIndex = GetFieldIndex(name, index, true); + if (fieldIndex == -1) { - CheckHasBeenRead(); + field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null; + return false; + } + + return TryGetField(type, fieldIndex, converter, out field); + } + + /// + public virtual bool TryGetField(int index, out T? field) + { + CheckHasBeenRead(); + + var converter = context.TypeConverterCache.GetConverter(); + return TryGetField(index, converter, out field); + } + + /// + public virtual bool TryGetField(string name, out T? field) + { + CheckHasBeenRead(); + + var converter = context.TypeConverterCache.GetConverter(); + return TryGetField(name, converter, out field); + } + + /// + public virtual bool TryGetField(string name, int index, out T? field) + { + CheckHasBeenRead(); + + var converter = context.TypeConverterCache.GetConverter(); + return TryGetField(name, index, converter, out field); + } - var converter = context.TypeConverterCache.GetConverter(type); - return TryGetField(type, name, index, converter, out field); + /// + public virtual bool TryGetField(int index, ITypeConverter converter, out T? field) + { + CheckHasBeenRead(); + + // TypeConverter.IsValid() just wraps a + // ConvertFrom() call in a try/catch, so lets not + // do it twice and just do it ourselves. + try + { + field = GetField(index, converter); + return true; } + catch + { + field = default; + return false; + } + } - /// - public virtual bool TryGetField(Type type, int index, ITypeConverter converter, out object field) + /// + public virtual bool TryGetField(string name, ITypeConverter converter, out T? field) + { + CheckHasBeenRead(); + + var index = GetFieldIndex(name, isTryGet: true); + if (index == -1) { - CheckHasBeenRead(); + field = default; + return false; + } - // TypeConverter.IsValid() just wraps a - // ConvertFrom() call in a try/catch, so lets not - // do it twice and just do it ourselves. - try - { - field = GetField(type, index, converter); - return true; - } - catch - { - field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null; - return false; - } + return TryGetField(index, converter, out field); + } + + /// + public virtual bool TryGetField(string name, int index, ITypeConverter converter, out T? field) + { + CheckHasBeenRead(); + + var fieldIndex = GetFieldIndex(name, index, true); + if (fieldIndex == -1) + { + field = default; + return false; } - /// - public virtual bool TryGetField(Type type, string name, ITypeConverter converter, out object field) + return TryGetField(fieldIndex, converter, out field); + } + + /// + public virtual bool TryGetField(int index, out T? field) where TConverter : ITypeConverter + { + CheckHasBeenRead(); + + var converter = ObjectResolver.Current.Resolve(); + return TryGetField(index, converter, out field); + } + + /// + public virtual bool TryGetField(string name, out T? field) where TConverter : ITypeConverter + { + CheckHasBeenRead(); + + var converter = ObjectResolver.Current.Resolve(); + return TryGetField(name, converter, out field); + } + + /// + public virtual bool TryGetField(string name, int index, out T? field) where TConverter : ITypeConverter + { + CheckHasBeenRead(); + + var converter = ObjectResolver.Current.Resolve(); + return TryGetField(name, index, converter, out field); + } + + /// + public virtual T? GetRecord() + { + CheckHasBeenRead(); + + if (headerRecord == null && hasHeaderRecord) { - CheckHasBeenRead(); + ReadHeader(); + ValidateHeader(); - var index = GetFieldIndex(name, isTryGet: true); - if (index == -1) + if (!Read()) { - field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null; - return false; + return default; } - - return TryGetField(type, index, converter, out field); } - /// - public virtual bool TryGetField(Type type, string name, int index, ITypeConverter converter, out object field) + T? record; + try { - CheckHasBeenRead(); + var read = recordManager.Value.GetReadDelegate(typeof(T)); + record = read(); + } + catch (Exception ex) + { + var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); - var fieldIndex = GetFieldIndex(name, index, true); - if (fieldIndex == -1) + var args = new ReadingExceptionOccurredArgs(csvHelperException); + if (readingExceptionOccurred?.Invoke(args) ?? true) { - field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null; - return false; + if (ex is CsvHelperException) + { + throw; + } + else + { + throw csvHelperException; + } } - return TryGetField(type, fieldIndex, converter, out field); + record = default; } - /// - public virtual bool TryGetField(int index, out T field) - { - CheckHasBeenRead(); + return record; + } - var converter = context.TypeConverterCache.GetConverter(); - return TryGetField(index, converter, out field); + /// + public virtual T? GetRecord(T anonymousTypeDefinition) + { + if (anonymousTypeDefinition == null) + { + throw new ArgumentNullException(nameof(anonymousTypeDefinition)); } - /// - public virtual bool TryGetField(string name, out T field) + if (!anonymousTypeDefinition.GetType().IsAnonymous()) { - CheckHasBeenRead(); - - var converter = context.TypeConverterCache.GetConverter(); - return TryGetField(name, converter, out field); + throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition)); } - /// - public virtual bool TryGetField(string name, int index, out T field) - { - CheckHasBeenRead(); + return GetRecord(); + } - var converter = context.TypeConverterCache.GetConverter(); - return TryGetField(name, index, converter, out field); - } + /// + public virtual object? GetRecord(Type type) + { + CheckHasBeenRead(); - /// - public virtual bool TryGetField(int index, ITypeConverter converter, out T field) + if (headerRecord == null && hasHeaderRecord) { - CheckHasBeenRead(); + ReadHeader(); + ValidateHeader(type); - // TypeConverter.IsValid() just wraps a - // ConvertFrom() call in a try/catch, so lets not - // do it twice and just do it ourselves. - try + if (!Read()) { - field = GetField(index, converter); - return true; + return null; } - catch + } + + object? record; + try + { + var read = recordManager.Value.GetReadDelegate(type); + record = read(); + } + catch (Exception ex) + { + var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); + + var args = new ReadingExceptionOccurredArgs(csvHelperException); + if (readingExceptionOccurred?.Invoke(args) ?? true) { - field = default; - return false; + if (ex is CsvHelperException) + { + throw; + } + else + { + throw csvHelperException; + } } + + record = default; } - /// - public virtual bool TryGetField(string name, ITypeConverter converter, out T field) + return record; + } + + /// + public virtual IEnumerable GetRecords() + { + if (disposed) { - CheckHasBeenRead(); + throw new ObjectDisposedException(nameof(CsvReader), + "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + + "you try and access the values. e.g. .ToList() Did you create CsvReader inside a using block and are now trying to access " + + "the records outside of that using block?" + ); + } - var index = GetFieldIndex(name, isTryGet: true); - if (index == -1) + // Don't need to check if it's been read + // since we're doing the reading ourselves. + + if (hasHeaderRecord && headerRecord == null) + { + if (!Read()) { - field = default; - return false; + yield break; } - return TryGetField(index, converter, out field); + ReadHeader(); + ValidateHeader(); } - /// - public virtual bool TryGetField(string name, int index, ITypeConverter converter, out T field) + Func? read = null; + + while (Read()) { - CheckHasBeenRead(); + T record; + try + { + if (read == null) + { + read = recordManager.Value.GetReadDelegate(typeof(T)); + } - var fieldIndex = GetFieldIndex(name, index, true); - if (fieldIndex == -1) + record = read(); + } + catch (Exception ex) { - field = default; - return false; + var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); + + var args = new ReadingExceptionOccurredArgs(csvHelperException); + if (readingExceptionOccurred?.Invoke(args) ?? true) + { + if (ex is CsvHelperException) + { + throw; + } + else + { + throw csvHelperException; + } + } + + // If the callback doesn't throw, keep going. + continue; } - return TryGetField(fieldIndex, converter, out field); + yield return record; } + } - /// - public virtual bool TryGetField(int index, out T field) where TConverter : ITypeConverter + /// + public virtual IEnumerable GetRecords(T anonymousTypeDefinition) + { + if (anonymousTypeDefinition == null) { - CheckHasBeenRead(); - - var converter = ObjectResolver.Current.Resolve(); - return TryGetField(index, converter, out field); + throw new ArgumentNullException(nameof(anonymousTypeDefinition)); } - /// - public virtual bool TryGetField(string name, out T field) where TConverter : ITypeConverter + if (!anonymousTypeDefinition.GetType().IsAnonymous()) { - CheckHasBeenRead(); + throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition)); + } - var converter = ObjectResolver.Current.Resolve(); - return TryGetField(name, converter, out field); + return GetRecords(); + } + + /// + public virtual IEnumerable GetRecords(Type type) + { + if (disposed) + { + throw new ObjectDisposedException(nameof(CsvReader), + "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + + "you try and access the values. e.g. .ToList() Did you create CsvReader inside a using block and are now trying to access " + + "the records outside of that using block?" + ); } - /// - public virtual bool TryGetField(string name, int index, out T field) where TConverter : ITypeConverter + // Don't need to check if it's been read + // since we're doing the reading ourselves. + + if (hasHeaderRecord && headerRecord == null) { - CheckHasBeenRead(); + if (!Read()) + { + yield break; + } - var converter = ObjectResolver.Current.Resolve(); - return TryGetField(name, index, converter, out field); + ReadHeader(); + ValidateHeader(type); } - /// - public virtual T GetRecord() - { - CheckHasBeenRead(); + Func? read = null; - if (headerRecord == null && hasHeaderRecord) + while (Read()) + { + object record; + try { - ReadHeader(); - ValidateHeader(); - - if (!Read()) + if (read == null) { - return default; + read = recordManager.Value.GetReadDelegate(type); } - } - T record; - try - { - var read = recordManager.Value.GetReadDelegate(typeof(T)); record = read(); } catch (Exception ex) @@ -765,49 +964,45 @@ record = read(); } } - record = default; + // If the callback doesn't throw, keep going. + continue; } - return record; + yield return record; } + } - /// - public virtual T GetRecord(T anonymousTypeDefinition) + /// + public virtual IEnumerable EnumerateRecords(T record) + { + if (disposed) { - if (anonymousTypeDefinition == null) - { - throw new ArgumentNullException(nameof(anonymousTypeDefinition)); - } + throw new ObjectDisposedException(nameof(CsvReader), + "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + + "you try and access the values. e.g. .ToList() Did you create CsvReader inside a using block and are now trying to access " + + "the records outside of that using block?" + ); + } - if (!anonymousTypeDefinition.GetType().IsAnonymous()) + // Don't need to check if it's been read + // since we're doing the reading ourselves. + + if (hasHeaderRecord && headerRecord == null) + { + if (!Read()) { - throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition)); + yield break; } - return GetRecord(); + ReadHeader(); + ValidateHeader(); } - /// - public virtual object GetRecord(Type type) + while (Read()) { - CheckHasBeenRead(); - - if (headerRecord == null && hasHeaderRecord) - { - ReadHeader(); - ValidateHeader(type); - - if (!Read()) - { - return null; - } - } - - object record; try { - var read = recordManager.Value.GetReadDelegate(type); - record = read(); + recordManager.Value.Hydrate(record); } catch (Exception ex) { @@ -826,620 +1021,417 @@ record = read(); } } - record = default; + // If the callback doesn't throw, keep going. + continue; } - return record; + yield return record; } + } - /// - public virtual IEnumerable GetRecords() + /// + public virtual async IAsyncEnumerable GetRecordsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default(CancellationToken)) + { + if (disposed) { - if (disposed) - { - throw new ObjectDisposedException(nameof(CsvReader), - "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + - "you try and access the values. e.g. .ToList() Did you create CsvReader inside a using block and are now trying to access " + - "the records outside of that using block?" - ); - } - - // Don't need to check if it's been read - // since we're doing the reading ourselves. - - if (hasHeaderRecord && headerRecord == null) - { - if (!Read()) - { - yield break; - } - - ReadHeader(); - ValidateHeader(); - } - - Func read = null; - - while (Read()) - { - T record; - try - { - if (read == null) - { - read = recordManager.Value.GetReadDelegate(typeof(T)); - } - - record = read(); - } - catch (Exception ex) - { - var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); - - var args = new ReadingExceptionOccurredArgs(csvHelperException); - if (readingExceptionOccurred?.Invoke(args) ?? true) - { - if (ex is CsvHelperException) - { - throw; - } - else - { - throw csvHelperException; - } - } - - // If the callback doesn't throw, keep going. - continue; - } - - yield return record; - } + throw new ObjectDisposedException(nameof(CsvReader), + "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + + "you try and access the values. Did you create CsvReader inside a using block and are now trying to access " + + "the records outside of that using block?" + ); } - /// - public virtual IEnumerable GetRecords(T anonymousTypeDefinition) - { - if (anonymousTypeDefinition == null) - { - throw new ArgumentNullException(nameof(anonymousTypeDefinition)); - } + // Don't need to check if it's been read + // since we're doing the reading ourselves. - if (!anonymousTypeDefinition.GetType().IsAnonymous()) + if (hasHeaderRecord && headerRecord == null) + { + if (!await ReadAsync().ConfigureAwait(false)) { - throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition)); + yield break; } - return GetRecords(); + ReadHeader(); + ValidateHeader(); } - /// - public virtual IEnumerable GetRecords(Type type) - { - if (disposed) - { - throw new ObjectDisposedException(nameof(CsvReader), - "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + - "you try and access the values. e.g. .ToList() Did you create CsvReader inside a using block and are now trying to access " + - "the records outside of that using block?" - ); - } - - // Don't need to check if it's been read - // since we're doing the reading ourselves. + Func? read = null; - if (hasHeaderRecord && headerRecord == null) + while (await ReadAsync().ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + T record; + try { - if (!Read()) + if (read == null) { - yield break; + read = recordManager.Value.GetReadDelegate(typeof(T)); } - ReadHeader(); - ValidateHeader(type); + record = read(); } - - Func read = null; - - while (Read()) + catch (Exception ex) { - object record; - try + var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); + + var args = new ReadingExceptionOccurredArgs(csvHelperException); + if (readingExceptionOccurred?.Invoke(args) ?? true) { - if (read == null) + if (ex is CsvHelperException) { - read = recordManager.Value.GetReadDelegate(type); + throw; } - - record = read(); - } - catch (Exception ex) - { - var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); - - var args = new ReadingExceptionOccurredArgs(csvHelperException); - if (readingExceptionOccurred?.Invoke(args) ?? true) + else { - if (ex is CsvHelperException) - { - throw; - } - else - { - throw csvHelperException; - } + throw csvHelperException; } - - // If the callback doesn't throw, keep going. - continue; } - yield return record; + // If the callback doesn't throw, keep going. + continue; } + + yield return record; } + } - /// - public virtual IEnumerable EnumerateRecords(T record) + /// + public virtual IAsyncEnumerable GetRecordsAsync(T anonymousTypeDefinition, CancellationToken cancellationToken = default) + { + if (anonymousTypeDefinition == null) { - if (disposed) - { - throw new ObjectDisposedException(nameof(CsvReader), - "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + - "you try and access the values. e.g. .ToList() Did you create CsvReader inside a using block and are now trying to access " + - "the records outside of that using block?" - ); - } - - // Don't need to check if it's been read - // since we're doing the reading ourselves. - - if (hasHeaderRecord && headerRecord == null) - { - if (!Read()) - { - yield break; - } - - ReadHeader(); - ValidateHeader(); - } - - while (Read()) - { - try - { - recordManager.Value.Hydrate(record); - } - catch (Exception ex) - { - var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); + throw new ArgumentNullException(nameof(anonymousTypeDefinition)); + } - var args = new ReadingExceptionOccurredArgs(csvHelperException); - if (readingExceptionOccurred?.Invoke(args) ?? true) - { - if (ex is CsvHelperException) - { - throw; - } - else - { - throw csvHelperException; - } - } + if (!anonymousTypeDefinition.GetType().IsAnonymous()) + { + throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition)); + } - // If the callback doesn't throw, keep going. - continue; - } + return GetRecordsAsync(cancellationToken); + } - yield return record; - } + /// + public virtual async IAsyncEnumerable GetRecordsAsync(Type type, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (disposed) + { + throw new ObjectDisposedException(nameof(CsvReader), + "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + + "you try and access the values. Did you create CsvReader inside a using block and are now trying to access " + + "the records outside of that using block?" + ); } - /// - public virtual async IAsyncEnumerable GetRecordsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default(CancellationToken)) + // Don't need to check if it's been read + // since we're doing the reading ourselves. + + if (hasHeaderRecord && headerRecord == null) { - if (disposed) + if (!await ReadAsync().ConfigureAwait(false)) { - throw new ObjectDisposedException(nameof(CsvReader), - "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + - "you try and access the values. Did you create CsvReader inside a using block and are now trying to access " + - "the records outside of that using block?" - ); + yield break; } - // Don't need to check if it's been read - // since we're doing the reading ourselves. + ReadHeader(); + ValidateHeader(type); + } + + Func? read = null; - if (hasHeaderRecord && headerRecord == null) + while (await ReadAsync().ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + object record; + try { - if (!await ReadAsync().ConfigureAwait(false)) + if (read == null) { - yield break; + read = recordManager.Value.GetReadDelegate(type); } - ReadHeader(); - ValidateHeader(); + record = read(); } - - Func read = null; - - while (await ReadAsync().ConfigureAwait(false)) + catch (Exception ex) { - cancellationToken.ThrowIfCancellationRequested(); - T record; - try + var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); + + var args = new ReadingExceptionOccurredArgs(csvHelperException); + if (readingExceptionOccurred?.Invoke(args) ?? true) { - if (read == null) + if (ex is CsvHelperException) { - read = recordManager.Value.GetReadDelegate(typeof(T)); + throw; } - - record = read(); - } - catch (Exception ex) - { - var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); - - var args = new ReadingExceptionOccurredArgs(csvHelperException); - if (readingExceptionOccurred?.Invoke(args) ?? true) + else { - if (ex is CsvHelperException) - { - throw; - } - else - { - throw csvHelperException; - } + throw csvHelperException; } - - // If the callback doesn't throw, keep going. - continue; } - yield return record; + // If the callback doesn't throw, keep going. + continue; } + + yield return record; } + } - /// - public virtual IAsyncEnumerable GetRecordsAsync(T anonymousTypeDefinition, CancellationToken cancellationToken = default) + /// + public virtual async IAsyncEnumerable EnumerateRecordsAsync(T record, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (disposed) { - if (anonymousTypeDefinition == null) - { - throw new ArgumentNullException(nameof(anonymousTypeDefinition)); - } + throw new ObjectDisposedException(nameof(CsvReader), + "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + + "you try and access the values. Did you create CsvReader inside a using block and are now trying to access " + + "the records outside of that using block?" + ); + } + + // Don't need to check if it's been read + // since we're doing the reading ourselves. - if (!anonymousTypeDefinition.GetType().IsAnonymous()) + if (hasHeaderRecord && headerRecord == null) + { + if (!await ReadAsync().ConfigureAwait(false)) { - throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition)); + yield break; } - return GetRecordsAsync(cancellationToken); + ReadHeader(); + ValidateHeader(); } - /// - public virtual async IAsyncEnumerable GetRecordsAsync(Type type, [EnumeratorCancellation] CancellationToken cancellationToken = default) + while (await ReadAsync().ConfigureAwait(false)) { - if (disposed) + cancellationToken.ThrowIfCancellationRequested(); + try { - throw new ObjectDisposedException(nameof(CsvReader), - "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + - "you try and access the values. Did you create CsvReader inside a using block and are now trying to access " + - "the records outside of that using block?" - ); + recordManager.Value.Hydrate(record); } - - // Don't need to check if it's been read - // since we're doing the reading ourselves. - - if (hasHeaderRecord && headerRecord == null) + catch (Exception ex) { - if (!await ReadAsync().ConfigureAwait(false)) - { - yield break; - } - - ReadHeader(); - ValidateHeader(type); - } - - Func read = null; + var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); - while (await ReadAsync().ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - object record; - try + var args = new ReadingExceptionOccurredArgs(csvHelperException); + if (readingExceptionOccurred?.Invoke(args) ?? true) { - if (read == null) + if (ex is CsvHelperException) { - read = recordManager.Value.GetReadDelegate(type); + throw; } - - record = read(); - } - catch (Exception ex) - { - var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); - - var args = new ReadingExceptionOccurredArgs(csvHelperException); - if (readingExceptionOccurred?.Invoke(args) ?? true) + else { - if (ex is CsvHelperException) - { - throw; - } - else - { - throw csvHelperException; - } + throw csvHelperException; } - - // If the callback doesn't throw, keep going. - continue; - } - - yield return record; - } - } - - /// - public virtual async IAsyncEnumerable EnumerateRecordsAsync(T record, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - if (disposed) - { - throw new ObjectDisposedException(nameof(CsvReader), - "GetRecords() returns an IEnumerable that yields records. This means that the method isn't actually called until " + - "you try and access the values. Did you create CsvReader inside a using block and are now trying to access " + - "the records outside of that using block?" - ); - } - - // Don't need to check if it's been read - // since we're doing the reading ourselves. - - if (hasHeaderRecord && headerRecord == null) - { - if (!await ReadAsync().ConfigureAwait(false)) - { - yield break; } - ReadHeader(); - ValidateHeader(); + // If the callback doesn't throw, keep going. + continue; } - while (await ReadAsync().ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - try - { - recordManager.Value.Hydrate(record); - } - catch (Exception ex) - { - var csvHelperException = ex as CsvHelperException ?? new ReaderException(context, "An unexpected error occurred.", ex); - - var args = new ReadingExceptionOccurredArgs(csvHelperException); - if (readingExceptionOccurred?.Invoke(args) ?? true) - { - if (ex is CsvHelperException) - { - throw; - } - else - { - throw csvHelperException; - } - } + yield return record; + } + } - // If the callback doesn't throw, keep going. - continue; - } + /// + /// Gets the index of the field with the given name. + /// + /// The name of the field. + /// The index of the field. + /// Indicates if a TryGet is executed. + /// The index of the field. + public virtual int GetFieldIndex(string name, int index = 0, bool isTryGet = false) + { + return GetFieldIndex(new[] { name }, index, isTryGet); + } - yield return record; - } + /// + /// Gets the index of the field with the given name. + /// + /// The names of the field. + /// The index of the field. + /// Indicates if a TryGet is executed. + /// Indicates if the field is optional. + /// The index of the field. + public virtual int GetFieldIndex(IEnumerable names, int index = 0, bool isTryGet = false, bool isOptional = false) + { + if (names == null) + { + throw new ArgumentNullException(nameof(names)); } - /// - /// Gets the index of the field with the given name. - /// - /// The name of the field. - /// The index of the field. - /// Indicates if a TryGet is executed. - /// The index of the field. - public virtual int GetFieldIndex(string name, int index = 0, bool isTryGet = false) + if (!hasHeaderRecord) { - return GetFieldIndex(new[] { name }, index, isTryGet); + throw new ReaderException(context, "There is no header record to determine the index by name."); } - /// - /// Gets the index of the field with the given name. - /// - /// The names of the field. - /// The index of the field. - /// Indicates if a TryGet is executed. - /// Indicates if the field is optional. - /// The index of the field. - public virtual int GetFieldIndex(IEnumerable names, int index = 0, bool isTryGet = false, bool isOptional = false) + if (headerRecord == null) { - if (names == null) - { - throw new ArgumentNullException(nameof(names)); - } + throw new ReaderException(context, "The header has not been read. You must call ReadHeader() before any fields can be retrieved by name."); + } - if (!hasHeaderRecord) - { - throw new ReaderException(context, "There is no header record to determine the index by name."); - } + // Caching the named index speeds up mappings that use ConvertUsing tremendously. + var nameKey = string.Join("_", names) + index; + if (namedIndexCache.TryGetValue(nameKey, out var cache)) + { + (var cachedName, var cachedIndex) = cache; + return namedIndexes[cachedName][cachedIndex]; + } - if (headerRecord == null) + // Check all possible names for this field. + string? name = null; + var i = 0; + foreach (var n in names) + { + // Get the list of indexes for this name. + var args = new PrepareHeaderForMatchArgs(n, i); + var fieldName = prepareHeaderForMatch(args); + if (namedIndexes.ContainsKey(fieldName ?? string.Empty)) { - throw new ReaderException(context, "The header has not been read. You must call ReadHeader() before any fields can be retrieved by name."); + name = fieldName; + break; } - // Caching the named index speeds up mappings that use ConvertUsing tremendously. - var nameKey = string.Join("_", names) + index; - if (namedIndexCache.TryGetValue(nameKey, out var cache)) - { - (var cachedName, var cachedIndex) = cache; - return namedIndexes[cachedName][cachedIndex]; - } + i++; + } - // Check all possible names for this field. - string name = null; - var i = 0; - foreach (var n in names) + // Check if the index position exists. + if (name == null || index >= namedIndexes[name].Count) + { + // It doesn't exist. The field is missing. + if (!isTryGet && !isOptional) { - // Get the list of indexes for this name. - var args = new PrepareHeaderForMatchArgs(n, i); - var fieldName = prepareHeaderForMatch(args); - if (namedIndexes.ContainsKey(fieldName)) - { - name = fieldName; - break; - } - - i++; + var args = new MissingFieldFoundArgs(names.ToArray(), index, context); + missingFieldFound?.Invoke(args); } - // Check if the index position exists. - if (name == null || index >= namedIndexes[name].Count) - { - // It doesn't exist. The field is missing. - if (!isTryGet && !isOptional) - { - var args = new MissingFieldFoundArgs(names.ToArray(), index, context); - missingFieldFound?.Invoke(args); - } + return -1; + } - return -1; - } + namedIndexCache.Add(nameKey, (name, index)); - namedIndexCache.Add(nameKey, (name, index)); + return namedIndexes[name][index]; + } - return namedIndexes[name][index]; - } + /// + /// Indicates if values can be read. + /// + /// The member map. + /// True if values can be read. + public virtual bool CanRead(MemberMap memberMap) + { + var cantRead = + // Ignored member; + memberMap.Data.Ignore; - /// - /// Indicates if values can be read. - /// - /// The member map. - /// True if values can be read. - public virtual bool CanRead(MemberMap memberMap) + var property = memberMap.Data.Member as PropertyInfo; + if (property != null) { - var cantRead = - // Ignored member; - memberMap.Data.Ignore; + cantRead = cantRead || + // Properties that don't have a public setter + // and we are honoring the accessor modifier. + property.GetSetMethod() == null && !includePrivateMembers || + // Properties that don't have a setter at all. + property.GetSetMethod(true) == null; + } - var property = memberMap.Data.Member as PropertyInfo; - if (property != null) - { - cantRead = cantRead || - // Properties that don't have a public setter - // and we are honoring the accessor modifier. - property.GetSetMethod() == null && !includePrivateMembers || - // Properties that don't have a setter at all. - property.GetSetMethod(true) == null; - } + return !cantRead; + } - return !cantRead; - } + /// + /// Indicates if values can be read. + /// + /// The member reference map. + /// True if values can be read. + public virtual bool CanRead(MemberReferenceMap memberReferenceMap) + { + var cantRead = false; - /// - /// Indicates if values can be read. - /// - /// The member reference map. - /// True if values can be read. - public virtual bool CanRead(MemberReferenceMap memberReferenceMap) + var property = memberReferenceMap.Data.Member as PropertyInfo; + if (property != null) { - var cantRead = false; + cantRead = + // Properties that don't have a public setter + // and we are honoring the accessor modifier. + property.GetSetMethod() == null && !includePrivateMembers || + // Properties that don't have a setter at all. + property.GetSetMethod(true) == null; + } - var property = memberReferenceMap.Data.Member as PropertyInfo; - if (property != null) - { - cantRead = - // Properties that don't have a public setter - // and we are honoring the accessor modifier. - property.GetSetMethod() == null && !includePrivateMembers || - // Properties that don't have a setter at all. - property.GetSetMethod(true) == null; - } + return !cantRead; + } - return !cantRead; - } + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - /// - public void Dispose() + /// + /// Disposes the object. + /// + /// Indicates if the object is being disposed. + protected virtual void Dispose(bool disposing) + { + if (disposed) { - Dispose(true); - GC.SuppressFinalize(this); + return; } - /// - /// Disposes the object. - /// - /// Indicates if the object is being disposed. - protected virtual void Dispose(bool disposing) + // Dispose managed state (managed objects) + if (disposing) { - if (disposed) - { - return; - } + parser.Dispose(); + } - // Dispose managed state (managed objects) - if (disposing) - { - parser.Dispose(); - } + // Free unmanaged resources (unmanaged objects) and override finalizer + // Set large fields to null - // Free unmanaged resources (unmanaged objects) and override finalizer - // Set large fields to null - context = null; + disposed = true; + } - disposed = true; + /// + /// Checks if the file has been read. + /// + /// Thrown when the file has not yet been read. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual void CheckHasBeenRead() + { + if (!hasBeenRead) + { + throw new ReaderException(context, "You must call read on the reader before accessing its data."); } + } - /// - /// Checks if the file has been read. - /// - /// Thrown when the file has not yet been read. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected virtual void CheckHasBeenRead() + /// + /// Parses the named indexes. + /// + /// Thrown when no header record was found. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual void ParseNamedIndexes() + { + if (headerRecord == null) { - if (!hasBeenRead) - { - throw new ReaderException(context, "You must call read on the reader before accessing its data."); - } + throw new ReaderException(context, "No header record was found."); } - /// - /// Parses the named indexes. - /// - /// Thrown when no header record was found. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected virtual void ParseNamedIndexes() + namedIndexes.Clear(); + namedIndexCache.Clear(); + + for (var i = 0; i < headerRecord.Length; i++) { - if (headerRecord == null) + var args = new PrepareHeaderForMatchArgs(headerRecord[i], i); + var name = prepareHeaderForMatch(args); + if (namedIndexes.TryGetValue(name, out var index)) { - throw new ReaderException(context, "No header record was found."); + index.Add(i); } - - namedIndexes.Clear(); - namedIndexCache.Clear(); - - for (var i = 0; i < headerRecord.Length; i++) + else { - var args = new PrepareHeaderForMatchArgs(headerRecord[i], i); - var name = prepareHeaderForMatch(args); - if (namedIndexes.TryGetValue(name, out var index)) - { - index.Add(i); - } - else - { - namedIndexes[name] = new List { i }; - } + namedIndexes[name] = new List { i }; } } } diff --git a/src/CsvHelper/CsvWriter.cs b/src/CsvHelper/CsvWriter.cs index c79819374..cc84f03f1 100644 --- a/src/CsvHelper/CsvWriter.cs +++ b/src/CsvHelper/CsvWriter.cs @@ -5,953 +5,942 @@ using CsvHelper.Configuration; using CsvHelper.Expressions; using CsvHelper.TypeConversion; -using System; using System.Collections; -using System.Collections.Generic; using System.Dynamic; using System.Globalization; -using System.IO; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; #pragma warning disable 649 #pragma warning disable 169 -namespace CsvHelper +namespace CsvHelper; + +/// +/// Used to write CSV files. +/// +public class CsvWriter : IWriter { + private readonly TextWriter writer; + private readonly CsvContext context; + private readonly Lazy recordManager; + private readonly TypeConverterCache typeConverterCache; + private readonly TrimOptions trimOptions; + private readonly ShouldQuote shouldQuote; + private readonly MemberMapData reusableMemberMapData = new MemberMapData(null); + private readonly Dictionary typeConverterOptionsCache = new Dictionary(); + private readonly string quoteString; + private readonly char quote; + private readonly CultureInfo cultureInfo; + private readonly char comment; + private readonly bool hasHeaderRecord; + private readonly bool includePrivateMembers; + private readonly IComparer? dynamicPropertySort; + private readonly string delimiter; + private readonly bool leaveOpen; + private readonly string newLine; + private readonly char[] injectionCharacters; + private readonly char injectionEscapeCharacter; + private readonly InjectionOptions injectionOptions; + private readonly CsvMode mode; + private readonly string escapeString; + private readonly string escapeQuoteString; + private readonly string escapeDelimiterString; + private readonly string escapeNewlineString; + private readonly string escapeEscapeString; + + private bool disposed; + private bool hasHeaderBeenWritten; + private int row = 1; + private int index; + private char[] buffer; + private int bufferSize; + private int bufferPosition; + private Type? fieldType; + + /// + public virtual string?[]? HeaderRecord { get; private set; } + + /// + public virtual int Row => row; + + /// + public virtual int Index => index; + + /// + public virtual CsvContext Context => context; + + /// + public virtual IWriterConfiguration Configuration { get; private set; } + /// - /// Used to write CSV files. + /// Initializes a new instance of the class. /// - public class CsvWriter : IWriter - { - private readonly TextWriter writer; - private readonly CsvContext context; - private readonly Lazy recordManager; - private readonly TypeConverterCache typeConverterCache; - private readonly TrimOptions trimOptions; - private readonly ShouldQuote shouldQuote; - private readonly MemberMapData reusableMemberMapData = new MemberMapData(null); - private readonly Dictionary typeConverterOptionsCache = new Dictionary(); - private readonly string quoteString; - private readonly char quote; - private readonly CultureInfo cultureInfo; - private readonly char comment; - private readonly bool hasHeaderRecord; - private readonly bool includePrivateMembers; - private readonly IComparer dynamicPropertySort; - private readonly string delimiter; - private readonly bool leaveOpen; - private readonly string newLine; - private readonly char[] injectionCharacters; - private readonly char injectionEscapeCharacter; - private readonly InjectionOptions injectionOptions; - private readonly CsvMode mode; - private readonly string escapeString; - private readonly string escapeQuoteString; - private readonly string escapeDelimiterString; - private readonly string escapeNewlineString; - private readonly string escapeEscapeString; - - private bool disposed; - private bool hasHeaderBeenWritten; - private int row = 1; - private int index; - private char[] buffer; - private int bufferSize; - private int bufferPosition; - private Type fieldType; - - /// - public virtual string[] HeaderRecord { get; private set; } - - /// - public virtual int Row => row; - - /// - public virtual int Index => index; - - /// - public virtual CsvContext Context => context; - - /// - public virtual IWriterConfiguration Configuration { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The writer. - /// The culture. - /// true to leave the open after the object is disposed, otherwise false. - public CsvWriter(TextWriter writer, CultureInfo culture, bool leaveOpen = false) : this(writer, new CsvConfiguration(culture), leaveOpen) { } - - /// - /// Initializes a new instance of the class. - /// - /// The writer. - /// The configuration. - /// true to leave the open after the object is disposed, otherwise false. - public CsvWriter(TextWriter writer, IWriterConfiguration configuration, bool leaveOpen = false) - { - configuration.Validate(); - - this.writer = writer; - Configuration = configuration; - context = new CsvContext(this); - typeConverterCache = context.TypeConverterCache; - recordManager = new Lazy(() => ObjectResolver.Current.Resolve(this)); - - comment = configuration.Comment; - bufferSize = configuration.BufferSize; - delimiter = configuration.Delimiter; - cultureInfo = configuration.CultureInfo; - dynamicPropertySort = configuration.DynamicPropertySort; - escapeDelimiterString = new string(configuration.Delimiter.SelectMany(c => new[] { configuration.Escape, c }).ToArray()); - escapeNewlineString = new string(configuration.NewLine.SelectMany(c => new[] { configuration.Escape, c }).ToArray()); - escapeQuoteString = new string(new[] { configuration.Escape, configuration.Quote }); - escapeEscapeString = new string(new[] { configuration.Escape, configuration.Escape }); - hasHeaderRecord = configuration.HasHeaderRecord; - includePrivateMembers = configuration.IncludePrivateMembers; - injectionCharacters = configuration.InjectionCharacters; - injectionEscapeCharacter = configuration.InjectionEscapeCharacter; - this.leaveOpen = leaveOpen; - mode = configuration.Mode; - newLine = configuration.NewLine; - quote = configuration.Quote; - quoteString = configuration.Quote.ToString(); - escapeString = configuration.Escape.ToString(); - injectionOptions = configuration.InjectionOptions; - shouldQuote = configuration.ShouldQuote; - trimOptions = configuration.TrimOptions; - - buffer = new char[bufferSize]; - } - - /// - public virtual void WriteConvertedField(string field, Type fieldType) - { - this.fieldType = fieldType; - - if (field == null) - { - return; - } + /// The writer. + /// The culture. + /// true to leave the open after the object is disposed, otherwise false. + public CsvWriter(TextWriter writer, CultureInfo culture, bool leaveOpen = false) : this(writer, new CsvConfiguration(culture), leaveOpen) { } - WriteField(field); + /// + /// Initializes a new instance of the class. + /// + /// The writer. + /// The configuration. + /// true to leave the open after the object is disposed, otherwise false. + public CsvWriter(TextWriter writer, IWriterConfiguration configuration, bool leaveOpen = false) + { + configuration.Validate(); + + this.writer = writer; + Configuration = configuration; + context = new CsvContext(this); + typeConverterCache = context.TypeConverterCache; + recordManager = new Lazy(() => ObjectResolver.Current.Resolve(this)); + + comment = configuration.Comment; + bufferSize = configuration.BufferSize; + delimiter = configuration.Delimiter; + cultureInfo = configuration.CultureInfo; + dynamicPropertySort = configuration.DynamicPropertySort; + escapeDelimiterString = new string(configuration.Delimiter.SelectMany(c => new[] { configuration.Escape, c }).ToArray()); + escapeNewlineString = new string(configuration.NewLine.SelectMany(c => new[] { configuration.Escape, c }).ToArray()); + escapeQuoteString = new string(new[] { configuration.Escape, configuration.Quote }); + escapeEscapeString = new string(new[] { configuration.Escape, configuration.Escape }); + hasHeaderRecord = configuration.HasHeaderRecord; + includePrivateMembers = configuration.IncludePrivateMembers; + injectionCharacters = configuration.InjectionCharacters; + injectionEscapeCharacter = configuration.InjectionEscapeCharacter; + this.leaveOpen = leaveOpen; + mode = configuration.Mode; + newLine = configuration.NewLine; + quote = configuration.Quote; + quoteString = configuration.Quote.ToString(); + escapeString = configuration.Escape.ToString(); + injectionOptions = configuration.InjectionOptions; + shouldQuote = configuration.ShouldQuote; + trimOptions = configuration.TrimOptions; + + buffer = new char[bufferSize]; + } + + /// + public virtual void WriteConvertedField(string? field, Type fieldType) + { + this.fieldType = fieldType; + + if (field == null) + { + return; } - /// - public virtual void WriteField(string field) + WriteField(field); + } + + /// + public virtual void WriteField(string? field) + { + if (field != null && (trimOptions & TrimOptions.Trim) == TrimOptions.Trim) { - if (field != null && (trimOptions & TrimOptions.Trim) == TrimOptions.Trim) - { - field = field.Trim(); - } + field = field.Trim(); + } - fieldType ??= typeof(string); + fieldType ??= typeof(string); - var args = new ShouldQuoteArgs(field, fieldType, this); - var shouldQuoteResult = shouldQuote(args); + var args = new ShouldQuoteArgs(field, fieldType, this); + var shouldQuoteResult = shouldQuote(args); - WriteField(field, shouldQuoteResult); - } + WriteField(field, shouldQuoteResult); + } - /// - public virtual void WriteField(string field, bool shouldQuote) + /// + public virtual void WriteField(string? field, bool shouldQuote) + { + if (mode == CsvMode.RFC4180) { - if (mode == CsvMode.RFC4180) + // All quotes must be escaped. + if (shouldQuote) { - // All quotes must be escaped. - if (shouldQuote) + if (escapeString != quoteString) { - if (escapeString != quoteString) - { - field = field?.Replace(escapeString, escapeEscapeString); - } - - field = field?.Replace(quoteString, escapeQuoteString); - field = quote + field + quote; + field = field?.Replace(escapeString, escapeEscapeString); } - } - else if (mode == CsvMode.Escape) - { - field = field? - .Replace(escapeString, escapeEscapeString) - .Replace(quoteString, escapeQuoteString) - .Replace(delimiter, escapeDelimiterString) - .Replace(newLine, escapeNewlineString); - } - if (injectionOptions != InjectionOptions.None) - { - field = SanitizeForInjection(field); + field = field?.Replace(quoteString, escapeQuoteString); + field = quote + field + quote; } - - if (index > 0) - { - WriteToBuffer(delimiter); - } - - WriteToBuffer(field); - index++; - fieldType = null; + } + else if (mode == CsvMode.Escape) + { + field = field? + .Replace(escapeString, escapeEscapeString) + .Replace(quoteString, escapeQuoteString) + .Replace(delimiter, escapeDelimiterString) + .Replace(newLine, escapeNewlineString); } - /// - public virtual void WriteField(T field) + if (injectionOptions != InjectionOptions.None) { - var type = field == null ? typeof(string) : field.GetType(); - var converter = typeConverterCache.GetConverter(type); - WriteField(field, converter); + field = SanitizeForInjection(field); } - /// - public virtual void WriteField(T field, ITypeConverter converter) + if (index > 0) { - var type = field == null ? typeof(string) : field.GetType(); - reusableMemberMapData.TypeConverter = converter; - if (!typeConverterOptionsCache.TryGetValue(type, out TypeConverterOptions typeConverterOptions)) - { - typeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = cultureInfo }, context.TypeConverterOptionsCache.GetOptions(type)); - typeConverterOptionsCache.Add(type, typeConverterOptions); - } + WriteToBuffer(delimiter); + } - reusableMemberMapData.TypeConverterOptions = typeConverterOptions; + WriteToBuffer(field); + index++; + fieldType = null; + } - var fieldString = converter.ConvertToString(field, this, reusableMemberMapData); + /// + public virtual void WriteField(T? field) + { + var type = field == null ? typeof(string) : field.GetType(); + var converter = typeConverterCache.GetConverter(type); + WriteField(field, converter); + } - WriteConvertedField(fieldString, type); + /// + public virtual void WriteField(T? field, ITypeConverter converter) + { + var type = field == null ? typeof(string) : field.GetType(); + reusableMemberMapData.TypeConverter = converter; + if (!typeConverterOptionsCache.TryGetValue(type, out TypeConverterOptions? typeConverterOptions)) + { + typeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = cultureInfo }, context.TypeConverterOptionsCache.GetOptions(type)); + typeConverterOptionsCache.Add(type, typeConverterOptions); } - /// - public virtual void WriteField(T field) - { - var converter = typeConverterCache.GetConverter(); + reusableMemberMapData.TypeConverterOptions = typeConverterOptions; - WriteField(field, converter); - } + var fieldString = converter.ConvertToString(field, this, reusableMemberMapData); + + WriteConvertedField(fieldString, type); + } + + /// + public virtual void WriteField(T? field) + { + var converter = typeConverterCache.GetConverter(); + + WriteField(field, converter); + } - /// - public virtual void WriteComment(string text) + /// + public virtual void WriteComment(string? text) + { + WriteField(comment + text, false); + } + + /// + public virtual void WriteHeader() + { + WriteHeader(typeof(T)); + } + + /// + public virtual void WriteHeader(Type type) + { + if (type == null) { - WriteField(comment + text, false); + throw new ArgumentNullException(nameof(type)); } - /// - public virtual void WriteHeader() + if (type == typeof(object)) { - WriteHeader(typeof(T)); + return; } - /// - public virtual void WriteHeader(Type type) + if (context.Maps[type] == null) { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - if (type == typeof(object)) - { - return; - } - - if (context.Maps[type] == null) - { - context.Maps.Add(context.AutoMap(type)); - } + context.Maps.Add(context.AutoMap(type)); + } - var members = new MemberMapCollection(); - members.AddMembers(context.Maps[type]); + var members = new MemberMapCollection(); + members.AddMembers(context.Maps[type]!); // The map is added above. - var headerRecord = new List(); + var headerRecord = new List(); - foreach (var member in members) + foreach (var member in members) + { + if (CanWrite(member)) { - if (CanWrite(member)) + if (member.Data.IndexEnd >= member.Data.Index) { - if (member.Data.IndexEnd >= member.Data.Index) - { - var count = member.Data.IndexEnd - member.Data.Index + 1; - for (var i = 1; i <= count; i++) - { - var header = member.Data.Names.FirstOrDefault() + i; - WriteField(header); - headerRecord.Add(header); - } - } - else + var count = member.Data.IndexEnd - member.Data.Index + 1; + for (var i = 1; i <= count; i++) { - var header = member.Data.Names.FirstOrDefault(); + var header = member.Data.Names.FirstOrDefault() + i; WriteField(header); headerRecord.Add(header); } } + else + { + var header = member.Data.Names.FirstOrDefault(); + WriteField(header); + headerRecord.Add(header); + } } + } + + HeaderRecord = headerRecord.ToArray(); - HeaderRecord = headerRecord.ToArray(); + hasHeaderBeenWritten = true; + } - hasHeaderBeenWritten = true; + /// + /// Writes a dynamic header record. + /// + /// The header record to write. + /// Thrown when no record is passed. + public virtual void WriteDynamicHeader(IDynamicMetaObjectProvider? record) + { + if (record == null) + { + throw new ArgumentNullException(nameof(record)); } - /// - /// Writes a dynamic header record. - /// - /// The header record to write. - /// Thrown when no record is passed. - public virtual void WriteDynamicHeader(IDynamicMetaObjectProvider record) + var metaObject = record.GetMetaObject(Expression.Constant(record)); + var names = metaObject.GetDynamicMemberNames().ToList(); + if (dynamicPropertySort != null) { - if (record == null) - { - throw new ArgumentNullException(nameof(record)); - } + names = names.OrderBy(name => name, dynamicPropertySort).ToList(); + } - var metaObject = record.GetMetaObject(Expression.Constant(record)); - var names = metaObject.GetDynamicMemberNames().ToList(); - if (dynamicPropertySort != null) - { - names = names.OrderBy(name => name, dynamicPropertySort).ToList(); - } + HeaderRecord = names.ToArray(); - HeaderRecord = names.ToArray(); + foreach (var name in names) + { + WriteField(name); + } - foreach (var name in names) - { - WriteField(name); - } + hasHeaderBeenWritten = true; + } - hasHeaderBeenWritten = true; + /// + public virtual void WriteRecord(T? record) + { + try + { + var recordTypeInfo = GetTypeInfoForRecord(record); + var write = recordManager.Value.GetWriteDelegate(recordTypeInfo); + write(record); } - - /// - public virtual void WriteRecord(T record) + catch (TargetInvocationException ex) { - try - { - var recordTypeInfo = GetTypeInfoForRecord(record); - var write = recordManager.Value.GetWriteDelegate(recordTypeInfo); - write(record); - } - catch (TargetInvocationException ex) + if (ex.InnerException != null) { - if (ex.InnerException != null) - { - throw ex.InnerException; - } - else - { - throw; - } + throw ex.InnerException; } - catch (Exception ex) when (ex is not CsvHelperException) + else { - throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + throw; } } + catch (Exception ex) when (ex is not CsvHelperException) + { + throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + } + } + + /// + public virtual void WriteRecords(IEnumerable records) + { + // Changes in this method require changes in method WriteRecords(IEnumerable records) also. - /// - public virtual void WriteRecords(IEnumerable records) + var enumerator = records.GetEnumerator(); + + try { - // Changes in this method require changes in method WriteRecords(IEnumerable records) also. + if (!enumerator.MoveNext()) + { + return; + } + + if (WriteHeaderFromRecord(enumerator.Current)) + { + NextRecord(); + } - var enumerator = records.GetEnumerator(); + Action? write = null; + RecordTypeInfo writeType = default; - try + do { - if (!enumerator.MoveNext()) - { - return; - } + var record = enumerator.Current; - if (WriteHeaderFromRecord(enumerator.Current)) + if (record == null) { + // Since every record could be a different type, just write a blank line. NextRecord(); + continue; } - Action write = null; - RecordTypeInfo writeType = default; - - do + if (write == null || writeType.RecordType != record.GetType()) { - var record = enumerator.Current; - - if (record == null) - { - // Since every record could be a different type, just write a blank line. - NextRecord(); - continue; - } - - if (write == null || writeType.RecordType != record.GetType()) - { - writeType = GetTypeInfoForRecord(record); - write = recordManager.Value.GetWriteDelegate(writeType); - } - - write(record); - NextRecord(); + writeType = GetTypeInfoForRecord(record); + write = recordManager.Value.GetWriteDelegate(writeType); } - while (enumerator.MoveNext()); - } - catch (Exception ex) when (ex is not CsvHelperException) - { - throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + + write(record); + NextRecord(); } - finally + while (enumerator.MoveNext()); + } + catch (Exception ex) when (ex is not CsvHelperException) + { + throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + } + finally + { + if (enumerator is IDisposable en) { - if (enumerator is IDisposable en) - { - en.Dispose(); - } + en.Dispose(); } } + } + + /// + public virtual void WriteRecords(IEnumerable records) + { + // Changes in this method require changes in method WriteRecords(IEnumerable records) also. + + var enumerator = records.GetEnumerator() ?? throw new InvalidOperationException("Enumerator is null."); - /// - public virtual void WriteRecords(IEnumerable records) + try { - // Changes in this method require changes in method WriteRecords(IEnumerable records) also. + if (WriteHeaderFromType()) + { + NextRecord(); + } - var enumerator = records.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } - try + if (WriteHeaderFromRecord(enumerator.Current)) { - if (WriteHeaderFromType()) - { - NextRecord(); - } + NextRecord(); + } - if (!enumerator.MoveNext()) - { - return; - } + Action? write = null; + RecordTypeInfo writeType = default; - if (WriteHeaderFromRecord(enumerator.Current)) + do + { + var record = enumerator.Current; + + if (write == null || (record != null && writeType.RecordType != typeof(T))) { - NextRecord(); + writeType = GetTypeInfoForRecord(record); + write = recordManager.Value.GetWriteDelegate(writeType); } - Action write = null; - RecordTypeInfo writeType = default; + write(record); + NextRecord(); + } + while (enumerator.MoveNext()); + } + catch (Exception ex) when (ex is not CsvHelperException) + { + throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + } + finally + { + if (enumerator is IDisposable en) + { + en.Dispose(); + } + } + } - do - { - var record = enumerator.Current; + /// + public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default) + { + // These methods should all be the same; + // - WriteRecordsAsync(IEnumerable records) + // - WriteRecordsAsync(IEnumerable records) + // - WriteRecordsAsync(IAsyncEnumerable records) - if (write == null || (record != null && writeType.RecordType != typeof(T))) - { - writeType = GetTypeInfoForRecord(record); - write = recordManager.Value.GetWriteDelegate(writeType); - } + var enumerator = records.GetEnumerator(); - write(record); - NextRecord(); - } - while (enumerator.MoveNext()); - } - catch (Exception ex) when (ex is not CsvHelperException) + try + { + if (!enumerator.MoveNext()) { - throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + return; } - finally + + if (WriteHeaderFromRecord(enumerator.Current)) { - if (enumerator is IDisposable en) - { - en.Dispose(); - } + await NextRecordAsync().ConfigureAwait(false); } - } - /// - public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default) - { - // These methods should all be the same; - // - WriteRecordsAsync(IEnumerable records) - // - WriteRecordsAsync(IEnumerable records) - // - WriteRecordsAsync(IAsyncEnumerable records) - - var enumerator = records.GetEnumerator(); + Action? write = null; + RecordTypeInfo writeType = default; - try + do { - if (!enumerator.MoveNext()) - { - return; - } + cancellationToken.ThrowIfCancellationRequested(); - if (WriteHeaderFromRecord(enumerator.Current)) + var record = enumerator.Current; + + if (write == null || (record != null && writeType.RecordType != record.GetType())) { - await NextRecordAsync().ConfigureAwait(false); + writeType = GetTypeInfoForRecord(record); + write = recordManager.Value.GetWriteDelegate(writeType); } - Action write = null; - RecordTypeInfo writeType = default; - - do - { - cancellationToken.ThrowIfCancellationRequested(); + write(record); + await NextRecordAsync().ConfigureAwait(false); + } + while (enumerator.MoveNext()); + } + catch (Exception ex) when (ex is not CsvHelperException) + { + throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + } + finally + { + if (enumerator is IDisposable en) + { + en.Dispose(); + } + } + } - var record = enumerator.Current; + /// + public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default) + { + // These methods should all be the same; + // - WriteRecordsAsync(IEnumerable records) + // - WriteRecordsAsync(IEnumerable records) + // - WriteRecordsAsync(IAsyncEnumerable records) - if (write == null || (record != null && writeType.RecordType != record.GetType())) - { - writeType = GetTypeInfoForRecord(record); - write = recordManager.Value.GetWriteDelegate(writeType); - } + var enumerator = records.GetEnumerator() ?? throw new InvalidOperationException("Enumerator is null."); - write(record); - await NextRecordAsync().ConfigureAwait(false); - } - while (enumerator.MoveNext()); - } - catch (Exception ex) when (ex is not CsvHelperException) + try + { + if (WriteHeaderFromType()) { - throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + await NextRecordAsync().ConfigureAwait(false); } - finally + + if (!enumerator.MoveNext()) { - if (enumerator is IDisposable en) - { - en.Dispose(); - } + return; } - } - /// - public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default) - { - // These methods should all be the same; - // - WriteRecordsAsync(IEnumerable records) - // - WriteRecordsAsync(IEnumerable records) - // - WriteRecordsAsync(IAsyncEnumerable records) + if (WriteHeaderFromRecord(enumerator.Current)) + { + await NextRecordAsync().ConfigureAwait(false); + } - var enumerator = records.GetEnumerator(); + Action? write = null; + RecordTypeInfo writeType = default; - try + do { - if (WriteHeaderFromType()) - { - await NextRecordAsync().ConfigureAwait(false); - } + cancellationToken.ThrowIfCancellationRequested(); - if (!enumerator.MoveNext()) - { - return; - } + var record = enumerator.Current; - if (WriteHeaderFromRecord(enumerator.Current)) + if (write == null || (record != null && writeType.RecordType != typeof(T))) { - await NextRecordAsync().ConfigureAwait(false); + writeType = GetTypeInfoForRecord(record); + write = recordManager.Value.GetWriteDelegate(writeType); } - Action write = null; - RecordTypeInfo writeType = default; + write(record); + await NextRecordAsync().ConfigureAwait(false); + } + while (enumerator.MoveNext()); + } + catch (Exception ex) when (ex is not CsvHelperException) + { + throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + } + finally + { + if (enumerator is IDisposable en) + { + en.Dispose(); + } + } + } - do - { - cancellationToken.ThrowIfCancellationRequested(); + /// + public virtual async Task WriteRecordsAsync(IAsyncEnumerable records, CancellationToken cancellationToken = default) + { + // These methods should all be the same; + // - WriteRecordsAsync(IEnumerable records) + // - WriteRecordsAsync(IEnumerable records) + // - WriteRecordsAsync(IAsyncEnumerable records) - var record = enumerator.Current; + var enumerator = records.GetAsyncEnumerator() ?? throw new InvalidOperationException("Enumerator is null."); - if (write == null || (record != null && writeType.RecordType != typeof(T))) - { - writeType = GetTypeInfoForRecord(record); - write = recordManager.Value.GetWriteDelegate(writeType); - } + try + { + if (WriteHeaderFromType()) + { + await NextRecordAsync().ConfigureAwait(false); + } - write(record); - await NextRecordAsync().ConfigureAwait(false); - } - while (enumerator.MoveNext()); + if (!await enumerator.MoveNextAsync()) + { + return; } - catch (Exception ex) when (ex is not CsvHelperException) + + if (WriteHeaderFromRecord(enumerator.Current)) { - throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + await NextRecordAsync().ConfigureAwait(false); } - finally + + Action? write = null; + RecordTypeInfo writeType = default; + + do { - if (enumerator is IDisposable en) + cancellationToken.ThrowIfCancellationRequested(); + + var record = enumerator.Current; + + if (write == null || (record != null && writeType.RecordType != typeof(T))) { - en.Dispose(); + writeType = GetTypeInfoForRecord(record); + write = recordManager.Value.GetWriteDelegate(writeType); } + + write(record); + await NextRecordAsync().ConfigureAwait(false); } + while (await enumerator.MoveNextAsync().ConfigureAwait(false)); } - - /// - public virtual async Task WriteRecordsAsync(IAsyncEnumerable records, CancellationToken cancellationToken = default) + catch (Exception ex) when (ex is not CsvHelperException) { - // These methods should all be the same; - // - WriteRecordsAsync(IEnumerable records) - // - WriteRecordsAsync(IEnumerable records) - // - WriteRecordsAsync(IAsyncEnumerable records) - - var enumerator = records.GetAsyncEnumerator(); - - try + throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); + } + finally + { + if (enumerator is IDisposable en) { - if (WriteHeaderFromType()) - { - await NextRecordAsync().ConfigureAwait(false); - } + en.Dispose(); + } + } + } - if (!await enumerator.MoveNextAsync()) - { - return; - } + /// + public virtual void NextRecord() + { + WriteToBuffer(newLine); + FlushBuffer(); - if (WriteHeaderFromRecord(enumerator.Current)) - { - await NextRecordAsync().ConfigureAwait(false); - } + index = 0; + row++; + } - Action write = null; - RecordTypeInfo writeType = default; + /// + public virtual async Task NextRecordAsync() + { + WriteToBuffer(newLine); + await FlushBufferAsync().ConfigureAwait(false); - do - { - cancellationToken.ThrowIfCancellationRequested(); + index = 0; + row++; + } - var record = enumerator.Current; + /// + public virtual void Flush() + { + FlushBuffer(); + writer.Flush(); + } - if (write == null || (record != null && writeType.RecordType != typeof(T))) - { - writeType = GetTypeInfoForRecord(record); - write = recordManager.Value.GetWriteDelegate(writeType); - } + /// + public virtual async Task FlushAsync() + { + await FlushBufferAsync().ConfigureAwait(false); + await writer.FlushAsync().ConfigureAwait(false); + } - write(record); - await NextRecordAsync().ConfigureAwait(false); - } - while (await enumerator.MoveNextAsync().ConfigureAwait(false)); - } - catch (Exception ex) when (ex is not CsvHelperException) - { - throw new WriterException(context, "An unexpected error occurred. See inner exception for details.", ex); - } - finally - { - if (enumerator is IDisposable en) - { - en.Dispose(); - } - } - } + /// + /// Flushes the buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual void FlushBuffer() + { + writer.Write(buffer, 0, bufferPosition); + bufferPosition = 0; + } - /// - public virtual void NextRecord() - { - WriteToBuffer(newLine); - FlushBuffer(); + /// + /// Asynchronously flushes the buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual async Task FlushBufferAsync() + { + await writer.WriteAsync(buffer, 0, bufferPosition).ConfigureAwait(false); + bufferPosition = 0; + } - index = 0; - row++; - } + /// + /// Indicates if values can be written. + /// + /// The member map. + /// True if values can be written. + public virtual bool CanWrite(MemberMap memberMap) + { + var cantWrite = + // Ignored members. + memberMap.Data.Ignore; - /// - public virtual async Task NextRecordAsync() + if (memberMap.Data.Member is PropertyInfo property) { - WriteToBuffer(newLine); - await FlushBufferAsync().ConfigureAwait(false); - - index = 0; - row++; + cantWrite = cantWrite || + // Properties that don't have a public getter + // and we are honoring the accessor modifier. + property.GetGetMethod() == null && !includePrivateMembers || + // Properties that don't have a getter at all. + property.GetGetMethod(true) == null; } - /// - public virtual void Flush() + return !cantWrite; + } + + /// + /// Determines the type for the given record. + /// + /// The type of the record. + /// The record to determine the type of. + /// The System.Type for the record. + public virtual RecordTypeInfo GetTypeInfoForRecord(T? record) + { + var type = typeof(T); + if (type == typeof(object) && record != null) { - FlushBuffer(); - writer.Flush(); + return new RecordTypeInfo(record.GetType(), true); } - /// - public virtual async Task FlushAsync() + return new RecordTypeInfo(type, false); + } + + /// + /// Sanitizes the given field, before it is injected. + /// + /// The field to sanitize. + /// The sanitized field. + /// Thrown when an injection character is found in the field. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual string? SanitizeForInjection(string? field) + { + if (field == null || field.Length == 0) { - await FlushBufferAsync().ConfigureAwait(false); - await writer.FlushAsync().ConfigureAwait(false); + return field; } - /// - /// Flushes the buffer. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected virtual void FlushBuffer() + int injectionCharIndex; + if (ArrayHelper.Contains(injectionCharacters, field[0])) { - writer.Write(buffer, 0, bufferPosition); - bufferPosition = 0; + injectionCharIndex = 0; } - - /// - /// Asynchronously flushes the buffer. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected virtual async Task FlushBufferAsync() + else if (field[0] == quote && field[field.Length - 1] == quote && ArrayHelper.Contains(injectionCharacters, field[1])) { - await writer.WriteAsync(buffer, 0, bufferPosition).ConfigureAwait(false); - bufferPosition = 0; + injectionCharIndex = 1; } - - /// - /// Indicates if values can be written. - /// - /// The member map. - /// True if values can be written. - public virtual bool CanWrite(MemberMap memberMap) + else { - var cantWrite = - // Ignored members. - memberMap.Data.Ignore; - - if (memberMap.Data.Member is PropertyInfo property) - { - cantWrite = cantWrite || - // Properties that don't have a public getter - // and we are honoring the accessor modifier. - property.GetGetMethod() == null && !includePrivateMembers || - // Properties that don't have a getter at all. - property.GetGetMethod(true) == null; - } - - return !cantWrite; + return field; } - /// - /// Determines the type for the given record. - /// - /// The type of the record. - /// The record to determine the type of. - /// The System.Type for the record. - public virtual RecordTypeInfo GetTypeInfoForRecord(T record) + if (injectionOptions == InjectionOptions.Exception) { - var type = typeof(T); - if (type == typeof(object)) - { - return new RecordTypeInfo(record.GetType(), true); - } - - return new RecordTypeInfo(type, false); + throw new WriterException(context, $"Injection character '{field[injectionCharIndex]}' detected"); } - /// - /// Sanitizes the given field, before it is injected. - /// - /// The field to sanitize. - /// The sanitized field. - /// Thrown when an injection character is found in the field. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected virtual string SanitizeForInjection(string field) + if (injectionOptions == InjectionOptions.Escape) { - if (string.IsNullOrEmpty(field)) + if (injectionCharIndex == 0) { - return field; - } - - int injectionCharIndex; - if (ArrayHelper.Contains(injectionCharacters, field[0])) - { - injectionCharIndex = 0; - } - else if (field[0] == quote && field[field.Length - 1] == quote && ArrayHelper.Contains(injectionCharacters, field[1])) - { - injectionCharIndex = 1; + // =1+"2 -> "'=1+""2" + field = quoteString + injectionEscapeCharacter + field.Replace(quoteString, escapeQuoteString) + quoteString; } else { - return field; + // "=1+2" -> "'=1+2" + field = quoteString + injectionEscapeCharacter + field.Substring(injectionCharIndex); } - - if (injectionOptions == InjectionOptions.Exception) + } + else if (injectionOptions == InjectionOptions.Strip) + { + while (true) { - throw new WriterException(context, $"Injection character '{field[injectionCharIndex]}' detected"); - } + field = field.Substring(1); - if (injectionOptions == InjectionOptions.Escape) - { - if (injectionCharIndex == 0) - { - // =1+"2 -> "'=1+""2" - field = quoteString + injectionEscapeCharacter + field.Replace(quoteString, escapeQuoteString) + quoteString; - } - else + if (field.Length == 0 || !ArrayHelper.Contains(injectionCharacters, field[0])) { - // "=1+2" -> "'=1+2" - field = quoteString + injectionEscapeCharacter + field.Substring(injectionCharIndex); + break; } } - else if (injectionOptions == InjectionOptions.Strip) + + if (injectionCharIndex == 1) { - while (true) - { - field = field.Substring(1); + field = quoteString + field; + } + } - if (field.Length == 0 || !ArrayHelper.Contains(injectionCharacters, field[0])) - { - break; - } - } + return field; + } - if (injectionCharIndex == 1) - { - field = quoteString + field; - } - } + /// + /// Writes the given value to the buffer. + /// + /// The value to write. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void WriteToBuffer(string? value) + { + var length = value?.Length ?? 0; - return field; + if (value == null || length == 0) + { + return; } - /// - /// Writes the given value to the buffer. - /// - /// The value to write. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void WriteToBuffer(string value) + var lengthNeeded = bufferPosition + length; + if (lengthNeeded >= bufferSize) { - var length = value?.Length ?? 0; - - if (value == null || length == 0) + while (lengthNeeded >= bufferSize) { - return; + bufferSize *= 2; } - var lengthNeeded = bufferPosition + length; - if (lengthNeeded >= bufferSize) - { - while (lengthNeeded >= bufferSize) - { - bufferSize *= 2; - } + Array.Resize(ref buffer, bufferSize); + } - Array.Resize(ref buffer, bufferSize); - } + value.CopyTo(0, buffer, bufferPosition, length); - value.CopyTo(0, buffer, bufferPosition, length); + bufferPosition += length; + } - bufferPosition += length; - } + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - /// - public void Dispose() + /// + /// Disposes the object. + /// + /// Indicates if the object is being disposed. + protected virtual void Dispose(bool disposing) + { + if (disposed) { - Dispose(true); - GC.SuppressFinalize(this); + return; } - /// - /// Disposes the object. - /// - /// Indicates if the object is being disposed. - protected virtual void Dispose(bool disposing) - { - if (disposed) - { - return; - } + Flush(); - Flush(); + if (disposing) + { + // Dispose managed state (managed objects) - if (disposing) + if (!leaveOpen) { - // Dispose managed state (managed objects) - - if (!leaveOpen) - { - writer.Dispose(); - } + writer.Dispose(); } + } - // Free unmanaged resources (unmanaged objects) and override finalizer - // Set large fields to null + // Free unmanaged resources (unmanaged objects) and override finalizer + // Set large fields to null - buffer = null; + disposed = true; + } - disposed = true; - } + /// + public async ValueTask DisposeAsync() + { + await DisposeAsync(true).ConfigureAwait(false); + GC.SuppressFinalize(this); + } - /// - public async ValueTask DisposeAsync() + /// + protected virtual async ValueTask DisposeAsync(bool disposing) + { + if (disposed) { - await DisposeAsync(true).ConfigureAwait(false); - GC.SuppressFinalize(this); + return; } - /// - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (disposed) - { - return; - } + await FlushAsync().ConfigureAwait(false); - await FlushAsync().ConfigureAwait(false); + if (disposing) + { + // Dispose managed state (managed objects) - if (disposing) + if (!leaveOpen) { - // Dispose managed state (managed objects) - - if (!leaveOpen) - { - await writer.DisposeAsync().ConfigureAwait(false); - } + await writer.DisposeAsync().ConfigureAwait(false); } + } - // Free unmanaged resources (unmanaged objects) and override finalizer - // Set large fields to null + // Free unmanaged resources (unmanaged objects) and override finalizer + // Set large fields to null - buffer = null; + disposed = true; + } - disposed = true; + private bool WriteHeaderFromType() + { + if (!hasHeaderRecord || hasHeaderBeenWritten) + { + return false; } - private bool WriteHeaderFromType() + var recordType = typeof(T); + var isPrimitive = recordType.GetTypeInfo().IsPrimitive; + if (!isPrimitive && recordType != typeof(object)) { - if (!hasHeaderRecord || hasHeaderBeenWritten) - { - return false; - } + WriteHeader(recordType); - var recordType = typeof(T); - var isPrimitive = recordType.GetTypeInfo().IsPrimitive; - if (!isPrimitive && recordType != typeof(object)) - { - WriteHeader(recordType); + return hasHeaderBeenWritten; + } - return hasHeaderBeenWritten; - } + return false; + } + private bool WriteHeaderFromRecord(object? record) + { + if (!hasHeaderRecord || hasHeaderBeenWritten) + { return false; } - private bool WriteHeaderFromRecord(object record) + if (record == null) { - if (!hasHeaderRecord || hasHeaderBeenWritten) - { - return false; - } - - if (record == null) - { - return false; - } - - if (record is IDynamicMetaObjectProvider dynamicObject) - { - WriteDynamicHeader(dynamicObject); + return false; + } - return true; - } + if (record is IDynamicMetaObjectProvider dynamicObject) + { + WriteDynamicHeader(dynamicObject); - var recordType = record.GetType(); - var isPrimitive = recordType.GetTypeInfo().IsPrimitive; - if (!isPrimitive) - { - WriteHeader(recordType); + return true; + } - return true; - } + var recordType = record.GetType(); + var isPrimitive = recordType.GetTypeInfo().IsPrimitive; + if (!isPrimitive) + { + WriteHeader(recordType); - return false; + return true; } + + return false; } } diff --git a/src/CsvHelper/Delegates/BadDataFound.cs b/src/CsvHelper/Delegates/BadDataFound.cs index 857561fe0..37ff11ce9 100644 --- a/src/CsvHelper/Delegates/BadDataFound.cs +++ b/src/CsvHelper/Delegates/BadDataFound.cs @@ -2,51 +2,44 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that gets called when bad data is found. +/// +/// The args. +public delegate void BadDataFound(BadDataFoundArgs args); + +/// +/// Information about the field that caused to be called. +/// +public readonly struct BadDataFoundArgs { /// - /// Function that gets called when bad data is found. + /// The full field unedited. /// - /// The args. - public delegate void BadDataFound(BadDataFoundArgs args); + public readonly string Field; /// - /// Information about the field that caused to be called. + /// The full row unedited. /// - public readonly struct BadDataFoundArgs - { - /// - /// The full field unedited. - /// - public readonly string Field; + public readonly string RawRecord; - /// - /// The full row unedited. - /// - public readonly string RawRecord; - - /// - /// The context. - /// - public readonly CsvContext Context; + /// + /// The context. + /// + public readonly CsvContext Context; - /// - /// Creates a new instance of BadDataFoundArgs. - /// - /// The full field unedited. - /// The full row unedited. - /// The context. - public BadDataFoundArgs(string field, string rawRecord, CsvContext context) - { - Field = field; - RawRecord = rawRecord; - Context = context; - } + /// + /// Creates a new instance of BadDataFoundArgs. + /// + /// The full field unedited. + /// The full row unedited. + /// The context. + public BadDataFoundArgs(string field, string rawRecord, CsvContext context) + { + Field = field; + RawRecord = rawRecord; + Context = context; } } diff --git a/src/CsvHelper/Delegates/ConvertFromString.cs b/src/CsvHelper/Delegates/ConvertFromString.cs index 4ab10d506..ea71326b6 100644 --- a/src/CsvHelper/Delegates/ConvertFromString.cs +++ b/src/CsvHelper/Delegates/ConvertFromString.cs @@ -2,39 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that converts a string into an object. +/// +/// The type of the member. +/// The args. +/// The class object. +public delegate TMember ConvertFromString(ConvertFromStringArgs args); + +/// +/// args. +/// +public readonly struct ConvertFromStringArgs { /// - /// Function that converts a string into an object. + /// The row. /// - /// The type of the member. - /// The args. - /// The class object. - public delegate TMember ConvertFromString(ConvertFromStringArgs args); + public readonly IReaderRow Row; /// - /// args. + /// Creates a new instance of ConvertFromStringArgs. /// - public readonly struct ConvertFromStringArgs + /// The row. + public ConvertFromStringArgs(IReaderRow row) { - /// - /// The row. - /// - public readonly IReaderRow Row; - - /// - /// Creates a new instance of ConvertFromStringArgs. - /// - /// The row. - public ConvertFromStringArgs(IReaderRow row) - { - Row = row; - } + Row = row; } } diff --git a/src/CsvHelper/Delegates/ConvertToString.cs b/src/CsvHelper/Delegates/ConvertToString.cs index 7952da8c4..bff75ec10 100644 --- a/src/CsvHelper/Delegates/ConvertToString.cs +++ b/src/CsvHelper/Delegates/ConvertToString.cs @@ -2,40 +2,33 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that converts an object into a string. +/// +/// The type of the class. +/// The args. +/// The string. +public delegate string ConvertToString(ConvertToStringArgs args); + +/// +/// args. +/// +/// The value to convert. +public readonly struct ConvertToStringArgs { /// - /// Function that converts an object into a string. + /// The value to convert. /// - /// The type of the class. - /// The args. - /// The string. - public delegate string ConvertToString(ConvertToStringArgs args); + public readonly TClass Value; /// - /// args. + /// Creates a new instance of ConvertToStringArgs{TClass}. /// - /// The value to convert. - public readonly struct ConvertToStringArgs + /// The value to convert. + public ConvertToStringArgs(TClass value) { - /// - /// The value to convert. - /// - public readonly TClass Value; - - /// - /// Creates a new instance of ConvertToStringArgs{TClass}. - /// - /// The value to convert. - public ConvertToStringArgs(TClass value) - { - Value = value; - } + Value = value; } } diff --git a/src/CsvHelper/Delegates/GetConstructor.cs b/src/CsvHelper/Delegates/GetConstructor.cs index c6156f91f..eeb180959 100644 --- a/src/CsvHelper/Delegates/GetConstructor.cs +++ b/src/CsvHelper/Delegates/GetConstructor.cs @@ -2,37 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Function that chooses the constructor to use for constructor mapping. +/// +public delegate ConstructorInfo GetConstructor(GetConstructorArgs args); + +/// +/// GetConstructor args. +/// +public readonly struct GetConstructorArgs { /// - /// Function that chooses the constructor to use for constructor mapping. + /// The class type. /// - public delegate ConstructorInfo GetConstructor(GetConstructorArgs args); + public readonly Type ClassType; /// - /// GetConstructor args. + /// Creates a new instance of GetConstructorArgs. /// - public readonly struct GetConstructorArgs + /// The class type. + public GetConstructorArgs(Type classType) { - /// - /// The class type. - /// - public readonly Type ClassType; - - /// - /// Creates a new instance of GetConstructorArgs. - /// - /// The class type. - public GetConstructorArgs(Type classType) - { - ClassType = classType; - } + ClassType = classType; } } diff --git a/src/CsvHelper/Delegates/GetDelimiter.cs b/src/CsvHelper/Delegates/GetDelimiter.cs index c1cf8062b..293c103eb 100644 --- a/src/CsvHelper/Delegates/GetDelimiter.cs +++ b/src/CsvHelper/Delegates/GetDelimiter.cs @@ -3,46 +3,40 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper.Delegates +namespace CsvHelper.Delegates; + +/// +/// Function that resolves the delimiter from the given text. +/// Returns null if no delimiter is found. +/// +/// +/// +public delegate string GetDelimiter(GetDelimiterArgs args); + +/// +/// GetDelimiter args. +/// +public readonly struct GetDelimiterArgs { /// - /// Function that resolves the delimiter from the given text. - /// Returns null if no delimiter is found. + /// The text to resolve the delimiter from. /// - /// - /// - public delegate string GetDelimiter(GetDelimiterArgs args); + public readonly string Text; /// - /// GetDelimiter args. + /// The configuration. /// - public readonly struct GetDelimiterArgs - { - /// - /// The text to resolve the delimiter from. - /// - public readonly string Text; - - /// - /// The configuration. - /// - public readonly IParserConfiguration Configuration; + public readonly IParserConfiguration Configuration; - /// - /// Creates an instance of GetDelimiterArgs. - /// - /// The text to resolve the delimiter from. - /// The configuration. - public GetDelimiterArgs(string text, IParserConfiguration configuration) - { - Text = text; - Configuration = configuration; - } + /// + /// Creates an instance of GetDelimiterArgs. + /// + /// The text to resolve the delimiter from. + /// The configuration. + public GetDelimiterArgs(string text, IParserConfiguration configuration) + { + Text = text; + Configuration = configuration; } } diff --git a/src/CsvHelper/Delegates/GetDynamicPropertyName.cs b/src/CsvHelper/Delegates/GetDynamicPropertyName.cs index c2f043fdc..81b28b48b 100644 --- a/src/CsvHelper/Delegates/GetDynamicPropertyName.cs +++ b/src/CsvHelper/Delegates/GetDynamicPropertyName.cs @@ -2,43 +2,36 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that gets the name to use for the property of the dynamic object. +/// +public delegate string GetDynamicPropertyName(GetDynamicPropertyNameArgs args); + +/// +/// GetDynamicPropertyName args. +/// +public readonly struct GetDynamicPropertyNameArgs { /// - /// Function that gets the name to use for the property of the dynamic object. + /// The field index. /// - public delegate string GetDynamicPropertyName(GetDynamicPropertyNameArgs args); + public readonly int FieldIndex; /// - /// GetDynamicPropertyName args. + /// The context. /// - public readonly struct GetDynamicPropertyNameArgs - { - /// - /// The field index. - /// - public readonly int FieldIndex; - - /// - /// The context. - /// - public readonly CsvContext Context; + public readonly CsvContext Context; - /// - /// Creates a new instance of GetDynamicPropertyNameArgs. - /// - /// The field index. - /// The context. - public GetDynamicPropertyNameArgs(int fieldIndex, CsvContext context) - { - FieldIndex = fieldIndex; - Context = context; - } + /// + /// Creates a new instance of GetDynamicPropertyNameArgs. + /// + /// The field index. + /// The context. + public GetDynamicPropertyNameArgs(int fieldIndex, CsvContext context) + { + FieldIndex = fieldIndex; + Context = context; } } diff --git a/src/CsvHelper/Delegates/HeaderValidated.cs b/src/CsvHelper/Delegates/HeaderValidated.cs index e362e7c60..a521482df 100644 --- a/src/CsvHelper/Delegates/HeaderValidated.cs +++ b/src/CsvHelper/Delegates/HeaderValidated.cs @@ -2,45 +2,38 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that is called when a header validation check is ran. The default function +/// will throw a if there is no header for a given member mapping. +/// You can supply your own function to do other things like logging the issue instead of throwing an exception. +/// +public delegate void HeaderValidated(HeaderValidatedArgs args); + +/// +/// HeaderValidated args. +/// +public readonly struct HeaderValidatedArgs { /// - /// Function that is called when a header validation check is ran. The default function - /// will throw a if there is no header for a given member mapping. - /// You can supply your own function to do other things like logging the issue instead of throwing an exception. + /// The invalid headers. /// - public delegate void HeaderValidated(HeaderValidatedArgs args); + public readonly InvalidHeader[] InvalidHeaders; /// - /// HeaderValidated args. + /// The context. /// - public readonly struct HeaderValidatedArgs - { - /// - /// The invalid headers. - /// - public readonly InvalidHeader[] InvalidHeaders; - - /// - /// The context. - /// - public readonly CsvContext Context; + public readonly CsvContext Context; - /// - /// Creates a new instance of HeaderValidatedArgs. - /// - /// The invalid headers. - /// The context. - public HeaderValidatedArgs(InvalidHeader[] invalidHeaders, CsvContext context) - { - InvalidHeaders = invalidHeaders; - Context = context; - } + /// + /// Creates a new instance of HeaderValidatedArgs. + /// + /// The invalid headers. + /// The context. + public HeaderValidatedArgs(InvalidHeader[] invalidHeaders, CsvContext context) + { + InvalidHeaders = invalidHeaders; + Context = context; } } diff --git a/src/CsvHelper/Delegates/MissingFieldFound.cs b/src/CsvHelper/Delegates/MissingFieldFound.cs index b0f7fd4aa..f9e6b4906 100644 --- a/src/CsvHelper/Delegates/MissingFieldFound.cs +++ b/src/CsvHelper/Delegates/MissingFieldFound.cs @@ -2,52 +2,45 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that is called when a missing field is found. The default function will +/// throw a . You can supply your own function to do other things +/// like logging the issue instead of throwing an exception. +/// +public delegate void MissingFieldFound(MissingFieldFoundArgs args); + +/// +/// MissingFieldFound args. +/// +public readonly struct MissingFieldFoundArgs { /// - /// Function that is called when a missing field is found. The default function will - /// throw a . You can supply your own function to do other things - /// like logging the issue instead of throwing an exception. + /// The header names. /// - public delegate void MissingFieldFound(MissingFieldFoundArgs args); + public readonly string[]? HeaderNames; /// - /// MissingFieldFound args. + /// The index. /// - public readonly struct MissingFieldFoundArgs - { - /// - /// The header names. - /// - public readonly string[] HeaderNames; + public readonly int Index; - /// - /// The index. - /// - public readonly int Index; - - /// - /// The context. - /// - public readonly CsvContext Context; + /// + /// The context. + /// + public readonly CsvContext Context; - /// - /// Creates a new instance of MissingFieldFoundArgs. - /// - /// The header names. - /// The index. - /// The context. - public MissingFieldFoundArgs(string[] headerNames, int index, CsvContext context) - { - HeaderNames = headerNames; - Index = index; - Context = context; - } + /// + /// Creates a new instance of MissingFieldFoundArgs. + /// + /// The header names. + /// The index. + /// The context. + public MissingFieldFoundArgs(string[]? headerNames, int index, CsvContext context) + { + HeaderNames = headerNames; + Index = index; + Context = context; } } diff --git a/src/CsvHelper/Delegates/PrepareHeaderForMatch.cs b/src/CsvHelper/Delegates/PrepareHeaderForMatch.cs index 6b44e61f9..b33f9ca34 100644 --- a/src/CsvHelper/Delegates/PrepareHeaderForMatch.cs +++ b/src/CsvHelper/Delegates/PrepareHeaderForMatch.cs @@ -2,46 +2,39 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that prepares the header field for matching against a member name. +/// The header field and the member name are both ran through this function. +/// You should do things like trimming, removing whitespace, removing underscores, +/// and making casing changes to ignore case. +/// +public delegate string PrepareHeaderForMatch(PrepareHeaderForMatchArgs args); + +/// +/// PrepareHeaderForMatch args. +/// +public readonly struct PrepareHeaderForMatchArgs { /// - /// Function that prepares the header field for matching against a member name. - /// The header field and the member name are both ran through this function. - /// You should do things like trimming, removing whitespace, removing underscores, - /// and making casing changes to ignore case. + /// The header. /// - public delegate string PrepareHeaderForMatch(PrepareHeaderForMatchArgs args); + public readonly string? Header; /// - /// PrepareHeaderForMatch args. + /// The field index. /// - public readonly struct PrepareHeaderForMatchArgs - { - /// - /// The header. - /// - public readonly string Header; - - /// - /// The field index. - /// - public readonly int FieldIndex; + public readonly int FieldIndex; - /// - /// Creates a new instance of PrepareHeaderForMatchArgs. - /// - /// The header. - /// The field index. - public PrepareHeaderForMatchArgs(string header, int fieldIndex) - { - Header = header; - FieldIndex = fieldIndex; - } + /// + /// Creates a new instance of PrepareHeaderForMatchArgs. + /// + /// The header. + /// The field index. + public PrepareHeaderForMatchArgs(string? header, int fieldIndex) + { + Header = header; + FieldIndex = fieldIndex; } } diff --git a/src/CsvHelper/Delegates/ReadingExceptionOccurred.cs b/src/CsvHelper/Delegates/ReadingExceptionOccurred.cs index a3cd5f6ed..280990b44 100644 --- a/src/CsvHelper/Delegates/ReadingExceptionOccurred.cs +++ b/src/CsvHelper/Delegates/ReadingExceptionOccurred.cs @@ -2,39 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that is called when a reading exception occurs. +/// The default function will re-throw the given exception. If you want to ignore +/// reading exceptions, you can supply your own function to do other things like +/// logging the issue. +/// +public delegate bool ReadingExceptionOccurred(ReadingExceptionOccurredArgs args); + +/// +/// ReadingExceptionOccurred args. +/// +public readonly struct ReadingExceptionOccurredArgs { /// - /// Function that is called when a reading exception occurs. - /// The default function will re-throw the given exception. If you want to ignore - /// reading exceptions, you can supply your own function to do other things like - /// logging the issue. + /// The exception. /// - public delegate bool ReadingExceptionOccurred(ReadingExceptionOccurredArgs args); + public readonly CsvHelperException Exception; /// - /// ReadingExceptionOccurred args. + /// Creates a new instance of ReadingExceptionOccurredArgs. /// - public readonly struct ReadingExceptionOccurredArgs + /// The exception. + public ReadingExceptionOccurredArgs(CsvHelperException exception) { - /// - /// The exception. - /// - public readonly CsvHelperException Exception; - - /// - /// Creates a new instance of ReadingExceptionOccurredArgs. - /// - /// The exception. - public ReadingExceptionOccurredArgs(CsvHelperException exception) - { - Exception = exception; - } + Exception = exception; } } diff --git a/src/CsvHelper/Delegates/ReferenceHeaderPrefix.cs b/src/CsvHelper/Delegates/ReferenceHeaderPrefix.cs index 197cf5f4c..fdb83f60f 100644 --- a/src/CsvHelper/Delegates/ReferenceHeaderPrefix.cs +++ b/src/CsvHelper/Delegates/ReferenceHeaderPrefix.cs @@ -2,43 +2,36 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that will return the prefix for a reference header. +/// +public delegate string ReferenceHeaderPrefix(ReferenceHeaderPrefixArgs args); + +/// +/// ReferenceHeaderPrefix args. +/// +public readonly struct ReferenceHeaderPrefixArgs { /// - /// Function that will return the prefix for a reference header. + /// The member type. /// - public delegate string ReferenceHeaderPrefix(ReferenceHeaderPrefixArgs args); + public readonly Type MemberType; /// - /// ReferenceHeaderPrefix args. + /// The member name. /// - public readonly struct ReferenceHeaderPrefixArgs - { - /// - /// The member type. - /// - public readonly Type MemberType; - - /// - /// The member name. - /// - public readonly string MemberName; + public readonly string MemberName; - /// - /// Creates a new instance of ReferenceHeaderPrefixArgs. - /// - /// The member type. - /// The member name. - public ReferenceHeaderPrefixArgs(Type memberType, string memberName) - { - MemberType = memberType; - MemberName = memberName; - } + /// + /// Creates a new instance of ReferenceHeaderPrefixArgs. + /// + /// The member type. + /// The member name. + public ReferenceHeaderPrefixArgs(Type memberType, string memberName) + { + MemberType = memberType; + MemberName = memberName; } } diff --git a/src/CsvHelper/Delegates/ShouldQuote.cs b/src/CsvHelper/Delegates/ShouldQuote.cs index 9c7c35ad2..2affb7c67 100644 --- a/src/CsvHelper/Delegates/ShouldQuote.cs +++ b/src/CsvHelper/Delegates/ShouldQuote.cs @@ -2,50 +2,43 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that is used to determine if a field should get quoted when writing. +/// +public delegate bool ShouldQuote(ShouldQuoteArgs args); + +/// +/// ShouldQuote args. +/// +public readonly struct ShouldQuoteArgs { /// - /// Function that is used to determine if a field should get quoted when writing. + /// The field. /// - public delegate bool ShouldQuote(ShouldQuoteArgs args); + public readonly string? Field; /// - /// ShouldQuote args. + /// The field type. /// - public readonly struct ShouldQuoteArgs - { - /// - /// The field. - /// - public readonly string Field; + public readonly Type FieldType; - /// - /// The field type. - /// - public readonly Type FieldType; - - /// - /// The row. - /// - public readonly IWriterRow Row; + /// + /// The row. + /// + public readonly IWriterRow Row; - /// - /// Creates a new instance of ShouldQuoteArgs. - /// - /// The field. - /// The field type. - /// The row. - public ShouldQuoteArgs(string field, Type fieldType, IWriterRow row) - { - Field = field; - FieldType = fieldType; - Row = row; - } + /// + /// Creates a new instance of ShouldQuoteArgs. + /// + /// The field. + /// The field type. + /// The row. + public ShouldQuoteArgs(string? field, Type fieldType, IWriterRow row) + { + Field = field; + FieldType = fieldType; + Row = row; } } diff --git a/src/CsvHelper/Delegates/ShouldSkipRecord.cs b/src/CsvHelper/Delegates/ShouldSkipRecord.cs index 2e8f26377..7131b1322 100644 --- a/src/CsvHelper/Delegates/ShouldSkipRecord.cs +++ b/src/CsvHelper/Delegates/ShouldSkipRecord.cs @@ -2,36 +2,29 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that determines whether to skip the given record or not. +/// +public delegate bool ShouldSkipRecord(ShouldSkipRecordArgs args); + +/// +/// ShouldSkipRecord args. +/// +public readonly struct ShouldSkipRecordArgs { /// - /// Function that determines whether to skip the given record or not. + /// The record. /// - public delegate bool ShouldSkipRecord(ShouldSkipRecordArgs args); + public readonly IReaderRow Row; /// - /// ShouldSkipRecord args. + /// Creates a new instance of ShouldSkipRecordArgs. /// - public readonly struct ShouldSkipRecordArgs + /// The row. + public ShouldSkipRecordArgs(IReaderRow row) { - /// - /// The record. - /// - public readonly IReaderRow Row; - - /// - /// Creates a new instance of ShouldSkipRecordArgs. - /// - /// The row. - public ShouldSkipRecordArgs(IReaderRow row) - { - Row = row; - } + Row = row; } } diff --git a/src/CsvHelper/Delegates/ShouldUseConstructorParameters.cs b/src/CsvHelper/Delegates/ShouldUseConstructorParameters.cs index 473dde3ca..5d86cc5f5 100644 --- a/src/CsvHelper/Delegates/ShouldUseConstructorParameters.cs +++ b/src/CsvHelper/Delegates/ShouldUseConstructorParameters.cs @@ -2,37 +2,30 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that determines if constructor parameters should be used to create +/// the class instead of the default constructor and members. +/// +public delegate bool ShouldUseConstructorParameters(ShouldUseConstructorParametersArgs args); + +/// +/// ShouldUseConstructorParameters args. +/// +public readonly struct ShouldUseConstructorParametersArgs { /// - /// Function that determines if constructor parameters should be used to create - /// the class instead of the default constructor and members. + /// The parameter type. /// - public delegate bool ShouldUseConstructorParameters(ShouldUseConstructorParametersArgs args); + public readonly Type ParameterType; /// - /// ShouldUseConstructorParameters args. + /// Creates a new instance of ShouldUseConstructorParametersArgs. /// - public readonly struct ShouldUseConstructorParametersArgs + /// The parameter type. + public ShouldUseConstructorParametersArgs(Type parameterType) { - /// - /// The parameter type. - /// - public readonly Type ParameterType; - - /// - /// Creates a new instance of ShouldUseConstructorParametersArgs. - /// - /// The parameter type. - public ShouldUseConstructorParametersArgs(Type parameterType) - { - ParameterType = parameterType; - } + ParameterType = parameterType; } } diff --git a/src/CsvHelper/Delegates/Validate.cs b/src/CsvHelper/Delegates/Validate.cs index 4b0489ff2..558115224 100644 --- a/src/CsvHelper/Delegates/Validate.cs +++ b/src/CsvHelper/Delegates/Validate.cs @@ -2,52 +2,45 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Function that validates a field. +/// +/// The args. +/// true if the field is valid, otherwise false. +public delegate bool Validate(ValidateArgs args); + +/// +/// Function that gets the exception message when validation fails. +/// +/// The args. +/// The exception message. +public delegate string ValidateMessage(ValidateArgs args); + +/// +/// Validate args. +/// +public readonly struct ValidateArgs { /// - /// Function that validates a field. + /// The field. /// - /// The args. - /// true if the field is valid, otherwise false. - public delegate bool Validate(ValidateArgs args); + public readonly string Field; /// - /// Function that gets the exception message when validation fails. + /// The row. /// - /// The args. - /// The exception message. - public delegate string ValidateMessage(ValidateArgs args); + public readonly IReaderRow Row; /// - /// Validate args. + /// Creates a new instance of ValidateArgs. /// - public readonly struct ValidateArgs + /// The field. + /// The row. + public ValidateArgs(string field, IReaderRow row) { - /// - /// The field. - /// - public readonly string Field; - - /// - /// The row. - /// - public readonly IReaderRow Row; - - /// - /// Creates a new instance of ValidateArgs. - /// - /// The field. - /// The row. - public ValidateArgs(string field, IReaderRow row) - { - Field = field; - Row = row; - } + Field = field; + Row = row; } } diff --git a/src/CsvHelper/Expressions/DynamicRecordCreator.cs b/src/CsvHelper/Expressions/DynamicRecordCreator.cs index 1d6b857c8..7b70028f2 100644 --- a/src/CsvHelper/Expressions/DynamicRecordCreator.cs +++ b/src/CsvHelper/Expressions/DynamicRecordCreator.cs @@ -2,59 +2,55 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; +namespace CsvHelper.Expressions; -namespace CsvHelper.Expressions +/// +/// Creates dynamic records. +/// +public class DynamicRecordCreator : RecordCreator { /// - /// Creates dynamic records. + /// Initializes a new instance. /// - public class DynamicRecordCreator : RecordCreator - { - /// - /// Initializes a new instance. - /// - /// The reader. - public DynamicRecordCreator(CsvReader reader) : base(reader) { } + /// The reader. + public DynamicRecordCreator(CsvReader reader) : base(reader) { } - /// - /// Creates a of type - /// that will create a record of the given type using the current - /// reader row. - /// - /// The record type. - protected override Delegate CreateCreateRecordDelegate(Type recordType) => (Func)CreateDynamicRecord; + /// + /// Creates a of type + /// that will create a record of the given type using the current + /// reader row. + /// + /// The record type. + protected override Delegate CreateCreateRecordDelegate(Type recordType) => (Func)CreateDynamicRecord; - /// - /// Creates a dynamic record of the current reader row. - /// - protected virtual dynamic CreateDynamicRecord() + /// + /// Creates a dynamic record of the current reader row. + /// + protected virtual dynamic CreateDynamicRecord() + { + var obj = new FastDynamicObject(); + var dict = obj as IDictionary; + if (Reader.HeaderRecord != null) { - var obj = new FastDynamicObject(); - var dict = obj as IDictionary; - if (Reader.HeaderRecord != null) + for (var i = 0; i < Reader.HeaderRecord.Length; i++) { - for (var i = 0; i < Reader.HeaderRecord.Length; i++) - { - var args = new GetDynamicPropertyNameArgs(i, Reader.Context); - var propertyName = Reader.Configuration.GetDynamicPropertyName(args); - Reader.TryGetField(i, out string field); - dict[propertyName] = field; - } + var args = new GetDynamicPropertyNameArgs(i, Reader.Context); + var propertyName = Reader.Configuration.GetDynamicPropertyName(args); + Reader.TryGetField(i, out string? field); + dict[propertyName] = field; } - else + } + else + { + for (var i = 0; i < Reader.Parser.Count; i++) { - for (var i = 0; i < Reader.Parser.Count; i++) - { - var args = new GetDynamicPropertyNameArgs(i, Reader.Context); - var propertyName = Reader.Configuration.GetDynamicPropertyName(args); - var field = Reader.GetField(i); - dict[propertyName] = field; - } + var args = new GetDynamicPropertyNameArgs(i, Reader.Context); + var propertyName = Reader.Configuration.GetDynamicPropertyName(args); + var field = Reader.GetField(i); + dict[propertyName] = field; } - - return obj; } + + return obj; } } diff --git a/src/CsvHelper/Expressions/DynamicRecordWriter.cs b/src/CsvHelper/Expressions/DynamicRecordWriter.cs index 5e79fb058..376cd40ea 100644 --- a/src/CsvHelper/Expressions/DynamicRecordWriter.cs +++ b/src/CsvHelper/Expressions/DynamicRecordWriter.cs @@ -3,73 +3,70 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using Microsoft.CSharp.RuntimeBinder; -using System; using System.Collections; using System.Dynamic; -using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Write dynamic records. +/// +public class DynamicRecordWriter : RecordWriter { + private readonly Hashtable getters = new Hashtable(); + /// - /// Write dynamic records. + /// Initializes a new instance using the given writer. /// - public class DynamicRecordWriter : RecordWriter - { - private readonly Hashtable getters = new Hashtable(); + /// The writer. + public DynamicRecordWriter(CsvWriter writer) : base(writer) { } - /// - /// Initializes a new instance using the given writer. - /// - /// The writer. - public DynamicRecordWriter(CsvWriter writer) : base(writer) { } + /// + /// Creates a of type + /// that will write the given record using the current writer row. + /// + /// The record type. + /// The type for the record. + protected override Action CreateWriteDelegate(Type type) + { + // http://stackoverflow.com/a/14011692/68499 - /// - /// Creates a of type - /// that will write the given record using the current writer row. - /// - /// The record type. - /// The type for the record. - protected override Action CreateWriteDelegate(Type type) + Action action = r => { - // http://stackoverflow.com/a/14011692/68499 + var provider = (IDynamicMetaObjectProvider)r!; + var type = provider.GetType(); - Action action = r => + var parameterExpression = Expression.Parameter(typeof(T), "record"); + var metaObject = provider.GetMetaObject(parameterExpression); + var memberNames = metaObject.GetDynamicMemberNames(); + if (Writer.Configuration.DynamicPropertySort != null) { - var provider = (IDynamicMetaObjectProvider)r; - var type = provider.GetType(); + memberNames = memberNames.OrderBy(name => name, Writer.Configuration.DynamicPropertySort); + } - var parameterExpression = Expression.Parameter(typeof(T), "record"); - var metaObject = provider.GetMetaObject(parameterExpression); - var memberNames = metaObject.GetDynamicMemberNames(); - if (Writer.Configuration.DynamicPropertySort != null) - { - memberNames = memberNames.OrderBy(name => name, Writer.Configuration.DynamicPropertySort); - } + foreach (var name in memberNames) + { + var value = GetValue(name, provider); + Writer.WriteField(value); + } + }; - foreach (var name in memberNames) - { - var value = GetValue(name, provider); - Writer.WriteField(value); - } - }; + return action; + } - return action; - } + private object GetValue(string name, IDynamicMetaObjectProvider target) + { + // https://stackoverflow.com/a/30757547/68499 - private object GetValue(string name, IDynamicMetaObjectProvider target) + var callSite = (CallSite>?)getters[name]; + if (callSite == null) { - // https://stackoverflow.com/a/30757547/68499 - - var callSite = (CallSite>)getters[name]; - if (callSite == null) - { - var getMemberBinder = Binder.GetMember(CSharpBinderFlags.None, name, typeof(DynamicRecordWriter), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); - getters[name] = callSite = CallSite>.Create(getMemberBinder); - } - - return callSite.Target(callSite, target); + var getMemberBinder = Binder.GetMember(CSharpBinderFlags.None, name, typeof(DynamicRecordWriter), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); + getters[name] = callSite = CallSite>.Create(getMemberBinder); } + + return callSite.Target(callSite, target); } } diff --git a/src/CsvHelper/Expressions/ExpandoObjectRecordWriter.cs b/src/CsvHelper/Expressions/ExpandoObjectRecordWriter.cs index 7d75c4985..9b37274ae 100644 --- a/src/CsvHelper/Expressions/ExpandoObjectRecordWriter.cs +++ b/src/CsvHelper/Expressions/ExpandoObjectRecordWriter.cs @@ -2,48 +2,43 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; +namespace CsvHelper.Expressions; -namespace CsvHelper.Expressions +/// +/// Writes expando objects. +/// +public class ExpandoObjectRecordWriter : RecordWriter { /// - /// Writes expando objects. + /// Initializes a new instance using the given writer. /// - public class ExpandoObjectRecordWriter : RecordWriter - { - /// - /// Initializes a new instance using the given writer. - /// - /// The writer. - public ExpandoObjectRecordWriter(CsvWriter writer) : base(writer) { } + /// The writer. + public ExpandoObjectRecordWriter(CsvWriter writer) : base(writer) { } - /// - /// Creates a of type - /// that will write the given record using the current writer row. - /// - /// The record type. - /// The record. - protected override Action CreateWriteDelegate(Type recordType) + /// + /// Creates a of type + /// that will write the given record using the current writer row. + /// + /// The record type. + /// The record. + protected override Action CreateWriteDelegate(Type recordType) + { + Action action = r => { - Action action = r => - { - var dict = ((IDictionary)r).AsEnumerable(); + var dict = ((IDictionary)r!).AsEnumerable(); - if (Writer.Configuration.DynamicPropertySort != null) - { - dict = dict.OrderBy(pair => pair.Key, Writer.Configuration.DynamicPropertySort); - } + if (Writer.Configuration.DynamicPropertySort != null) + { + dict = dict.OrderBy(pair => pair.Key, Writer.Configuration.DynamicPropertySort); + } - var values = dict.Select(pair => pair.Value); - foreach (var val in values) - { - Writer.WriteField(val); - } - }; + var values = dict.Select(pair => pair.Value); + foreach (var val in values) + { + Writer.WriteField(val); + } + }; - return action; - } + return action; } } diff --git a/src/CsvHelper/Expressions/ExpressionManager.cs b/src/CsvHelper/Expressions/ExpressionManager.cs index ef9c72112..ef22fc2aa 100644 --- a/src/CsvHelper/Expressions/ExpressionManager.cs +++ b/src/CsvHelper/Expressions/ExpressionManager.cs @@ -4,487 +4,495 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; using CsvHelper.TypeConversion; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Manages expression creation. +/// +public class ExpressionManager { + private readonly CsvReader? reader; + private readonly CsvWriter? writer; + /// - /// Manages expression creation. + /// Initializes a new instance using the given reader. /// - public class ExpressionManager + /// The reader. + public ExpressionManager(CsvReader reader) { - private readonly CsvReader reader; - private readonly CsvWriter writer; - - /// - /// Initializes a new instance using the given reader. - /// - /// The reader. - public ExpressionManager(CsvReader reader) - { - this.reader = reader; - } + this.reader = reader; + } - /// - /// Initializes a new instance using the given writer. - /// - /// The writer. - public ExpressionManager(CsvWriter writer) - { - this.writer = writer; - } + /// + /// Initializes a new instance using the given writer. + /// + /// The writer. + public ExpressionManager(CsvWriter writer) + { + this.writer = writer; + } - /// - /// Creates the constructor arguments used to create a type. - /// - /// The mapping to create the arguments for. - /// The arguments that will be added to the mapping. - public virtual void CreateConstructorArgumentExpressionsForMapping(ClassMap map, List argumentExpressions) + /// + /// Creates the constructor arguments used to create a type. + /// + /// The mapping to create the arguments for. + /// The arguments that will be added to the mapping. + public virtual void CreateConstructorArgumentExpressionsForMapping(ClassMap map, List argumentExpressions) + { + if (reader == null) throw new InvalidOperationException("Reader is null"); + + foreach (var parameterMap in map.ParameterMaps) { - foreach (var parameterMap in map.ParameterMaps) + if (parameterMap.Data.IsConstantSet) { - if (parameterMap.Data.IsConstantSet) - { - var constantExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Constant), parameterMap.Data.Parameter.ParameterType); - argumentExpressions.Add(constantExpression); + var constantExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Constant), parameterMap.Data.Parameter.ParameterType); + argumentExpressions.Add(constantExpression); - continue; - } + continue; + } - if (parameterMap.Data.Ignore) + if (parameterMap.Data.Ignore) + { + Expression defaultExpression; + if (parameterMap.Data.IsDefaultSet) { - Expression defaultExpression; - if (parameterMap.Data.IsDefaultSet) - { - defaultExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Default), parameterMap.Data.Parameter.ParameterType); - } - else if (parameterMap.Data.Parameter.HasDefaultValue) - { - defaultExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Parameter.DefaultValue), parameterMap.Data.Parameter.ParameterType); - } - else - { - defaultExpression = Expression.Default(parameterMap.Data.Parameter.ParameterType); - } - - argumentExpressions.Add(defaultExpression); - - continue; + defaultExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Default), parameterMap.Data.Parameter.ParameterType); } - - if (parameterMap.ConstructorTypeMap != null) + else if (parameterMap.Data.Parameter.HasDefaultValue) { - // Constructor parameter type. - var arguments = new List(); - CreateConstructorArgumentExpressionsForMapping(parameterMap.ConstructorTypeMap, arguments); - var args = new GetConstructorArgs(parameterMap.ConstructorTypeMap.ClassType); - var constructorExpression = Expression.New(reader.Configuration.GetConstructor(args), arguments); - - argumentExpressions.Add(constructorExpression); + defaultExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Parameter.DefaultValue), parameterMap.Data.Parameter.ParameterType); } - else if (parameterMap.ReferenceMap != null) + else { - // Reference type. + defaultExpression = Expression.Default(parameterMap.Data.Parameter.ParameterType); + } - var referenceAssignments = new List(); - CreateMemberAssignmentsForMapping(parameterMap.ReferenceMap.Data.Mapping, referenceAssignments); + argumentExpressions.Add(defaultExpression); - var referenceBody = CreateInstanceAndAssignMembers(parameterMap.ReferenceMap.Data.Parameter.ParameterType, referenceAssignments); - argumentExpressions.Add(referenceBody); - } - else - { - // Value type. + continue; + } - int index; - if (reader.Configuration.HasHeaderRecord && (parameterMap.Data.IsNameSet || !parameterMap.Data.IsIndexSet)) + if (parameterMap.ConstructorTypeMap != null) + { + // Constructor parameter type. + var arguments = new List(); + CreateConstructorArgumentExpressionsForMapping(parameterMap.ConstructorTypeMap, arguments); + var args = new GetConstructorArgs(parameterMap.ConstructorTypeMap.ClassType); + var constructorExpression = Expression.New(reader.Configuration.GetConstructor(args), arguments); + + argumentExpressions.Add(constructorExpression); + } + else if (parameterMap.ReferenceMap != null) + { + // Reference type. + + var referenceAssignments = new List(); + CreateMemberAssignmentsForMapping(parameterMap.ReferenceMap.Data.Mapping, referenceAssignments); + + var referenceBody = CreateInstanceAndAssignMembers(parameterMap.ReferenceMap.Data.Parameter.ParameterType, referenceAssignments); + argumentExpressions.Add(referenceBody); + } + else + { + // Value type. + + int index; + if (reader.Configuration.HasHeaderRecord && (parameterMap.Data.IsNameSet || !parameterMap.Data.IsIndexSet)) + { + // Use name. + index = reader.GetFieldIndex(parameterMap.Data.Names, parameterMap.Data.NameIndex, parameterMap.Data.IsOptional); + if (index == -1) { - // Use name. - index = reader.GetFieldIndex(parameterMap.Data.Names, parameterMap.Data.NameIndex, parameterMap.Data.IsOptional); - if (index == -1) + if (parameterMap.Data.IsDefaultSet || parameterMap.Data.IsOptional) { - if (parameterMap.Data.IsDefaultSet || parameterMap.Data.IsOptional) - { - var defaultExpression = CreateDefaultExpression(parameterMap, Expression.Constant(string.Empty)); - argumentExpressions.Add(defaultExpression); - continue; - } - - // Skip if the index was not found. + var defaultExpression = CreateDefaultExpression(parameterMap, Expression.Constant(string.Empty)); + argumentExpressions.Add(defaultExpression); continue; } - } - else if (!parameterMap.Data.IsIndexSet && parameterMap.Data.IsOptional) - { - // If there wasn't an index explicitly, use a default value since constructors need all - // arguments to be created. - var defaultExpression = CreateDefaultExpression(parameterMap, Expression.Constant(string.Empty)); - argumentExpressions.Add(defaultExpression); - continue; - } - else - { - // Use index. - index = parameterMap.Data.Index; - } - - // Get the field using the field index. - var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) }).GetGetMethod(); - Expression fieldExpression = Expression.Call(Expression.Constant(reader), method, Expression.Constant(index, typeof(int))); - if (parameterMap.Data.IsDefaultSet) - { - fieldExpression = CreateDefaultExpression(parameterMap, fieldExpression); - } - else - { - fieldExpression = CreateTypeConverterExpression(parameterMap, fieldExpression); + // Skip if the index was not found. + continue; } - - argumentExpressions.Add(fieldExpression); } - } - } - - /// - /// Creates the member assignments for the given . - /// - /// The mapping to create the assignments for. - /// The assignments that will be added to from the mapping. - public virtual void CreateMemberAssignmentsForMapping(ClassMap mapping, List assignments) - { - foreach (var memberMap in mapping.MemberMaps) - { - var fieldExpression = CreateGetFieldExpression(memberMap); - if (fieldExpression == null) + else if (!parameterMap.Data.IsIndexSet && parameterMap.Data.IsOptional) { + // If there wasn't an index explicitly, use a default value since constructors need all + // arguments to be created. + var defaultExpression = CreateDefaultExpression(parameterMap, Expression.Constant(string.Empty)); + argumentExpressions.Add(defaultExpression); continue; } - - assignments.Add(Expression.Bind(memberMap.Data.Member, fieldExpression)); - } - - foreach (var referenceMap in mapping.ReferenceMaps) - { - if (!reader.CanRead(referenceMap)) + else { - continue; + // Use index. + index = parameterMap.Data.Index; } - Expression referenceBody; - if (referenceMap.Data.Mapping.ParameterMaps.Count > 0) + // Get the field using the field index. + var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) })!.GetGetMethod()!; + Expression fieldExpression = Expression.Call(Expression.Constant(reader), method, Expression.Constant(index, typeof(int))); + + if (parameterMap.Data.IsDefaultSet) { - var arguments = new List(); - CreateConstructorArgumentExpressionsForMapping(referenceMap.Data.Mapping, arguments); - var args = new GetConstructorArgs(referenceMap.Data.Mapping.ClassType); - referenceBody = Expression.New(reader.Configuration.GetConstructor(args), arguments); + fieldExpression = CreateDefaultExpression(parameterMap, fieldExpression); } else { - var referenceAssignments = new List(); - CreateMemberAssignmentsForMapping(referenceMap.Data.Mapping, referenceAssignments); - referenceBody = CreateInstanceAndAssignMembers(referenceMap.Data.Member.MemberType(), referenceAssignments); + fieldExpression = CreateTypeConverterExpression(parameterMap, fieldExpression); } - assignments.Add(Expression.Bind(referenceMap.Data.Member, referenceBody)); + argumentExpressions.Add(fieldExpression); } } + } - /// - /// Creates an expression the represents getting the field for the given - /// member and converting it to the member's type. - /// - /// The mapping for the member. - public virtual Expression CreateGetFieldExpression(MemberMap memberMap) + /// + /// Creates the member assignments for the given . + /// + /// The mapping to create the assignments for. + /// The assignments that will be added to from the mapping. + public virtual void CreateMemberAssignmentsForMapping(ClassMap mapping, List assignments) + { + if (reader == null) throw new InvalidOperationException("Reader is null"); + + foreach (var memberMap in mapping.MemberMaps) { - if (memberMap.Data.ReadingConvertExpression != null) + var fieldExpression = CreateGetFieldExpression(memberMap); + if (fieldExpression == null) { - // The user is providing the expression to do the conversion. - Expression exp = Expression.Invoke(memberMap.Data.ReadingConvertExpression, Expression.Constant(new ConvertFromStringArgs(reader))); - return Expression.Convert(exp, memberMap.Data.Member.MemberType()); + continue; } - if (!reader.CanRead(memberMap)) - { - return null; - } + assignments.Add(Expression.Bind(memberMap.Data.Member!, fieldExpression)); + } - if (memberMap.Data.IsConstantSet) + foreach (var referenceMap in mapping.ReferenceMaps) + { + if (!reader.CanRead(referenceMap)) { - return Expression.Convert(Expression.Constant(memberMap.Data.Constant), memberMap.Data.Member.MemberType()); + continue; } - if (memberMap.Data.TypeConverter == null) + Expression referenceBody; + if (referenceMap.Data.Mapping.ParameterMaps.Count > 0) { - // Skip if the type isn't convertible. - return null; - } - - int index; - if (reader.Configuration.HasHeaderRecord && (memberMap.Data.IsNameSet || !memberMap.Data.IsIndexSet)) - { - // Use the name. - index = reader.GetFieldIndex(memberMap.Data.Names, memberMap.Data.NameIndex, memberMap.Data.IsOptional); - if (index == -1) - { - if (memberMap.Data.IsDefaultSet) - { - return CreateDefaultExpression(memberMap, Expression.Constant(string.Empty)); - } - - // Skip if the index was not found. - return null; - } + var arguments = new List(); + CreateConstructorArgumentExpressionsForMapping(referenceMap.Data.Mapping, arguments); + var args = new GetConstructorArgs(referenceMap.Data.Mapping.ClassType); + referenceBody = Expression.New(reader.Configuration.GetConstructor(args), arguments); } else { - // Use the index. - index = memberMap.Data.Index; + var referenceAssignments = new List(); + CreateMemberAssignmentsForMapping(referenceMap.Data.Mapping, referenceAssignments); + referenceBody = CreateInstanceAndAssignMembers(referenceMap.Data.Member.MemberType(), referenceAssignments); } - // Get the field using the field index. - var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) }).GetGetMethod(); - Expression fieldExpression = Expression.Call(Expression.Constant(reader), method, Expression.Constant(index, typeof(int))); - - // Validate the field. - if (memberMap.Data.ValidateExpression != null) - { - var constructor = typeof(ValidateArgs).GetConstructor(new Type[] { typeof(string), typeof(IReaderRow) }); - var args = Expression.New(constructor, fieldExpression, Expression.Constant(reader)); - var validateExpression = Expression.IsFalse(Expression.Invoke(memberMap.Data.ValidateExpression, args)); - var validationExceptionConstructor = typeof(FieldValidationException).GetConstructor(new Type[] { typeof(CsvContext), typeof(string), typeof(string) }); - var messageExpression = Expression.Invoke(memberMap.Data.ValidateMessageExpression, args); - var newValidationExceptionExpression = Expression.New(validationExceptionConstructor, Expression.Constant(reader.Context), fieldExpression, messageExpression); - var throwExpression = Expression.Throw(newValidationExceptionExpression); - fieldExpression = Expression.Block( - // If the validate method returns false, throw an exception. - Expression.IfThen(validateExpression, throwExpression), - fieldExpression - ); - } + assignments.Add(Expression.Bind(referenceMap.Data.Member, referenceBody)); + } + } - if (memberMap.Data.IsDefaultSet) - { - return CreateDefaultExpression(memberMap, fieldExpression); - } + /// + /// Creates an expression the represents getting the field for the given + /// member and converting it to the member's type. + /// + /// The mapping for the member. + public virtual Expression? CreateGetFieldExpression(MemberMap memberMap) + { + if (reader == null) throw new InvalidOperationException("Reader is null"); - fieldExpression = CreateTypeConverterExpression(memberMap, fieldExpression); + if (memberMap.Data.ReadingConvertExpression != null) + { + // The user is providing the expression to do the conversion. + Expression exp = Expression.Invoke(memberMap.Data.ReadingConvertExpression, Expression.Constant(new ConvertFromStringArgs(reader))); + return Expression.Convert(exp, memberMap.Data.Member!.MemberType()); + } - return fieldExpression; + if (!reader.CanRead(memberMap)) + { + return null; } - /// - /// Creates a member expression for the given member on the record. - /// This will recursively traverse the mapping to find the member - /// and create a safe member accessor for each level as it goes. - /// - /// The current member expression. - /// The mapping to look for the member to map on. - /// The member map to look for on the mapping. - /// An Expression to access the given member. - public virtual Expression CreateGetMemberExpression(Expression recordExpression, ClassMap mapping, MemberMap memberMap) + if (memberMap.Data.IsConstantSet) { - if (mapping.MemberMaps.Any(mm => mm == memberMap)) - { - // The member is on this level. - if (memberMap.Data.Member is PropertyInfo) - { - return Expression.Property(recordExpression, (PropertyInfo)memberMap.Data.Member); - } + return Expression.Convert(Expression.Constant(memberMap.Data.Constant), memberMap.Data.Member!.MemberType()); + } - if (memberMap.Data.Member is FieldInfo) - { - return Expression.Field(recordExpression, (FieldInfo)memberMap.Data.Member); - } - } + if (memberMap.Data.TypeConverter == null) + { + // Skip if the type isn't convertible. + return null; + } - // The member isn't on this level of the mapping. - // We need to search down through the reference maps. - foreach (var refMap in mapping.ReferenceMaps) + int index; + if (reader.Configuration.HasHeaderRecord && (memberMap.Data.IsNameSet || !memberMap.Data.IsIndexSet)) + { + // Use the name. + index = reader.GetFieldIndex(memberMap.Data.Names, memberMap.Data.NameIndex, memberMap.Data.IsOptional); + if (index == -1) { - var wrapped = refMap.Data.Member.GetMemberExpression(recordExpression); - var memberExpression = CreateGetMemberExpression(wrapped, refMap.Data.Mapping, memberMap); - if (memberExpression == null) - { - continue; - } - - if (refMap.Data.Member.MemberType().GetTypeInfo().IsValueType) - { - return memberExpression; - } - - var nullCheckExpression = Expression.Equal(wrapped, Expression.Constant(null)); - - var isValueType = memberMap.Data.Member.MemberType().GetTypeInfo().IsValueType; - var isGenericType = isValueType && memberMap.Data.Member.MemberType().GetTypeInfo().IsGenericType; - Type memberType; - if (isValueType && !isGenericType && !writer.Configuration.UseNewObjectForNullReferenceMembers) - { - memberType = typeof(Nullable<>).MakeGenericType(memberMap.Data.Member.MemberType()); - memberExpression = Expression.Convert(memberExpression, memberType); - } - else + if (memberMap.Data.IsDefaultSet) { - memberType = memberMap.Data.Member.MemberType(); + return CreateDefaultExpression(memberMap, Expression.Constant(string.Empty)); } - var defaultValueExpression = isValueType && !isGenericType - ? (Expression)Expression.New(memberType) - : Expression.Constant(null, memberType); - var conditionExpression = Expression.Condition(nullCheckExpression, defaultValueExpression, memberExpression); - return conditionExpression; + // Skip if the index was not found. + return null; } - - return null; } + else + { + // Use the index. + index = memberMap.Data.Index; + } + + // Get the field using the field index. + var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) })!.GetGetMethod()!; + Expression fieldExpression = Expression.Call(Expression.Constant(reader), method, Expression.Constant(index, typeof(int))); - /// - /// Creates an instance of the given type using , then assigns - /// the given member assignments to that instance. - /// - /// The type of the record we're creating. - /// The member assignments that will be assigned to the created instance. - /// A representing the instance creation and assignments. - public virtual BlockExpression CreateInstanceAndAssignMembers(Type recordType, List assignments) + // Validate the field. + if (memberMap.Data.ValidateExpression != null) { - var expressions = new List(); - var createInstanceMethod = typeof(IObjectResolver).GetMethod(nameof(IObjectResolver.Resolve), new Type[] { typeof(Type), typeof(object[]) }); - var instanceExpression = Expression.Convert(Expression.Call(Expression.Constant(ObjectResolver.Current), createInstanceMethod, Expression.Constant(recordType), Expression.Constant(new object[0])), recordType); - var variableExpression = Expression.Variable(instanceExpression.Type, "instance"); - expressions.Add(Expression.Assign(variableExpression, instanceExpression)); - expressions.AddRange(assignments.Select(b => Expression.Assign(Expression.MakeMemberAccess(variableExpression, b.Member), b.Expression))); - expressions.Add(variableExpression); - var variables = new ParameterExpression[] { variableExpression }; - var blockExpression = Expression.Block(variables, expressions); - - return blockExpression; + var constructor = typeof(ValidateArgs).GetConstructor(new Type[] { typeof(string), typeof(IReaderRow) })!; + var args = Expression.New(constructor, fieldExpression, Expression.Constant(reader)); + var validateExpression = Expression.IsFalse(Expression.Invoke(memberMap.Data.ValidateExpression, args)); + var validationExceptionConstructor = typeof(FieldValidationException).GetConstructor(new Type[] { typeof(CsvContext), typeof(string), typeof(string) })!; + var messageExpression = Expression.Invoke(memberMap.Data.ValidateMessageExpression!, args); + var newValidationExceptionExpression = Expression.New(validationExceptionConstructor, Expression.Constant(reader.Context), fieldExpression, messageExpression); + var throwExpression = Expression.Throw(newValidationExceptionExpression); + fieldExpression = Expression.Block( + // If the validate method returns false, throw an exception. + Expression.IfThen(validateExpression, throwExpression), + fieldExpression + ); } - /// - /// Creates an expression that converts the field expression using a type converter. - /// - /// The mapping for the member. - /// The field expression. - public virtual Expression CreateTypeConverterExpression(MemberMap memberMap, Expression fieldExpression) + if (memberMap.Data.IsDefaultSet) { - memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = reader.Configuration.CultureInfo }, reader.Context.TypeConverterOptionsCache.GetOptions(memberMap.Data.Member.MemberType()), memberMap.Data.TypeConverterOptions); + return CreateDefaultExpression(memberMap, fieldExpression); + } - Expression typeConverterFieldExpression = Expression.Call(Expression.Constant(memberMap.Data.TypeConverter), nameof(ITypeConverter.ConvertFromString), null, fieldExpression, Expression.Constant(reader), Expression.Constant(memberMap.Data)); - typeConverterFieldExpression = Expression.Convert(typeConverterFieldExpression, memberMap.Data.Member.MemberType()); + fieldExpression = CreateTypeConverterExpression(memberMap, fieldExpression); - return typeConverterFieldExpression; - } + return fieldExpression; + } + + /// + /// Creates a member expression for the given member on the record. + /// This will recursively traverse the mapping to find the member + /// and create a safe member accessor for each level as it goes. + /// + /// The current member expression. + /// The mapping to look for the member to map on. + /// The member map to look for on the mapping. + /// An Expression to access the given member. + public virtual Expression? CreateGetMemberExpression(Expression recordExpression, ClassMap mapping, MemberMap memberMap) + { + if (writer == null) throw new InvalidOperationException("Writer is null"); - /// - /// Creates an expression that converts the field expression using a type converter. - /// - /// The mapping for the parameter. - /// The field expression. - public virtual Expression CreateTypeConverterExpression(ParameterMap parameterMap, Expression fieldExpression) + if (mapping.MemberMaps.Any(mm => mm == memberMap)) { - parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge - ( - new TypeConverterOptions { CultureInfo = reader.Configuration.CultureInfo }, - reader.Context.TypeConverterOptionsCache.GetOptions(parameterMap.Data.Parameter.ParameterType), - parameterMap.Data.TypeConverterOptions - ); + // The member is on this level. + if (memberMap.Data.Member is PropertyInfo) + { + return Expression.Property(recordExpression, (PropertyInfo)memberMap.Data.Member); + } - var memberMapData = new MemberMapData(null) + if (memberMap.Data.Member is FieldInfo) { - Constant = parameterMap.Data.Constant, - Default = parameterMap.Data.Default, - Ignore = parameterMap.Data.Ignore, - Index = parameterMap.Data.Index, - IsConstantSet = parameterMap.Data.IsConstantSet, - IsDefaultSet = parameterMap.Data.IsDefaultSet, - IsIndexSet = parameterMap.Data.IsIndexSet, - IsNameSet = parameterMap.Data.IsNameSet, - NameIndex = parameterMap.Data.NameIndex, - TypeConverter = parameterMap.Data.TypeConverter, - TypeConverterOptions = parameterMap.Data.TypeConverterOptions - }; - memberMapData.Names.AddRange(parameterMap.Data.Names); - - Expression typeConverterFieldExpression = Expression.Call(Expression.Constant(parameterMap.Data.TypeConverter), nameof(ITypeConverter.ConvertFromString), null, fieldExpression, Expression.Constant(reader), Expression.Constant(memberMapData)); - typeConverterFieldExpression = Expression.Convert(typeConverterFieldExpression, parameterMap.Data.Parameter.ParameterType); - - return typeConverterFieldExpression; + return Expression.Field(recordExpression, (FieldInfo)memberMap.Data.Member); + } } - /// - /// Creates a default expression if field expression is empty. - /// - /// The mapping for the member. - /// The field expression. - public virtual Expression CreateDefaultExpression(MemberMap memberMap, Expression fieldExpression) + // The member isn't on this level of the mapping. + // We need to search down through the reference maps. + foreach (var refMap in mapping.ReferenceMaps) { - var typeConverterExpression = CreateTypeConverterExpression(memberMap, fieldExpression); + var wrapped = refMap.Data.Member.GetMemberExpression(recordExpression); + var memberExpression = CreateGetMemberExpression(wrapped, refMap.Data.Mapping, memberMap); + if (memberExpression == null) + { + continue; + } - // Create default value expression. - Expression defaultValueExpression; - if (memberMap.Data.Member.MemberType() != typeof(string) && memberMap.Data.Default != null && memberMap.Data.Default.GetType() == typeof(string)) + if (refMap.Data.Member.MemberType().GetTypeInfo().IsValueType) { - // The default is a string but the member type is not. Use a converter. - defaultValueExpression = Expression.Call(Expression.Constant(memberMap.Data.TypeConverter), nameof(ITypeConverter.ConvertFromString), null, Expression.Constant(memberMap.Data.Default), Expression.Constant(reader), Expression.Constant(memberMap.Data)); + return memberExpression; + } + + var nullCheckExpression = Expression.Equal(wrapped, Expression.Constant(null)); + + var isValueType = memberMap.Data.Member!.MemberType().GetTypeInfo().IsValueType; + var isGenericType = isValueType && memberMap.Data.Member!.MemberType().GetTypeInfo().IsGenericType; + Type memberType; + if (isValueType && !isGenericType && !writer.Configuration.UseNewObjectForNullReferenceMembers) + { + memberType = typeof(Nullable<>).MakeGenericType(memberMap.Data.Member!.MemberType()); + memberExpression = Expression.Convert(memberExpression, memberType); } else { - // The member type and default type match. - defaultValueExpression = Expression.Constant(memberMap.Data.Default); + memberType = memberMap.Data.Member!.MemberType(); } - defaultValueExpression = Expression.Convert(defaultValueExpression, memberMap.Data.Member.MemberType()); + var defaultValueExpression = isValueType && !isGenericType + ? (Expression)Expression.New(memberType) + : Expression.Constant(null, memberType); + var conditionExpression = Expression.Condition(nullCheckExpression, defaultValueExpression, memberExpression); + return conditionExpression; + } - // If null, use string.Empty. - var coalesceExpression = Expression.Coalesce(fieldExpression, Expression.Constant(string.Empty)); + return null; + } - // Check if the field is an empty string. - var checkFieldEmptyExpression = Expression.Equal(Expression.Convert(coalesceExpression, typeof(string)), Expression.Constant(string.Empty, typeof(string))); + /// + /// Creates an instance of the given type using , then assigns + /// the given member assignments to that instance. + /// + /// The type of the record we're creating. + /// The member assignments that will be assigned to the created instance. + /// A representing the instance creation and assignments. + public virtual BlockExpression CreateInstanceAndAssignMembers(Type recordType, List assignments) + { + var expressions = new List(); + var createInstanceMethod = typeof(IObjectResolver).GetMethod(nameof(IObjectResolver.Resolve), new Type[] { typeof(Type), typeof(object[]) })!; + var instanceExpression = Expression.Convert(Expression.Call(Expression.Constant(ObjectResolver.Current), createInstanceMethod, Expression.Constant(recordType), Expression.Constant(new object[0])), recordType); + var variableExpression = Expression.Variable(instanceExpression.Type, "instance"); + expressions.Add(Expression.Assign(variableExpression, instanceExpression)); + expressions.AddRange(assignments.Select(b => Expression.Assign(Expression.MakeMemberAccess(variableExpression, b.Member), b.Expression))); + expressions.Add(variableExpression); + var variables = new ParameterExpression[] { variableExpression }; + var blockExpression = Expression.Block(variables, expressions); + + return blockExpression; + } - // Use a default value if the field is an empty string. - fieldExpression = Expression.Condition(checkFieldEmptyExpression, defaultValueExpression, typeConverterExpression); + /// + /// Creates an expression that converts the field expression using a type converter. + /// + /// The mapping for the member. + /// The field expression. + public virtual Expression CreateTypeConverterExpression(MemberMap memberMap, Expression fieldExpression) + { + if (reader == null) throw new InvalidOperationException("Reader is null"); - return fieldExpression; - } + memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = reader.Configuration.CultureInfo }, reader.Context.TypeConverterOptionsCache.GetOptions(memberMap.Data.Member!.MemberType()), memberMap.Data.TypeConverterOptions); - /// - /// Creates a default expression if field expression is empty. - /// - /// The mapping for the parameter. - /// The field expression. - public virtual Expression CreateDefaultExpression(ParameterMap parameterMap, Expression fieldExpression) + Expression typeConverterFieldExpression = Expression.Call(Expression.Constant(memberMap.Data.TypeConverter), nameof(ITypeConverter.ConvertFromString), null, fieldExpression, Expression.Constant(reader), Expression.Constant(memberMap.Data)); + typeConverterFieldExpression = Expression.Convert(typeConverterFieldExpression, memberMap.Data.Member!.MemberType()); + + return typeConverterFieldExpression; + } + + /// + /// Creates an expression that converts the field expression using a type converter. + /// + /// The mapping for the parameter. + /// The field expression. + public virtual Expression CreateTypeConverterExpression(ParameterMap parameterMap, Expression fieldExpression) + { + if (reader == null) throw new InvalidOperationException("Reader is null"); + + parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge + ( + new TypeConverterOptions { CultureInfo = reader.Configuration.CultureInfo }, + reader.Context.TypeConverterOptionsCache.GetOptions(parameterMap.Data.Parameter.ParameterType), + parameterMap.Data.TypeConverterOptions + ); + + var memberMapData = new MemberMapData(null) { - var typeConverterExpression = CreateTypeConverterExpression(parameterMap, fieldExpression); + Constant = parameterMap.Data.Constant, + Default = parameterMap.Data.Default, + Ignore = parameterMap.Data.Ignore, + Index = parameterMap.Data.Index, + IsConstantSet = parameterMap.Data.IsConstantSet, + IsDefaultSet = parameterMap.Data.IsDefaultSet, + IsIndexSet = parameterMap.Data.IsIndexSet, + IsNameSet = parameterMap.Data.IsNameSet, + NameIndex = parameterMap.Data.NameIndex, + TypeConverter = parameterMap.Data.TypeConverter, + TypeConverterOptions = parameterMap.Data.TypeConverterOptions + }; + memberMapData.Names.AddRange(parameterMap.Data.Names); + + Expression typeConverterFieldExpression = Expression.Call(Expression.Constant(parameterMap.Data.TypeConverter), nameof(ITypeConverter.ConvertFromString), null, fieldExpression, Expression.Constant(reader), Expression.Constant(memberMapData)); + typeConverterFieldExpression = Expression.Convert(typeConverterFieldExpression, parameterMap.Data.Parameter.ParameterType); + + return typeConverterFieldExpression; + } - // Create default value expression. - Expression defaultValueExpression; - if (parameterMap.Data.Parameter.ParameterType != typeof(string) && parameterMap.Data.Default != null && parameterMap.Data.Default.GetType() == typeof(string)) - { - // The default is a string but the member type is not. Use a converter. - //defaultValueExpression = Expression.Call(Expression.Constant(parameterMap.Data.TypeConverter), nameof(ITypeConverter.ConvertFromString), null, Expression.Constant(parameterMap.Data.Default), Expression.Constant(reader), Expression.Constant(memberMap.Data)); - defaultValueExpression = CreateTypeConverterExpression(parameterMap, Expression.Constant(parameterMap.Data.Default)); - } - else - { - // The member type and default type match. - defaultValueExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Default), parameterMap.Data.Parameter.ParameterType); - } + /// + /// Creates a default expression if field expression is empty. + /// + /// The mapping for the member. + /// The field expression. + public virtual Expression CreateDefaultExpression(MemberMap memberMap, Expression fieldExpression) + { + var typeConverterExpression = CreateTypeConverterExpression(memberMap, fieldExpression); + + // Create default value expression. + Expression defaultValueExpression; + if (memberMap.Data.Member!.MemberType() != typeof(string) && memberMap.Data.Default != null && memberMap.Data.Default.GetType() == typeof(string)) + { + // The default is a string but the member type is not. Use a converter. + defaultValueExpression = Expression.Call(Expression.Constant(memberMap.Data.TypeConverter), nameof(ITypeConverter.ConvertFromString), null, Expression.Constant(memberMap.Data.Default), Expression.Constant(reader), Expression.Constant(memberMap.Data)); + } + else + { + // The member type and default type match. + defaultValueExpression = Expression.Constant(memberMap.Data.Default); + } + + defaultValueExpression = Expression.Convert(defaultValueExpression, memberMap.Data.Member!.MemberType()); + + // If null, use string.Empty. + var coalesceExpression = Expression.Coalesce(fieldExpression, Expression.Constant(string.Empty)); - // If null, use string.Empty. - var coalesceExpression = Expression.Coalesce(fieldExpression, Expression.Constant(string.Empty)); + // Check if the field is an empty string. + var checkFieldEmptyExpression = Expression.Equal(Expression.Convert(coalesceExpression, typeof(string)), Expression.Constant(string.Empty, typeof(string))); - // Check if the field is an empty string. - var checkFieldEmptyExpression = Expression.Equal(Expression.Convert(coalesceExpression, typeof(string)), Expression.Constant(string.Empty, typeof(string))); + // Use a default value if the field is an empty string. + fieldExpression = Expression.Condition(checkFieldEmptyExpression, defaultValueExpression, typeConverterExpression); - // Use a default value if the field is an empty string. - fieldExpression = Expression.Condition(checkFieldEmptyExpression, defaultValueExpression, typeConverterExpression); + return fieldExpression; + } + + /// + /// Creates a default expression if field expression is empty. + /// + /// The mapping for the parameter. + /// The field expression. + public virtual Expression CreateDefaultExpression(ParameterMap parameterMap, Expression fieldExpression) + { + var typeConverterExpression = CreateTypeConverterExpression(parameterMap, fieldExpression); - return fieldExpression; + // Create default value expression. + Expression defaultValueExpression; + if (parameterMap.Data.Parameter.ParameterType != typeof(string) && parameterMap.Data.Default != null && parameterMap.Data.Default.GetType() == typeof(string)) + { + // The default is a string but the member type is not. Use a converter. + //defaultValueExpression = Expression.Call(Expression.Constant(parameterMap.Data.TypeConverter), nameof(ITypeConverter.ConvertFromString), null, Expression.Constant(parameterMap.Data.Default), Expression.Constant(reader), Expression.Constant(memberMap.Data)); + defaultValueExpression = CreateTypeConverterExpression(parameterMap, Expression.Constant(parameterMap.Data.Default)); } + else + { + // The member type and default type match. + defaultValueExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Default), parameterMap.Data.Parameter.ParameterType); + } + + // If null, use string.Empty. + var coalesceExpression = Expression.Coalesce(fieldExpression, Expression.Constant(string.Empty)); + + // Check if the field is an empty string. + var checkFieldEmptyExpression = Expression.Equal(Expression.Convert(coalesceExpression, typeof(string)), Expression.Constant(string.Empty, typeof(string))); + + // Use a default value if the field is an empty string. + fieldExpression = Expression.Condition(checkFieldEmptyExpression, defaultValueExpression, typeConverterExpression); + + return fieldExpression; } } diff --git a/src/CsvHelper/Expressions/ObjectRecordCreator.cs b/src/CsvHelper/Expressions/ObjectRecordCreator.cs index 041958182..10fd3e23a 100644 --- a/src/CsvHelper/Expressions/ObjectRecordCreator.cs +++ b/src/CsvHelper/Expressions/ObjectRecordCreator.cs @@ -2,65 +2,62 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; using System.Linq.Expressions; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Creates objects. +/// +public class ObjectRecordCreator : RecordCreator { /// - /// Creates objects. + /// Initializes a new instance using the given reader. /// - public class ObjectRecordCreator : RecordCreator - { - /// - /// Initializes a new instance using the given reader. - /// - /// - public ObjectRecordCreator(CsvReader reader) : base(reader) { } + /// + public ObjectRecordCreator(CsvReader reader) : base(reader) { } - /// - /// Creates a of type - /// that will create a record of the given type using the current - /// reader row. - /// - /// The record type. - protected override Delegate CreateCreateRecordDelegate(Type recordType) + /// + /// Creates a of type + /// that will create a record of the given type using the current + /// reader row. + /// + /// The record type. + protected override Delegate CreateCreateRecordDelegate(Type recordType) + { + if (Reader.Context.Maps[recordType] == null) { - if (Reader.Context.Maps[recordType] == null) - { - Reader.Context.Maps.Add(Reader.Context.AutoMap(recordType)); - } - - var map = Reader.Context.Maps[recordType]; + Reader.Context.Maps.Add(Reader.Context.AutoMap(recordType)); + } - Expression body; + var map = Reader.Context.Maps[recordType]!; // The map is added above. - if (map.ParameterMaps.Count > 0) - { - // This is a constructor parameter type. - var arguments = new List(); - ExpressionManager.CreateConstructorArgumentExpressionsForMapping(map, arguments); + Expression body; - var args = new GetConstructorArgs(map.ClassType); - body = Expression.New(Reader.Configuration.GetConstructor(args), arguments); - } - else - { - var assignments = new List(); - ExpressionManager.CreateMemberAssignmentsForMapping(map, assignments); + if (map.ParameterMaps.Count > 0) + { + // This is a constructor parameter type. + var arguments = new List(); + ExpressionManager.CreateConstructorArgumentExpressionsForMapping(map, arguments); - if (assignments.Count == 0) - { - throw new ReaderException(Reader.Context, $"No members are mapped for type '{recordType.FullName}'."); - } + var args = new GetConstructorArgs(map.ClassType); + body = Expression.New(Reader.Configuration.GetConstructor(args), arguments); + } + else + { + var assignments = new List(); + ExpressionManager.CreateMemberAssignmentsForMapping(map, assignments); - body = ExpressionManager.CreateInstanceAndAssignMembers(recordType, assignments); + if (assignments.Count == 0) + { + throw new ReaderException(Reader.Context, $"No members are mapped for type '{recordType.FullName}'."); } - var funcType = typeof(Func<>).MakeGenericType(recordType); - - return Expression.Lambda(funcType, body).Compile(); + body = ExpressionManager.CreateInstanceAndAssignMembers(recordType, assignments); } + + var funcType = typeof(Func<>).MakeGenericType(recordType); + + return Expression.Lambda(funcType, body).Compile(); } } diff --git a/src/CsvHelper/Expressions/ObjectRecordWriter.cs b/src/CsvHelper/Expressions/ObjectRecordWriter.cs index c044b4dc4..e00119024 100644 --- a/src/CsvHelper/Expressions/ObjectRecordWriter.cs +++ b/src/CsvHelper/Expressions/ObjectRecordWriter.cs @@ -4,121 +4,118 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; using CsvHelper.TypeConversion; -using System; -using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Writes objects. +/// +public class ObjectRecordWriter : RecordWriter { /// - /// Writes objects. + /// Initializes a new instance using the given writer. /// - public class ObjectRecordWriter : RecordWriter + /// The writer. + public ObjectRecordWriter(CsvWriter writer) : base(writer) { } + + /// + /// Creates a of type + /// that will write the given record using the current writer row. + /// + /// The record type. + /// The type for the record. + protected override Action CreateWriteDelegate(Type type) { - /// - /// Initializes a new instance using the given writer. - /// - /// The writer. - public ObjectRecordWriter(CsvWriter writer) : base(writer) { } - - /// - /// Creates a of type - /// that will write the given record using the current writer row. - /// - /// The record type. - /// The type for the record. - protected override Action CreateWriteDelegate(Type type) + if (Writer.Context.Maps[type] == null) { - if (Writer.Context.Maps[type] == null) - { - Writer.Context.Maps.Add(Writer.Context.AutoMap(type)); - } + Writer.Context.Maps.Add(Writer.Context.AutoMap(type)); + } + + var recordParameter = Expression.Parameter(typeof(T), "record"); + var recordParameterConverted = Expression.Convert(recordParameter, type); + + // Get a list of all the members so they will + // be sorted properly. + var members = new MemberMapCollection(); + members.AddMembers(Writer.Context.Maps[type]!); // The map is added above. - var recordParameter = Expression.Parameter(typeof(T), "record"); - var recordParameterConverted = Expression.Convert(recordParameter, type); + if (members.Count == 0) + { + throw new WriterException(Writer.Context, $"No properties are mapped for type '{type.FullName}'."); + } - // Get a list of all the members so they will - // be sorted properly. - var members = new MemberMapCollection(); - members.AddMembers(Writer.Context.Maps[type]); + var delegates = new List>(); - if (members.Count == 0) + foreach (var memberMap in members) + { + if (memberMap.Data.WritingConvertExpression != null) { - throw new WriterException(Writer.Context, $"No properties are mapped for type '{type.FullName}'."); + // The user is providing the expression to do the conversion. + Type convertGenericType = memberMap.Data.WritingConvertExpression.Type.GenericTypeArguments[0]; + + var constructor = typeof(ConvertToStringArgs<>).MakeGenericType(convertGenericType).GetConstructor(new Type[] { convertGenericType })!; + var args = Expression.New(constructor, recordParameterConverted); + Expression exp = Expression.Invoke(memberMap.Data.WritingConvertExpression, args); + exp = Expression.Call(Expression.Constant(Writer), nameof(Writer.WriteField), null, exp); + delegates.Add(Expression.Lambda>(exp, recordParameter).Compile()); + continue; } - var delegates = new List>(); + if (!Writer.CanWrite(memberMap)) + { + continue; + } - foreach (var memberMap in members) + Expression fieldExpression; + + if (memberMap.Data.IsConstantSet) { - if (memberMap.Data.WritingConvertExpression != null) + if (memberMap.Data.Constant == null) { - // The user is providing the expression to do the conversion. - Type convertGenericType = memberMap.Data.WritingConvertExpression.Type.GenericTypeArguments[0]; - - var constructor = typeof(ConvertToStringArgs<>).MakeGenericType(convertGenericType).GetConstructor(new Type[] { convertGenericType }); - var args = Expression.New(constructor, recordParameterConverted); - Expression exp = Expression.Invoke(memberMap.Data.WritingConvertExpression, args); - exp = Expression.Call(Expression.Constant(Writer), nameof(Writer.WriteField), null, exp); - delegates.Add(Expression.Lambda>(exp, recordParameter).Compile()); - continue; + fieldExpression = Expression.Constant(string.Empty); } - - if (!Writer.CanWrite(memberMap)) + else { - continue; + fieldExpression = Expression.Constant(memberMap.Data.Constant); + var typeConverterExpression = Expression.Constant(Writer.Context.TypeConverterCache.GetConverter(memberMap.Data.Constant.GetType())); + var method = typeof(ITypeConverter).GetMethod(nameof(ITypeConverter.ConvertToString))!; + fieldExpression = Expression.Convert(fieldExpression, typeof(object)); + fieldExpression = Expression.Call(typeConverterExpression, method, fieldExpression, Expression.Constant(Writer), Expression.Constant(memberMap.Data)); } - - Expression fieldExpression; - - if (memberMap.Data.IsConstantSet) + } + else + { + if (memberMap.Data.TypeConverter == null) { - if (memberMap.Data.Constant == null) - { - fieldExpression = Expression.Constant(string.Empty); - } - else - { - fieldExpression = Expression.Constant(memberMap.Data.Constant); - var typeConverterExpression = Expression.Constant(Writer.Context.TypeConverterCache.GetConverter(memberMap.Data.Constant.GetType())); - var method = typeof(ITypeConverter).GetMethod(nameof(ITypeConverter.ConvertToString)); - fieldExpression = Expression.Convert(fieldExpression, typeof(object)); - fieldExpression = Expression.Call(typeConverterExpression, method, fieldExpression, Expression.Constant(Writer), Expression.Constant(memberMap.Data)); - } + // Skip if the type isn't convertible. + continue; } - else - { - if (memberMap.Data.TypeConverter == null) - { - // Skip if the type isn't convertible. - continue; - } - fieldExpression = ExpressionManager.CreateGetMemberExpression(recordParameterConverted, Writer.Context.Maps[type], memberMap); + fieldExpression = ExpressionManager.CreateGetMemberExpression(recordParameterConverted, Writer.Context.Maps[type]!, memberMap)!; - var typeConverterExpression = Expression.Constant(memberMap.Data.TypeConverter); - memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = Writer.Configuration.CultureInfo }, Writer.Context.TypeConverterOptionsCache.GetOptions(memberMap.Data.Member.MemberType()), memberMap.Data.TypeConverterOptions); + var typeConverterExpression = Expression.Constant(memberMap.Data.TypeConverter); + memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = Writer.Configuration.CultureInfo }, Writer.Context.TypeConverterOptionsCache.GetOptions(memberMap.Data.Member!.MemberType()), memberMap.Data.TypeConverterOptions); - var method = typeof(ITypeConverter).GetMethod(nameof(ITypeConverter.ConvertToString)); - fieldExpression = Expression.Convert(fieldExpression, typeof(object)); - fieldExpression = Expression.Call(typeConverterExpression, method, fieldExpression, Expression.Constant(Writer), Expression.Constant(memberMap.Data)); + var method = typeof(ITypeConverter).GetMethod(nameof(ITypeConverter.ConvertToString))!; + fieldExpression = Expression.Convert(fieldExpression, typeof(object)); + fieldExpression = Expression.Call(typeConverterExpression, method, fieldExpression, Expression.Constant(Writer), Expression.Constant(memberMap.Data)); - if (type.GetTypeInfo().IsClass) - { - var areEqualExpression = Expression.Equal(recordParameterConverted, Expression.Constant(null)); - fieldExpression = Expression.Condition(areEqualExpression, Expression.Constant(string.Empty), fieldExpression); - } + if (type.GetTypeInfo().IsClass) + { + var areEqualExpression = Expression.Equal(recordParameterConverted, Expression.Constant(null)); + fieldExpression = Expression.Condition(areEqualExpression, Expression.Constant(string.Empty), fieldExpression); } - - var writeFieldMethodCall = Expression.Call(Expression.Constant(Writer), nameof(Writer.WriteConvertedField), null, fieldExpression, Expression.Constant(memberMap.Data.Type)); - - delegates.Add(Expression.Lambda>(writeFieldMethodCall, recordParameter).Compile()); } - var action = CombineDelegates(delegates) ?? new Action((T parameter) => { }); + var writeFieldMethodCall = Expression.Call(Expression.Constant(Writer), nameof(Writer.WriteConvertedField), null, fieldExpression, Expression.Constant(memberMap.Data.Type)); - return action; + delegates.Add(Expression.Lambda>(writeFieldMethodCall, recordParameter).Compile()); } + + var action = CombineDelegates(delegates) ?? new Action((T parameter) => { }); + + return action; } } diff --git a/src/CsvHelper/Expressions/PrimitiveRecordCreator.cs b/src/CsvHelper/Expressions/PrimitiveRecordCreator.cs index e0bf6ab3c..d44536e32 100644 --- a/src/CsvHelper/Expressions/PrimitiveRecordCreator.cs +++ b/src/CsvHelper/Expressions/PrimitiveRecordCreator.cs @@ -4,46 +4,44 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; using CsvHelper.TypeConversion; -using System; using System.Linq.Expressions; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Creates primitive records. +/// +public class PrimitiveRecordCreator : RecordCreator { /// - /// Creates primitive records. + /// Initializes a new instance using the given reader. /// - public class PrimitiveRecordCreator : RecordCreator + /// The reader. + public PrimitiveRecordCreator(CsvReader reader) : base(reader) { } + + /// + /// Creates a of type + /// that will create a record of the given type using the current + /// reader row. + /// + /// The record type. + protected override Delegate CreateCreateRecordDelegate(Type recordType) { - /// - /// Initializes a new instance using the given reader. - /// - /// The reader. - public PrimitiveRecordCreator(CsvReader reader) : base(reader) { } + var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) })!.GetGetMethod()!; + Expression fieldExpression = Expression.Call(Expression.Constant(Reader), method, Expression.Constant(0, typeof(int))); - /// - /// Creates a of type - /// that will create a record of the given type using the current - /// reader row. - /// - /// The record type. - protected override Delegate CreateCreateRecordDelegate(Type recordType) + var memberMapData = new MemberMapData(null) { - var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) }).GetGetMethod(); - Expression fieldExpression = Expression.Call(Expression.Constant(Reader), method, Expression.Constant(0, typeof(int))); - - var memberMapData = new MemberMapData(null) - { - Index = 0, - TypeConverter = Reader.Context.TypeConverterCache.GetConverter(recordType) - }; - memberMapData.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = Reader.Configuration.CultureInfo }, Reader.Context.TypeConverterOptionsCache.GetOptions(recordType)); + Index = 0, + TypeConverter = Reader.Context.TypeConverterCache.GetConverter(recordType) + }; + memberMapData.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = Reader.Configuration.CultureInfo }, Reader.Context.TypeConverterOptionsCache.GetOptions(recordType)); - fieldExpression = Expression.Call(Expression.Constant(memberMapData.TypeConverter), "ConvertFromString", null, fieldExpression, Expression.Constant(Reader), Expression.Constant(memberMapData)); - fieldExpression = Expression.Convert(fieldExpression, recordType); + fieldExpression = Expression.Call(Expression.Constant(memberMapData.TypeConverter), "ConvertFromString", null, fieldExpression, Expression.Constant(Reader), Expression.Constant(memberMapData)); + fieldExpression = Expression.Convert(fieldExpression, recordType); - var funcType = typeof(Func<>).MakeGenericType(recordType); + var funcType = typeof(Func<>).MakeGenericType(recordType); - return Expression.Lambda(funcType, fieldExpression).Compile(); - } + return Expression.Lambda(funcType, fieldExpression).Compile(); } } diff --git a/src/CsvHelper/Expressions/PrimitiveRecordWriter.cs b/src/CsvHelper/Expressions/PrimitiveRecordWriter.cs index 5a45842e2..07f879f8c 100644 --- a/src/CsvHelper/Expressions/PrimitiveRecordWriter.cs +++ b/src/CsvHelper/Expressions/PrimitiveRecordWriter.cs @@ -4,52 +4,50 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; using CsvHelper.TypeConversion; -using System; using System.Linq.Expressions; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Writes primitives. +/// +public class PrimitiveRecordWriter : RecordWriter { /// - /// Writes primitives. + /// Initializes a new instance using the given writer. + /// + /// The writer. + public PrimitiveRecordWriter(CsvWriter writer) : base(writer) { } + + /// + /// Creates a of type + /// that will write the given record using the current writer row. /// - public class PrimitiveRecordWriter : RecordWriter + /// The record type. + /// The type for the record. + protected override Action CreateWriteDelegate(Type type) { - /// - /// Initializes a new instance using the given writer. - /// - /// The writer. - public PrimitiveRecordWriter(CsvWriter writer) : base(writer) { } - - /// - /// Creates a of type - /// that will write the given record using the current writer row. - /// - /// The record type. - /// The type for the record. - protected override Action CreateWriteDelegate(Type type) - { - var recordParameter = Expression.Parameter(typeof(T), "record"); + var recordParameter = Expression.Parameter(typeof(T), "record"); - Expression fieldExpression = Expression.Convert(recordParameter, typeof(object)); + Expression fieldExpression = Expression.Convert(recordParameter, typeof(object)); - var typeConverter = Writer.Context.TypeConverterCache.GetConverter(type); - var typeConverterExpression = Expression.Constant(typeConverter); - var method = typeof(ITypeConverter).GetMethod(nameof(ITypeConverter.ConvertToString)); + var typeConverter = Writer.Context.TypeConverterCache.GetConverter(type); + var typeConverterExpression = Expression.Constant(typeConverter); + var method = typeof(ITypeConverter).GetMethod(nameof(ITypeConverter.ConvertToString))!; - var memberMapData = new MemberMapData(null) - { - Index = 0, - TypeConverter = typeConverter, - TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), Writer.Context.TypeConverterOptionsCache.GetOptions(type)) - }; - memberMapData.TypeConverterOptions.CultureInfo = Writer.Configuration.CultureInfo; + var memberMapData = new MemberMapData(null) + { + Index = 0, + TypeConverter = typeConverter, + TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), Writer.Context.TypeConverterOptionsCache.GetOptions(type)) + }; + memberMapData.TypeConverterOptions.CultureInfo = Writer.Configuration.CultureInfo; - fieldExpression = Expression.Call(typeConverterExpression, method, fieldExpression, Expression.Constant(Writer), Expression.Constant(memberMapData)); - fieldExpression = Expression.Call(Expression.Constant(Writer), nameof(Writer.WriteConvertedField), null, fieldExpression, Expression.Constant(type)); + fieldExpression = Expression.Call(typeConverterExpression, method, fieldExpression, Expression.Constant(Writer), Expression.Constant(memberMapData)); + fieldExpression = Expression.Call(Expression.Constant(Writer), nameof(Writer.WriteConvertedField), null, fieldExpression, Expression.Constant(type)); - var action = Expression.Lambda>(fieldExpression, recordParameter).Compile(); + var action = Expression.Lambda>(fieldExpression, recordParameter).Compile(); - return action; - } + return action; } } diff --git a/src/CsvHelper/Expressions/RecordCreator.cs b/src/CsvHelper/Expressions/RecordCreator.cs index da0fcc081..d03cdb77c 100644 --- a/src/CsvHelper/Expressions/RecordCreator.cs +++ b/src/CsvHelper/Expressions/RecordCreator.cs @@ -2,60 +2,55 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Reflection; +namespace CsvHelper.Expressions; -namespace CsvHelper.Expressions +/// +/// Base implementation for classes that create records. +/// +public abstract class RecordCreator { + private readonly Dictionary createRecordFuncs = new Dictionary(); + /// - /// Base implementation for classes that create records. + /// The reader. /// - public abstract class RecordCreator - { - private readonly Dictionary createRecordFuncs = new Dictionary(); - - /// - /// The reader. - /// - protected CsvReader Reader { get; private set; } + protected CsvReader Reader { get; private set; } - /// - /// The expression manager. - /// - protected ExpressionManager ExpressionManager { get; private set; } + /// + /// The expression manager. + /// + protected ExpressionManager ExpressionManager { get; private set; } - /// - /// Initializes a new instance using the given reader. - /// - /// The reader. - public RecordCreator(CsvReader reader) - { - Reader = reader; - ExpressionManager = new ExpressionManager(reader); - } + /// + /// Initializes a new instance using the given reader. + /// + /// The reader. + public RecordCreator(CsvReader reader) + { + Reader = reader; + ExpressionManager = new ExpressionManager(reader); + } - /// - /// Gets the delegate to create a record for the given record type. - /// If the delegate doesn't exist, one will be created and cached. - /// - /// The record type. - public virtual Func GetCreateRecordDelegate(Type recordType) + /// + /// Gets the delegate to create a record for the given record type. + /// If the delegate doesn't exist, one will be created and cached. + /// + /// The record type. + public virtual Func GetCreateRecordDelegate(Type recordType) + { + if (!createRecordFuncs.TryGetValue(recordType, out Delegate? func)) { - if (!createRecordFuncs.TryGetValue(recordType, out Delegate func)) - { - createRecordFuncs[recordType] = func = CreateCreateRecordDelegate(recordType); - } - - return (Func)func; + createRecordFuncs[recordType] = func = CreateCreateRecordDelegate(recordType); } - /// - /// Creates a of type - /// that will create a record of the given type using the current - /// reader row. - /// - /// The record type. - protected abstract Delegate CreateCreateRecordDelegate(Type recordType); + return (Func)func; } + + /// + /// Creates a of type + /// that will create a record of the given type using the current + /// reader row. + /// + /// The record type. + protected abstract Delegate CreateCreateRecordDelegate(Type recordType); } diff --git a/src/CsvHelper/Expressions/RecordCreatorFactory.cs b/src/CsvHelper/Expressions/RecordCreatorFactory.cs index 96e17d833..ba53ddda8 100644 --- a/src/CsvHelper/Expressions/RecordCreatorFactory.cs +++ b/src/CsvHelper/Expressions/RecordCreatorFactory.cs @@ -2,48 +2,46 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Reflection; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Factory to create record creators. +/// +public class RecordCreatorFactory { + private readonly DynamicRecordCreator dynamicRecordCreator; + private readonly PrimitiveRecordCreator primitiveRecordCreator; + private readonly ObjectRecordCreator objectRecordCreator; + /// - /// Factory to create record creators. + /// Initializes a new instance using the given reader. /// - public class RecordCreatorFactory + /// The reader. + public RecordCreatorFactory(CsvReader reader) { - private readonly DynamicRecordCreator dynamicRecordCreator; - private readonly PrimitiveRecordCreator primitiveRecordCreator; - private readonly ObjectRecordCreator objectRecordCreator; + dynamicRecordCreator = new DynamicRecordCreator(reader); + primitiveRecordCreator = new PrimitiveRecordCreator(reader); + objectRecordCreator = new ObjectRecordCreator(reader); + } - /// - /// Initializes a new instance using the given reader. - /// - /// The reader. - public RecordCreatorFactory(CsvReader reader) + /// + /// Creates a record creator for the given record type. + /// + /// The record type. + public virtual RecordCreator MakeRecordCreator(Type recordType) + { + if (recordType.GetTypeInfo().IsPrimitive) { - dynamicRecordCreator = new DynamicRecordCreator(reader); - primitiveRecordCreator = new PrimitiveRecordCreator(reader); - objectRecordCreator = new ObjectRecordCreator(reader); + return primitiveRecordCreator; } - /// - /// Creates a record creator for the given record type. - /// - /// The record type. - public virtual RecordCreator MakeRecordCreator(Type recordType) + if (recordType == typeof(object)) { - if (recordType.GetTypeInfo().IsPrimitive) - { - return primitiveRecordCreator; - } - - if (recordType == typeof(object)) - { - return dynamicRecordCreator; - } - - return objectRecordCreator; + return dynamicRecordCreator; } + + return objectRecordCreator; } } diff --git a/src/CsvHelper/Expressions/RecordHydrator.cs b/src/CsvHelper/Expressions/RecordHydrator.cs index cee3d5608..473e5c438 100644 --- a/src/CsvHelper/Expressions/RecordHydrator.cs +++ b/src/CsvHelper/Expressions/RecordHydrator.cs @@ -2,123 +2,120 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Hydrates members of an existing record. +/// +public class RecordHydrator { + private readonly CsvReader reader; + private readonly ExpressionManager expressionManager; + private readonly Dictionary hydrateRecordActions = new Dictionary(); + /// - /// Hydrates members of an existing record. + /// Creates a new instance using the given reader. /// - public class RecordHydrator + /// The reader. + public RecordHydrator(CsvReader reader) { - private readonly CsvReader reader; - private readonly ExpressionManager expressionManager; - private readonly Dictionary hydrateRecordActions = new Dictionary(); - - /// - /// Creates a new instance using the given reader. - /// - /// The reader. - public RecordHydrator(CsvReader reader) + this.reader = reader; + expressionManager = ObjectResolver.Current.Resolve(reader); + } + + /// + /// Hydrates members of the given record using the current reader row. + /// + /// The record type. + /// The record. + public void Hydrate(T record) + { + try { - this.reader = reader; - expressionManager = ObjectResolver.Current.Resolve(reader); + GetHydrateRecordAction()(record); } - - /// - /// Hydrates members of the given record using the current reader row. - /// - /// The record type. - /// The record. - public void Hydrate(T record) + catch (TargetInvocationException ex) { - try + if (ex.InnerException != null) { - GetHydrateRecordAction()(record); + throw ex.InnerException; } - catch (TargetInvocationException ex) + else { - if (ex.InnerException != null) - { - throw ex.InnerException; - } - else - { - throw; - } + throw; } } + } - /// - /// Gets the action delegate used to hydrate a custom class object's members with data from the reader. - /// - /// The record type. - protected virtual Action GetHydrateRecordAction() + /// + /// Gets the action delegate used to hydrate a custom class object's members with data from the reader. + /// + /// The record type. + protected virtual Action GetHydrateRecordAction() + { + var recordType = typeof(T); + + if (!hydrateRecordActions.TryGetValue(recordType, out Delegate? action)) { - var recordType = typeof(T); + hydrateRecordActions[recordType] = action = CreateHydrateRecordAction(); + } - if (!hydrateRecordActions.TryGetValue(recordType, out Delegate action)) - { - hydrateRecordActions[recordType] = action = CreateHydrateRecordAction(); - } + return (Action)action; + } - return (Action)action; - } + /// + /// Creates the action delegate used to hydrate a record's members with data from the reader. + /// + /// The record type. + protected virtual Action CreateHydrateRecordAction() + { + var recordType = typeof(T); - /// - /// Creates the action delegate used to hydrate a record's members with data from the reader. - /// - /// The record type. - protected virtual Action CreateHydrateRecordAction() + if (reader.Context.Maps[recordType] == null) { - var recordType = typeof(T); - - if (reader.Context.Maps[recordType] == null) - { - reader.Context.Maps.Add(reader.Context.AutoMap(recordType)); - } + reader.Context.Maps.Add(reader.Context.AutoMap(recordType)); + } - var mapping = reader.Context.Maps[recordType]; + var mapping = reader.Context.Maps[recordType]!; // Map is added above. - var recordTypeParameter = Expression.Parameter(recordType, "record"); - var memberAssignments = new List(); + var recordTypeParameter = Expression.Parameter(recordType, "record"); + var memberAssignments = new List(); - foreach (var memberMap in mapping.MemberMaps) + foreach (var memberMap in mapping.MemberMaps) + { + var fieldExpression = expressionManager.CreateGetFieldExpression(memberMap); + if (fieldExpression == null) { - var fieldExpression = expressionManager.CreateGetFieldExpression(memberMap); - if (fieldExpression == null) - { - continue; - } - - var memberAccess = Expression.MakeMemberAccess(recordTypeParameter, memberMap.Data.Member); - var memberAssignment = Expression.Assign(memberAccess, fieldExpression); - memberAssignments.Add(memberAssignment); + continue; } - foreach (var referenceMap in mapping.ReferenceMaps) + var memberAccess = Expression.MakeMemberAccess(recordTypeParameter, memberMap.Data.Member!); + var memberAssignment = Expression.Assign(memberAccess, fieldExpression); + memberAssignments.Add(memberAssignment); + } + + foreach (var referenceMap in mapping.ReferenceMaps) + { + if (!reader.CanRead(referenceMap)) { - if (!reader.CanRead(referenceMap)) - { - continue; - } + continue; + } - var referenceAssignments = new List(); - expressionManager.CreateMemberAssignmentsForMapping(referenceMap.Data.Mapping, referenceAssignments); + var referenceAssignments = new List(); + expressionManager.CreateMemberAssignmentsForMapping(referenceMap.Data.Mapping, referenceAssignments); - var referenceBody = expressionManager.CreateInstanceAndAssignMembers(referenceMap.Data.Member.MemberType(), referenceAssignments); + var referenceBody = expressionManager.CreateInstanceAndAssignMembers(referenceMap.Data.Member.MemberType(), referenceAssignments); - var memberAccess = Expression.MakeMemberAccess(recordTypeParameter, referenceMap.Data.Member); - var memberAssignment = Expression.Assign(memberAccess, referenceBody); - memberAssignments.Add(memberAssignment); - } + var memberAccess = Expression.MakeMemberAccess(recordTypeParameter, referenceMap.Data.Member); + var memberAssignment = Expression.Assign(memberAccess, referenceBody); + memberAssignments.Add(memberAssignment); + } - var body = Expression.Block(memberAssignments); + var body = Expression.Block(memberAssignments); - return Expression.Lambda>(body, recordTypeParameter).Compile(); - } + return Expression.Lambda>(body, recordTypeParameter).Compile(); } } diff --git a/src/CsvHelper/Expressions/RecordManager.cs b/src/CsvHelper/Expressions/RecordManager.cs index b501e2e9b..f5acb5320 100644 --- a/src/CsvHelper/Expressions/RecordManager.cs +++ b/src/CsvHelper/Expressions/RecordManager.cs @@ -2,68 +2,71 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.Expressions; -namespace CsvHelper.Expressions +/// +/// Manages record manipulation. +/// +public class RecordManager { + private readonly RecordCreatorFactory? recordCreatorFactory; + private readonly RecordHydrator? recordHydrator; + private readonly RecordWriterFactory? recordWriterFactory; + /// - /// Manages record manipulation. + /// Initializes a new instance using the given reader. /// - public class RecordManager + /// + public RecordManager(CsvReader reader) { - private readonly RecordCreatorFactory recordCreatorFactory; - private readonly RecordHydrator recordHydrator; - private readonly RecordWriterFactory recordWriterFactory; + recordCreatorFactory = ObjectResolver.Current.Resolve(reader); + recordHydrator = ObjectResolver.Current.Resolve(reader); + } - /// - /// Initializes a new instance using the given reader. - /// - /// - public RecordManager(CsvReader reader) - { - recordCreatorFactory = ObjectResolver.Current.Resolve(reader); - recordHydrator = ObjectResolver.Current.Resolve(reader); - } + /// + /// Initializes a new instance using the given writer. + /// + /// The writer. + public RecordManager(CsvWriter writer) + { + recordWriterFactory = ObjectResolver.Current.Resolve(writer); + } - /// - /// Initializes a new instance using the given writer. - /// - /// The writer. - public RecordManager(CsvWriter writer) - { - recordWriterFactory = ObjectResolver.Current.Resolve(writer); - } + /// + /// Gets a cached reader delegate for the given type. + /// + /// The type of the record. + /// The type of the record. + public Func GetReadDelegate(Type recordType) + { + if (recordCreatorFactory is null) throw new InvalidOperationException("The record creator factory is null."); - /// - /// Gets a cached reader delegate for the given type. - /// - /// The type of the record. - /// The type of the record. - public Func GetReadDelegate(Type recordType) - { - var recordCreator = recordCreatorFactory.MakeRecordCreator(recordType); - return recordCreator.GetCreateRecordDelegate(recordType); - } + var recordCreator = recordCreatorFactory.MakeRecordCreator(recordType); + return recordCreator.GetCreateRecordDelegate(recordType); + } - /// - /// Hydrates the given record using the current reader row. - /// - /// The type of the record. - /// The record to hydrate. - public void Hydrate(T record) - { - recordHydrator.Hydrate(record); - } + /// + /// Hydrates the given record using the current reader row. + /// + /// The type of the record. + /// The record to hydrate. + public void Hydrate(T record) + { + if (recordHydrator is null) throw new InvalidOperationException("The record hydrator is null."); + + recordHydrator.Hydrate(record); + } + + /// + /// Gets a cached writer delegate for the given type. + /// + /// The record type information. + /// The type of record being written. + public Action GetWriteDelegate(RecordTypeInfo typeInfo) + { + if (recordWriterFactory is null) throw new InvalidOperationException("The record creator factory is null."); - /// - /// Gets a cached writer delegate for the given type. - /// - /// The record type information. - /// The type of record being written. - public Action GetWriteDelegate(RecordTypeInfo typeInfo) - { - var recordWriter = recordWriterFactory.MakeRecordWriter(typeInfo.RecordType); - return recordWriter.GetWriteDelegate(typeInfo); - } + var recordWriter = recordWriterFactory.MakeRecordWriter(typeInfo.RecordType); + return recordWriter.GetWriteDelegate(typeInfo); } } diff --git a/src/CsvHelper/Expressions/RecordWriter.cs b/src/CsvHelper/Expressions/RecordWriter.cs index 18a0a3c40..53dba1c4e 100644 --- a/src/CsvHelper/Expressions/RecordWriter.cs +++ b/src/CsvHelper/Expressions/RecordWriter.cs @@ -2,92 +2,86 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +namespace CsvHelper.Expressions; -namespace CsvHelper.Expressions +/// +/// Base implementation for classes that write records. +/// +public abstract class RecordWriter { + private readonly Dictionary typeActions = new Dictionary(); + private readonly int objectHashCode = typeof(object).GetHashCode(); + /// - /// Base implementation for classes that write records. + /// Gets the writer. /// - public abstract class RecordWriter - { - private readonly Dictionary typeActions = new Dictionary(); - private readonly int objectHashCode = typeof(object).GetHashCode(); + protected CsvWriter Writer { get; private set; } - /// - /// Gets the writer. - /// - protected CsvWriter Writer { get; private set; } + /// + /// The expression manager. + /// + protected ExpressionManager ExpressionManager { get; private set; } - /// - /// The expression manager. - /// - protected ExpressionManager ExpressionManager { get; private set; } + /// + /// Initializes a new instance using the given writer. + /// + /// The writer. + public RecordWriter(CsvWriter writer) + { + Writer = writer; + ExpressionManager = ObjectResolver.Current.Resolve(writer); + } - /// - /// Initializes a new instance using the given writer. - /// - /// The writer. - public RecordWriter(CsvWriter writer) - { - Writer = writer; - ExpressionManager = ObjectResolver.Current.Resolve(writer); - } + /// + /// Gets the delegate to write the given record. + /// If the delegate doesn't exist, one will be created and cached. + /// + /// The record type. + /// The type for the record. + public virtual Action GetWriteDelegate(RecordTypeInfo typeInfo) + { + var typeKey = typeInfo.HashCode; - /// - /// Gets the delegate to write the given record. - /// If the delegate doesn't exist, one will be created and cached. - /// - /// The record type. - /// The type for the record. - public virtual Action GetWriteDelegate(RecordTypeInfo typeInfo) + if (typeInfo.IsObject) { - var typeKey = typeInfo.HashCode; - - if (typeInfo.IsObject) - { - typeKey = HashCode.Combine(objectHashCode, typeKey); - } - - if (!typeActions.TryGetValue(typeKey, out Delegate action)) - { - typeActions[typeKey] = action = CreateWriteDelegate(typeInfo.RecordType); - } - - return (Action)action; + typeKey = HashCode.Combine(objectHashCode, typeKey); } - /// - /// Creates a of type - /// that will write the given record using the current writer row. - /// - /// The record type. - /// The record. - protected virtual Action CreateWriteDelegate(T record) + if (!typeActions.TryGetValue(typeKey, out Delegate? action)) { - return CreateWriteDelegate(Writer.GetTypeInfoForRecord(record).RecordType); + typeActions[typeKey] = action = CreateWriteDelegate(typeInfo.RecordType); } - /// - /// Creates a of type - /// that will write the given record using the current writer row. - /// - /// The type of the record. - protected abstract Action CreateWriteDelegate(Type recordType); + return (Action)action; + } - /// - /// Combines the delegates into a single multicast delegate. - /// This is needed because Silverlight doesn't have the - /// Delegate.Combine( params Delegate[] ) overload. - /// - /// The delegates to combine. - /// A multicast delegate combined from the given delegates. - protected virtual Action CombineDelegates(IEnumerable> delegates) - { - return (Action)delegates.Aggregate(null, Delegate.Combine); - } + /// + /// Creates a of type + /// that will write the given record using the current writer row. + /// + /// The record type. + /// The record. + protected virtual Action CreateWriteDelegate(T record) + { + return CreateWriteDelegate(Writer.GetTypeInfoForRecord(record).RecordType); + } + + /// + /// Creates a of type + /// that will write the given record using the current writer row. + /// + /// The type of the record. + protected abstract Action CreateWriteDelegate(Type recordType); + + /// + /// Combines the delegates into a single multicast delegate. + /// This is needed because Silverlight doesn't have the + /// Delegate.Combine( params Delegate[] ) overload. + /// + /// The delegates to combine. + /// A multicast delegate combined from the given delegates. + protected virtual Action? CombineDelegates(IEnumerable> delegates) + { + return (Action?)delegates.Aggregate(null, Delegate.Combine);; } } diff --git a/src/CsvHelper/Expressions/RecordWriterFactory.cs b/src/CsvHelper/Expressions/RecordWriterFactory.cs index 5ef8fe433..e9148dff3 100644 --- a/src/CsvHelper/Expressions/RecordWriterFactory.cs +++ b/src/CsvHelper/Expressions/RecordWriterFactory.cs @@ -2,56 +2,53 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; using System.Dynamic; -namespace CsvHelper.Expressions +namespace CsvHelper.Expressions; + +/// +/// Factory to create record writers. +/// +public class RecordWriterFactory { + private readonly ExpandoObjectRecordWriter expandoObjectRecordWriter; + private readonly DynamicRecordWriter dynamicRecordWriter; + private readonly PrimitiveRecordWriter primitiveRecordWriter; + private readonly ObjectRecordWriter objectRecordWriter; + /// - /// Factory to create record writers. + /// Initializes a new instance using the given writer. /// - public class RecordWriterFactory + /// The writer. + public RecordWriterFactory(CsvWriter writer) { - private readonly ExpandoObjectRecordWriter expandoObjectRecordWriter; - private readonly DynamicRecordWriter dynamicRecordWriter; - private readonly PrimitiveRecordWriter primitiveRecordWriter; - private readonly ObjectRecordWriter objectRecordWriter; + expandoObjectRecordWriter = new ExpandoObjectRecordWriter(writer); + dynamicRecordWriter = new DynamicRecordWriter(writer); + primitiveRecordWriter = new PrimitiveRecordWriter(writer); + objectRecordWriter = new ObjectRecordWriter(writer); + } - /// - /// Initializes a new instance using the given writer. - /// - /// The writer. - public RecordWriterFactory(CsvWriter writer) + /// + /// Creates a new record writer for the given record. + /// + /// The type of the record. + public virtual RecordWriter MakeRecordWriter(Type recordType) + { + if (recordType.IsPrimitive) { - expandoObjectRecordWriter = new ExpandoObjectRecordWriter(writer); - dynamicRecordWriter = new DynamicRecordWriter(writer); - primitiveRecordWriter = new PrimitiveRecordWriter(writer); - objectRecordWriter = new ObjectRecordWriter(writer); + return primitiveRecordWriter; } - /// - /// Creates a new record writer for the given record. - /// - /// The type of the record. - public virtual RecordWriter MakeRecordWriter(Type recordType) + if (typeof(IDictionary).IsAssignableFrom(recordType)) { - if (recordType.IsPrimitive) - { - return primitiveRecordWriter; - } - - if (typeof(IDictionary).IsAssignableFrom(recordType)) - { - return expandoObjectRecordWriter; - } - - if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(recordType)) - { - return dynamicRecordWriter; - } + return expandoObjectRecordWriter; + } - return objectRecordWriter; + if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(recordType)) + { + return dynamicRecordWriter; } + + return objectRecordWriter; } } diff --git a/src/CsvHelper/Factory.cs b/src/CsvHelper/Factory.cs index 62426f354..8c363b5c2 100644 --- a/src/CsvHelper/Factory.cs +++ b/src/CsvHelper/Factory.cs @@ -2,107 +2,105 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; -using System.IO; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Creates CsvHelper classes. +/// +public class Factory : IFactory { /// - /// Creates CsvHelper classes. + /// Creates an . /// - public class Factory : IFactory + /// The text reader to use for the csv parser. + /// The configuration to use for the csv parser. + /// The created parser. + public virtual IParser CreateParser(TextReader reader, Configuration.CsvConfiguration configuration) { - /// - /// Creates an . - /// - /// The text reader to use for the csv parser. - /// The configuration to use for the csv parser. - /// The created parser. - public virtual IParser CreateParser(TextReader reader, Configuration.CsvConfiguration configuration) - { - return new CsvParser(reader, configuration); - } + return new CsvParser(reader, configuration); + } - /// - /// Creates an . - /// - /// The text reader to use for the csv parser. - /// The culture information. - /// - /// The created parser. - /// - public virtual IParser CreateParser(TextReader reader, CultureInfo cultureInfo) - { - return new CsvParser(reader, cultureInfo); - } + /// + /// Creates an . + /// + /// The text reader to use for the csv parser. + /// The culture information. + /// + /// The created parser. + /// + public virtual IParser CreateParser(TextReader reader, CultureInfo cultureInfo) + { + return new CsvParser(reader, cultureInfo); + } - /// - /// Creates an . - /// - /// The text reader to use for the csv reader. - /// The configuration to use for the reader. - /// The created reader. - public virtual IReader CreateReader(TextReader reader, Configuration.CsvConfiguration configuration) - { - return new CsvReader(reader, configuration); - } + /// + /// Creates an . + /// + /// The text reader to use for the csv reader. + /// The configuration to use for the reader. + /// The created reader. + public virtual IReader CreateReader(TextReader reader, Configuration.CsvConfiguration configuration) + { + return new CsvReader(reader, configuration); + } - /// - /// Creates an . - /// - /// The text reader to use for the csv reader. - /// The culture information. - /// - /// The created reader. - /// - public virtual IReader CreateReader(TextReader reader, CultureInfo cultureInfo) - { - return new CsvReader(reader, cultureInfo); - } + /// + /// Creates an . + /// + /// The text reader to use for the csv reader. + /// The culture information. + /// + /// The created reader. + /// + public virtual IReader CreateReader(TextReader reader, CultureInfo cultureInfo) + { + return new CsvReader(reader, cultureInfo); + } - /// - /// Creates an . - /// - /// The parser used to create the reader. - /// The created reader. - public virtual IReader CreateReader(IParser parser) - { - return new CsvReader(parser); - } + /// + /// Creates an . + /// + /// The parser used to create the reader. + /// The created reader. + public virtual IReader CreateReader(IParser parser) + { + return new CsvReader(parser); + } - /// - /// Creates an . - /// - /// The text writer to use for the csv writer. - /// The configuration to use for the writer. - /// The created writer. - public virtual IWriter CreateWriter(TextWriter writer, Configuration.CsvConfiguration configuration) - { - return new CsvWriter(writer, configuration); - } + /// + /// Creates an . + /// + /// The text writer to use for the csv writer. + /// The configuration to use for the writer. + /// The created writer. + public virtual IWriter CreateWriter(TextWriter writer, Configuration.CsvConfiguration configuration) + { + return new CsvWriter(writer, configuration); + } - /// - /// Creates an . - /// - /// The text writer to use for the csv writer. - /// The culture information. - /// - /// The created writer. - /// - public virtual IWriter CreateWriter(TextWriter writer, CultureInfo cultureInfo) - { - return new CsvWriter(writer, cultureInfo); - } + /// + /// Creates an . + /// + /// The text writer to use for the csv writer. + /// The culture information. + /// + /// The created writer. + /// + public virtual IWriter CreateWriter(TextWriter writer, CultureInfo cultureInfo) + { + return new CsvWriter(writer, cultureInfo); + } - /// - /// Access point for fluent interface to dynamically build a - /// - /// Type you will be making a class map for - /// Options to further configure the - public IHasMap CreateClassMapBuilder() - { - return new ClassMapBuilder(); - } + /// + /// Access point for fluent interface to dynamically build a + /// + /// Type you will be making a class map for + /// Options to further configure the + public IHasMap CreateClassMapBuilder() + { + return new ClassMapBuilder(); } } diff --git a/src/CsvHelper/FastDynamicObject.cs b/src/CsvHelper/FastDynamicObject.cs index 72bfbca5d..33088840e 100644 --- a/src/CsvHelper/FastDynamicObject.cs +++ b/src/CsvHelper/FastDynamicObject.cs @@ -1,10 +1,10 @@ -using System; +// Copyright 2009-2024 Josh Close +// This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. +// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. +// https://github.com/JoshClose/CsvHelper using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -124,7 +124,7 @@ bool ICollection>.Remove(KeyValuePair.TryGetValue(string key, out object value) { - return dict.TryGetValue(key, out value); + return dict.TryGetValue(key, out value!); } private class FastDynamicMetaObject : DynamicMetaObject diff --git a/src/CsvHelper/FieldCache.cs b/src/CsvHelper/FieldCache.cs index 37686e527..722a8fe24 100644 --- a/src/CsvHelper/FieldCache.cs +++ b/src/CsvHelper/FieldCache.cs @@ -2,136 +2,130 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; // https://blog.markvincze.com/back-to-basics-dictionary-part-2-net-implementation/ -namespace CsvHelper +namespace CsvHelper; + +/// +/// Caches fields. +/// Based on C#'s . +/// +internal class FieldCache { - /// - /// Caches fields. - /// Based on C#'s . - /// - internal class FieldCache + private readonly int maxFieldSize; + private int size; + private int[] buckets; + private Entry[] entries; + private int count; + + public FieldCache(int initialSize = 128, int maxFieldSize = 128) { - private readonly int maxFieldSize; - private int size; - private int[] buckets; - private Entry[] entries; - private int count; + this.maxFieldSize = maxFieldSize; + size = initialSize; + buckets = new int[size]; + entries = new Entry[size]; + } - public FieldCache(int initialSize = 128, int maxFieldSize = 128) + public string GetField(char[] buffer, int start, int length) + { + if (length == 0) { - this.maxFieldSize = maxFieldSize; - size = initialSize; - buckets = new int[size]; - entries = new Entry[size]; + return string.Empty; } - public string GetField(char[] buffer, int start, int length) + if (length > maxFieldSize) { - if (length == 0) - { - return string.Empty; - } - - if (length > maxFieldSize) - { - return new string(buffer, start, length); - } - - var hashCode = GetHashCode(buffer, start, length); - ref var bucket = ref GetBucket(hashCode); - int i = bucket - 1; - while ((uint)i < (uint)entries.Length) - { - ref var entry = ref entries[i]; - - if (entry.HashCode == hashCode && entry.Value.AsSpan().SequenceEqual(new Span(buffer, start, length))) - { - return entry.Value; - } + return new string(buffer, start, length); + } - i = entry.Next; - } + var hashCode = GetHashCode(buffer, start, length); + ref var bucket = ref GetBucket(hashCode); + int i = bucket - 1; + while ((uint)i < (uint)entries.Length) + { + ref var entry = ref entries[i]; - if (count == entries.Length) + if (entry.HashCode == hashCode && entry.Value.AsSpan().SequenceEqual(new Span(buffer, start, length))) { - Resize(); - bucket = ref GetBucket(hashCode); + return entry.Value; } - ref var reference = ref entries[count]; - reference.HashCode = hashCode; - reference.Next = bucket - 1; - reference.Value = new string(buffer, start, length); - bucket = count + 1; - count++; + i = entry.Next; + } - return reference.Value; + if (count == entries.Length) + { + Resize(); + bucket = ref GetBucket(hashCode); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private uint GetHashCode(char[] buffer, int start, int length) + ref var reference = ref entries[count]; + reference.HashCode = hashCode; + reference.Next = bucket - 1; + reference.Value = new string(buffer, start, length); + bucket = count + 1; + count++; + + return reference.Value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint GetHashCode(char[] buffer, int start, int length) + { + unchecked { - unchecked + uint hash = 17; + for (var i = start; i < start + length; i++) { - uint hash = 17; - for (var i = start; i < start + length; i++) - { - hash = hash * 31 + buffer[i]; - } - - return hash; + hash = hash * 31 + buffer[i]; } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref int GetBucket(uint hashCode) - { - return ref buckets[hashCode & buckets.Length - 1]; + return hash; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Resize() - { - size *= 2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref int GetBucket(uint hashCode) + { + return ref buckets[hashCode & buckets.Length - 1]; + } - var tempEntries = new Entry[size]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Resize() + { + size *= 2; - Array.Copy(entries, tempEntries, count); + var tempEntries = new Entry[size]; - buckets = new int[size]; + Array.Copy(entries, tempEntries, count); - for (int i = 0; i < count; i++) + buckets = new int[size]; + + for (int i = 0; i < count; i++) + { + ref var tempEntry = ref tempEntries[i]; + + if (tempEntry.Next >= -1) { - ref var tempEntry = ref tempEntries[i]; - - if (tempEntry.Next >= -1) - { - ref var bucket = ref GetBucket(tempEntry.HashCode); - tempEntry.Next = bucket - 1; - bucket = i + 1; - } + ref var bucket = ref GetBucket(tempEntry.HashCode); + tempEntry.Next = bucket - 1; + bucket = i + 1; } - - entries = tempEntries; } - [DebuggerDisplay("HashCode = {HashCode}, Next = {Next}, Value = {Value}")] - private struct Entry - { - public uint HashCode; + entries = tempEntries; + } + + [DebuggerDisplay("HashCode = {HashCode}, Next = {Next}, Value = {Value}")] + private struct Entry + { + public uint HashCode; - public int Next; + public int Next; - public string Value; - } + public string Value; } } diff --git a/src/CsvHelper/FieldValidationException.cs b/src/CsvHelper/FieldValidationException.cs index 5fcc7f5e7..c9cce6360 100644 --- a/src/CsvHelper/FieldValidationException.cs +++ b/src/CsvHelper/FieldValidationException.cs @@ -2,54 +2,51 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents a user supplied field validation failure. +/// +public class FieldValidationException : ValidationException { /// - /// Represents a user supplied field validation failure. + /// Gets the field that failed validation. /// - public class FieldValidationException : ValidationException - { - /// - /// Gets the field that failed validation. - /// - public string Field { get; private set; } + public string Field { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The reading context. - /// The field that failed validation. - public FieldValidationException(CsvContext context, string field) : base(context) - { - Field = field; - } + /// + /// Initializes a new instance of the class. + /// + /// The reading context. + /// The field that failed validation. + public FieldValidationException(CsvContext context, string field) : base(context) + { + Field = field; + } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The reading context. - /// The field that failed validation. - /// The message that describes the error. - public FieldValidationException(CsvContext context, string field, string message) : base(context, message) - { - Field = field; - } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The reading context. + /// The field that failed validation. + /// The message that describes the error. + public FieldValidationException(CsvContext context, string field, string message) : base(context, message) + { + Field = field; + } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The reading context. - /// The field that failed validation. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public FieldValidationException(CsvContext context, string field, string message, Exception innerException) : base(context, message, innerException) - { - Field = field; - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The reading context. + /// The field that failed validation. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public FieldValidationException(CsvContext context, string field, string message, Exception innerException) : base(context, message, innerException) + { + Field = field; } } diff --git a/src/CsvHelper/HeaderValidationException.cs b/src/CsvHelper/HeaderValidationException.cs index f68250525..993ef65c9 100644 --- a/src/CsvHelper/HeaderValidationException.cs +++ b/src/CsvHelper/HeaderValidationException.cs @@ -2,54 +2,51 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents a header validation failure. +/// +public class HeaderValidationException : ValidationException { /// - /// Represents a header validation failure. + /// Gets the invalid headers. /// - public class HeaderValidationException : ValidationException - { - /// - /// Gets the invalid headers. - /// - public InvalidHeader[] InvalidHeaders { get; private set; } + public InvalidHeader[] InvalidHeaders { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The reading context. - /// The invalid headers. - public HeaderValidationException(CsvContext context, InvalidHeader[] invalidHeaders) : base(context) - { - InvalidHeaders = invalidHeaders; - } + /// + /// Initializes a new instance of the class. + /// + /// The reading context. + /// The invalid headers. + public HeaderValidationException(CsvContext context, InvalidHeader[] invalidHeaders) : base(context) + { + InvalidHeaders = invalidHeaders; + } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The reading context. - /// The invalid headers. - /// The message that describes the error. - public HeaderValidationException(CsvContext context, InvalidHeader[] invalidHeaders, string message) : base(context, message) - { - InvalidHeaders = invalidHeaders; - } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The reading context. + /// The invalid headers. + /// The message that describes the error. + public HeaderValidationException(CsvContext context, InvalidHeader[] invalidHeaders, string message) : base(context, message) + { + InvalidHeaders = invalidHeaders; + } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The reading context. - /// The invalid headers. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public HeaderValidationException(CsvContext context, InvalidHeader[] invalidHeaders, string message, Exception innerException) : base(context, message, innerException) - { - InvalidHeaders = invalidHeaders; - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The reading context. + /// The invalid headers. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public HeaderValidationException(CsvContext context, InvalidHeader[] invalidHeaders, string message, Exception innerException) : base(context, message, innerException) + { + InvalidHeaders = invalidHeaders; } } diff --git a/src/CsvHelper/IFactory.cs b/src/CsvHelper/IFactory.cs index a8bc17b6d..8e44f7f9a 100644 --- a/src/CsvHelper/IFactory.cs +++ b/src/CsvHelper/IFactory.cs @@ -2,84 +2,82 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; -using System.IO; using CsvHelper.Configuration; +using System.Globalization; + +namespace CsvHelper; -namespace CsvHelper +/// +/// Defines methods used to create +/// CsvHelper classes. +/// +public interface IFactory { /// - /// Defines methods used to create - /// CsvHelper classes. + /// Creates an . /// - public interface IFactory - { - /// - /// Creates an . - /// - /// The text reader to use for the csv parser. - /// The configuration to use for the csv parser. - /// The created parser. - IParser CreateParser(TextReader reader, Configuration.CsvConfiguration configuration); + /// The text reader to use for the csv parser. + /// The configuration to use for the csv parser. + /// The created parser. + IParser CreateParser(TextReader reader, Configuration.CsvConfiguration configuration); - /// - /// Creates an . - /// - /// The text reader to use for the csv parser. - /// The culture information. - /// - /// The created parser. - /// - IParser CreateParser(TextReader reader, CultureInfo cultureInfo); + /// + /// Creates an . + /// + /// The text reader to use for the csv parser. + /// The culture information. + /// + /// The created parser. + /// + IParser CreateParser(TextReader reader, CultureInfo cultureInfo); - /// - /// Creates an . - /// - /// The text reader to use for the csv reader. - /// The configuration to use for the reader. - /// The created reader. - IReader CreateReader(TextReader reader, Configuration.CsvConfiguration configuration); + /// + /// Creates an . + /// + /// The text reader to use for the csv reader. + /// The configuration to use for the reader. + /// The created reader. + IReader CreateReader(TextReader reader, Configuration.CsvConfiguration configuration); - /// - /// Creates an . - /// - /// The text reader to use for the csv reader. - /// The culture information. - /// - /// The created reader. - /// - IReader CreateReader(TextReader reader, CultureInfo cultureInfo); + /// + /// Creates an . + /// + /// The text reader to use for the csv reader. + /// The culture information. + /// + /// The created reader. + /// + IReader CreateReader(TextReader reader, CultureInfo cultureInfo); - /// - /// Creates an . - /// - /// The parser used to create the reader. - /// The created reader. - IReader CreateReader(IParser parser); + /// + /// Creates an . + /// + /// The parser used to create the reader. + /// The created reader. + IReader CreateReader(IParser parser); - /// - /// Creates an . - /// - /// The text writer to use for the csv writer. - /// The configuration to use for the writer. - /// The created writer. - IWriter CreateWriter(TextWriter writer, Configuration.CsvConfiguration configuration); + /// + /// Creates an . + /// + /// The text writer to use for the csv writer. + /// The configuration to use for the writer. + /// The created writer. + IWriter CreateWriter(TextWriter writer, Configuration.CsvConfiguration configuration); - /// - /// Creates an . - /// - /// The text writer to use for the csv writer. - /// The culture information. - /// - /// The created writer. - /// - IWriter CreateWriter(TextWriter writer, CultureInfo cultureInfo); + /// + /// Creates an . + /// + /// The text writer to use for the csv writer. + /// The culture information. + /// + /// The created writer. + /// + IWriter CreateWriter(TextWriter writer, CultureInfo cultureInfo); - /// - /// Provides a fluent interface for dynamically creating s - /// - /// Type of class to map - /// Next available options - IHasMap CreateClassMapBuilder(); - } + /// + /// Provides a fluent interface for dynamically creating s + /// + /// Type of class to map + /// Next available options + IHasMap CreateClassMapBuilder(); } diff --git a/src/CsvHelper/IObjectResolver.cs b/src/CsvHelper/IObjectResolver.cs index 897d3af0a..b1a5070f6 100644 --- a/src/CsvHelper/IObjectResolver.cs +++ b/src/CsvHelper/IObjectResolver.cs @@ -2,56 +2,53 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Defines the functionality of a class that creates objects +/// from a given type. +/// +public interface IObjectResolver { /// - /// Defines the functionality of a class that creates objects - /// from a given type. + /// A value indicating if the resolver's + /// returns false that an object will still be created using + /// CsvHelper's object creation. True to fallback, otherwise false. + /// Default value is true. /// - public interface IObjectResolver - { - /// - /// A value indicating if the resolver's - /// returns false that an object will still be created using - /// CsvHelper's object creation. True to fallback, otherwise false. - /// Default value is true. - /// - bool UseFallback { get; } + bool UseFallback { get; } - /// - /// A value indicating if the resolver is able to resolve - /// the given type. True if the type can be resolved, - /// otherwise false. - /// - Func CanResolve { get; } + /// + /// A value indicating if the resolver is able to resolve + /// the given type. True if the type can be resolved, + /// otherwise false. + /// + Func CanResolve { get; } - /// - /// The function that creates an object from a given type. - /// - Func ResolveFunction { get; } + /// + /// The function that creates an object from a given type. + /// + Func ResolveFunction { get; } - /// - /// Creates an object from the given type using the - /// function. If is false, the object will be - /// created using CsvHelper's default object creation. If - /// is false, an exception is thrown. - /// - /// The type to create an instance from. The created object - /// may not be the same type as the given type. - /// Constructor arguments used to create the type. - object Resolve( Type type, params object[] constructorArgs ); + /// + /// Creates an object from the given type using the + /// function. If is false, the object will be + /// created using CsvHelper's default object creation. If + /// is false, an exception is thrown. + /// + /// The type to create an instance from. The created object + /// may not be the same type as the given type. + /// Constructor arguments used to create the type. + object Resolve(Type type, params object[] constructorArgs); - /// - /// Creates an object from the given type using the - /// function. If is false, the object will be - /// created using CsvHelper's default object creation. If - /// is false, an exception is thrown. - /// - /// The type to create an instance from. The created object - /// may not be the same type as the given type. - /// Constructor arguments used to create the type. - T Resolve( params object[] constructorArgs ); - } + /// + /// Creates an object from the given type using the + /// function. If is false, the object will be + /// created using CsvHelper's default object creation. If + /// is false, an exception is thrown. + /// + /// The type to create an instance from. The created object + /// may not be the same type as the given type. + /// Constructor arguments used to create the type. + T Resolve(params object[] constructorArgs); } diff --git a/src/CsvHelper/IParser.cs b/src/CsvHelper/IParser.cs index f3ddd6445..15b4703bc 100644 --- a/src/CsvHelper/IParser.cs +++ b/src/CsvHelper/IParser.cs @@ -2,88 +2,85 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using CsvHelper.Configuration; -using System.Threading.Tasks; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Defines methods used the parse a CSV file. +/// +public interface IParser : IDisposable { /// - /// Defines methods used the parse a CSV file. + /// Gets the count of how many bytes have been read. + /// needs + /// to be enabled for this value to be populated. /// - public interface IParser : IDisposable - { - /// - /// Gets the count of how many bytes have been read. - /// needs - /// to be enabled for this value to be populated. - /// - long ByteCount { get; } + long ByteCount { get; } - /// - /// Gets the count of how many characters have been read. - /// - long CharCount { get; } + /// + /// Gets the count of how many characters have been read. + /// + long CharCount { get; } - /// - /// Gets the number of fields for the current row. - /// - int Count { get; } + /// + /// Gets the number of fields for the current row. + /// + int Count { get; } - /// - /// Gets the field at the specified index for the current row. - /// - /// The index. - /// The field. - string this[int index] { get; } + /// + /// Gets the field at the specified index for the current row. + /// + /// The index. + /// The field. + string this[int index] { get; } - /// - /// Gets the record for the current row. Note: - /// It is much more efficient to only get the fields you need. If - /// you need all fields, then use this. - /// - string[] Record { get; } + /// + /// Gets the record for the current row. Note: + /// It is much more efficient to only get the fields you need. If + /// you need all fields, then use this. + /// + string[]? Record { get; } - /// - /// Gets the raw record for the current row. - /// - string RawRecord { get; } + /// + /// Gets the raw record for the current row. + /// + string RawRecord { get; } - /// - /// Gets the CSV row the parser is currently on. - /// - int Row { get; } + /// + /// Gets the CSV row the parser is currently on. + /// + int Row { get; } - /// - /// Gets the raw row the parser is currently on. - /// - int RawRow { get; } + /// + /// Gets the raw row the parser is currently on. + /// + int RawRow { get; } - /// - /// The delimiter the parser is using. - /// - string Delimiter { get; } + /// + /// The delimiter the parser is using. + /// + string Delimiter { get; } - /// - /// Gets the reading context. - /// - CsvContext Context { get; } + /// + /// Gets the reading context. + /// + CsvContext Context { get; } - /// - /// Gets the configuration. - /// - IParserConfiguration Configuration { get; } + /// + /// Gets the configuration. + /// + IParserConfiguration Configuration { get; } - /// - /// Reads a record from the CSV file. - /// - /// True if there are more records to read, otherwise false. - bool Read(); + /// + /// Reads a record from the CSV file. + /// + /// True if there are more records to read, otherwise false. + bool Read(); - /// - /// Reads a record from the CSV file asynchronously. - /// - /// True if there are more records to read, otherwise false. - Task ReadAsync(); - } + /// + /// Reads a record from the CSV file asynchronously. + /// + /// True if there are more records to read, otherwise false. + Task ReadAsync(); } diff --git a/src/CsvHelper/IReader.cs b/src/CsvHelper/IReader.cs index 76f39e051..8c4ebff5c 100644 --- a/src/CsvHelper/IReader.cs +++ b/src/CsvHelper/IReader.cs @@ -2,123 +2,117 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Threading; +namespace CsvHelper; -namespace CsvHelper +/// +/// Defines methods used to read parsed data +/// from a CSV file. +/// +public interface IReader : IReaderRow, IDisposable { /// - /// Defines methods used to read parsed data - /// from a CSV file. + /// Reads the header record without reading the first row. /// - public interface IReader : IReaderRow, IDisposable - { - /// - /// Reads the header record without reading the first row. - /// - /// True if there are more records, otherwise false. - bool ReadHeader(); + /// True if there are more records, otherwise false. + bool ReadHeader(); - /// - /// Advances the reader to the next record. This will not read headers. - /// You need to call then - /// for the headers to be read. - /// - /// True if there are more records, otherwise false. - bool Read(); + /// + /// Advances the reader to the next record. This will not read headers. + /// You need to call then + /// for the headers to be read. + /// + /// True if there are more records, otherwise false. + bool Read(); - /// - /// Advances the reader to the next record. This will not read headers. - /// You need to call then - /// for the headers to be read. - /// - /// True if there are more records, otherwise false. - Task ReadAsync(); + /// + /// Advances the reader to the next record. This will not read headers. + /// You need to call then + /// for the headers to be read. + /// + /// True if there are more records, otherwise false. + Task ReadAsync(); - /// - /// Gets all the records in the CSV file and - /// converts each to T. The Read method - /// should not be used when using this. - /// - /// The of the record. - /// An of records. - IEnumerable GetRecords(); + /// + /// Gets all the records in the CSV file and + /// converts each to T. The Read method + /// should not be used when using this. + /// + /// The of the record. + /// An of records. + IEnumerable GetRecords(); - /// - /// Gets all the records in the CSV file and converts - /// each to T. The read method - /// should not be used when using this. - /// - /// The of the record. - /// The anonymous type definition to use for the records. - /// An of records. - IEnumerable GetRecords(T anonymousTypeDefinition); + /// + /// Gets all the records in the CSV file and converts + /// each to T. The read method + /// should not be used when using this. + /// + /// The of the record. + /// The anonymous type definition to use for the records. + /// An of records. + IEnumerable GetRecords(T anonymousTypeDefinition); - /// - /// Gets all the records in the CSV file and - /// converts each to T. The Read method - /// should not be used when using this. - /// - /// The of the record. - /// An of records. - IEnumerable GetRecords(Type type); + /// + /// Gets all the records in the CSV file and + /// converts each to T. The Read method + /// should not be used when using this. + /// + /// The of the record. + /// An of records. + IEnumerable GetRecords(Type type); - /// - /// Enumerates the records hydrating the given record instance with row data. - /// The record instance is re-used and not cleared on each enumeration. - /// This only works for streaming rows. If any methods are called on the projection - /// that force the evaluation of the IEnumerable, such as ToList(), the entire list - /// will contain the same instance of the record, which is the last row. - /// - /// The type of the record. - /// The record to fill each enumeration. - /// An of records. - IEnumerable EnumerateRecords(T record); + /// + /// Enumerates the records hydrating the given record instance with row data. + /// The record instance is re-used and not cleared on each enumeration. + /// This only works for streaming rows. If any methods are called on the projection + /// that force the evaluation of the IEnumerable, such as ToList(), the entire list + /// will contain the same instance of the record, which is the last row. + /// + /// The type of the record. + /// The record to fill each enumeration. + /// An of records. + IEnumerable EnumerateRecords(T record); - /// - /// Gets all the records in the CSV file and - /// converts each to T. The Read method - /// should not be used when using this. - /// - /// The of the record. - /// The cancellation token to stop the writing. - /// An of records. - IAsyncEnumerable GetRecordsAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Gets all the records in the CSV file and + /// converts each to T. The Read method + /// should not be used when using this. + /// + /// The of the record. + /// The cancellation token to stop the writing. + /// An of records. + IAsyncEnumerable GetRecordsAsync(CancellationToken cancellationToken = default(CancellationToken)); - /// - /// Gets all the records in the CSV file and converts - /// each to T. The read method - /// should not be used when using this. - /// - /// The of the record. - /// The anonymous type definition to use for the records. - /// The cancellation token to stop the writing. - /// An of records. - IAsyncEnumerable GetRecordsAsync(T anonymousTypeDefinition, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Gets all the records in the CSV file and converts + /// each to T. The read method + /// should not be used when using this. + /// + /// The of the record. + /// The anonymous type definition to use for the records. + /// The cancellation token to stop the writing. + /// An of records. + IAsyncEnumerable GetRecordsAsync(T anonymousTypeDefinition, CancellationToken cancellationToken = default(CancellationToken)); - /// - /// Gets all the records in the CSV file and - /// converts each to T. The Read method - /// should not be used when using this. - /// - /// The of the record. - /// The cancellation token to stop the writing. - /// An of records. - IAsyncEnumerable GetRecordsAsync(Type type, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Gets all the records in the CSV file and + /// converts each to T. The Read method + /// should not be used when using this. + /// + /// The of the record. + /// The cancellation token to stop the writing. + /// An of records. + IAsyncEnumerable GetRecordsAsync(Type type, CancellationToken cancellationToken = default(CancellationToken)); - /// - /// Enumerates the records hydrating the given record instance with row data. - /// The record instance is re-used and not cleared on each enumeration. - /// This only works for streaming rows. If any methods are called on the projection - /// that force the evaluation of the IEnumerable, such as ToList(), the entire list - /// will contain the same instance of the record, which is the last row. - /// - /// The type of the record. - /// The record to fill each enumeration. - /// /// The cancellation token to stop the writing. - /// An of records. - IAsyncEnumerable EnumerateRecordsAsync(T record, CancellationToken cancellationToken = default(CancellationToken)); - } + /// + /// Enumerates the records hydrating the given record instance with row data. + /// The record instance is re-used and not cleared on each enumeration. + /// This only works for streaming rows. If any methods are called on the projection + /// that force the evaluation of the IEnumerable, such as ToList(), the entire list + /// will contain the same instance of the record, which is the last row. + /// + /// The type of the record. + /// The record to fill each enumeration. + /// /// The cancellation token to stop the writing. + /// An of records. + IAsyncEnumerable EnumerateRecordsAsync(T record, CancellationToken cancellationToken = default(CancellationToken)); } diff --git a/src/CsvHelper/IReaderRow.cs b/src/CsvHelper/IReaderRow.cs index 1d897d816..f7e5e65f3 100644 --- a/src/CsvHelper/IReaderRow.cs +++ b/src/CsvHelper/IReaderRow.cs @@ -2,427 +2,425 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using CsvHelper.Configuration; using CsvHelper.TypeConversion; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Defines methods used to read parsed data +/// from a CSV file row. +/// +public interface IReaderRow { /// - /// Defines methods used to read parsed data - /// from a CSV file row. - /// - public interface IReaderRow - { - /// - /// Gets the column count of the current row. - /// This should match . - /// - int ColumnCount { get; } - - /// - /// Gets the field index the reader is currently on. - /// - int CurrentIndex { get; } - - /// - /// Gets the header record. - /// - string[] HeaderRecord { get; } - - /// - /// Gets the parser. - /// - IParser Parser { get; } - - /// - /// Gets the reading context. - /// - CsvContext Context { get; } - - /// - /// Gets or sets the configuration. - /// - IReaderConfiguration Configuration { get; } - - /// - /// Gets the raw field at position (column) index. - /// - /// The zero based index of the field. - /// The raw field. - string this[int index] { get; } - - /// - /// Gets the raw field at position (column) name. - /// - /// The named index of the field. - /// The raw field. - string this[string name] { get; } - - /// - /// Gets the raw field at position (column) name. - /// - /// The named index of the field. - /// The zero based index of the field. - /// The raw field. - string this[string name, int index] { get; } - - /// - /// Gets the raw field at position (column) index. - /// - /// The zero based index of the field. - /// The raw field. - string GetField(int index); - - /// - /// Gets the raw field at position (column) name. - /// - /// The named index of the field. - /// The raw field. - string GetField(string name); - - /// - /// Gets the raw field at position (column) name and the index - /// instance of that field. The index is used when there are - /// multiple columns with the same header name. - /// - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The raw field. - string GetField(string name, int index); - - /// - /// Gets the field converted to using - /// the specified . - /// - /// The type of the field. - /// The index of the field. - /// The field converted to . - object GetField(Type type, int index); - - /// - /// Gets the field converted to using - /// the specified . - /// - /// The type of the field. - /// The named index of the field. - /// The field converted to . - object GetField(Type type, string name); - - /// - /// Gets the field converted to using - /// the specified . - /// - /// The type of the field. - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The field converted to . - object GetField(Type type, string name, int index); - - /// - /// Gets the field converted to using - /// the specified . - /// - /// The type of the field. - /// The index of the field. - /// The used to convert the field to . - /// The field converted to . - object GetField(Type type, int index, ITypeConverter converter); - - /// - /// Gets the field converted to using - /// the specified . - /// - /// The type of the field. - /// The named index of the field. - /// The used to convert the field to . - /// The field converted to . - object GetField(Type type, string name, ITypeConverter converter); - - /// - /// Gets the field converted to using - /// the specified . - /// - /// The type of the field. - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The used to convert the field to . - /// The field converted to . - object GetField(Type type, string name, int index, ITypeConverter converter); - - /// - /// Gets the field converted to T at position (column) index. - /// - /// The of the field. - /// The zero based index of the field. - /// The field converted to T. - T GetField(int index); - - /// - /// Gets the field converted to T at position (column) name. - /// - /// The of the field. - /// The named index of the field. - /// The field converted to T. - T GetField(string name); - - /// - /// Gets the field converted to T at position - /// (column) name and the index instance of that field. The index - /// is used when there are multiple columns with the same header name. - /// - /// - /// The named index of the field. - /// The zero based index of the instance of the field. - /// - T GetField(string name, int index); - - /// - /// Gets the field converted to T at position (column) index using - /// the given . - /// - /// The of the field. - /// The zero based index of the field. - /// The used to convert the field to T. - /// The field converted to T. - T GetField(int index, ITypeConverter converter); - - /// - /// Gets the field converted to T at position (column) name using - /// the given . - /// - /// The of the field. - /// The named index of the field. - /// The used to convert the field to T. - /// The field converted to T. - T GetField(string name, ITypeConverter converter); - - /// - /// Gets the field converted to T at position - /// (column) name and the index instance of that field. The index - /// is used when there are multiple columns with the same header name. - /// - /// The of the field. - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The used to convert the field to T. - /// The field converted to T. - T GetField(string name, int index, ITypeConverter converter); - - /// - /// Gets the field converted to T at position (column) index using - /// the given . - /// - /// The of the field. - /// The used to convert the field to T. - /// The zero based index of the field. - /// The field converted to T. - T GetField(int index) where TConverter : ITypeConverter; - - /// - /// Gets the field converted to T at position (column) name using - /// the given . - /// - /// The of the field. - /// The used to convert the field to T. - /// The named index of the field. - /// The field converted to T. - T GetField(string name) where TConverter : ITypeConverter; - - /// - /// Gets the field converted to T at position - /// (column) name and the index instance of that field. The index - /// is used when there are multiple columns with the same header name. - /// - /// The of the field. - /// The used to convert the field to T. - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The field converted to T. - T GetField(string name, int index) where TConverter : ITypeConverter; - - /// - /// Gets the field converted to T at position (column) index. - /// - /// The of the field. - /// The zero based index of the field. - /// The field converted to type T. - /// A value indicating if the get was successful. - bool TryGetField(Type type, int index, out object field); - - /// - /// Gets the field converted to T at position (column) name. - /// - /// The of the field. - /// The named index of the field. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(Type type, string name, out object field); - - /// - /// Gets the field converted to T at position - /// (column) name and the index instance of that field. The index - /// is used when there are multiple columns with the same header name. - /// - /// The of the field. - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(Type type, string name, int index, out object field); - - /// - /// Gets the field converted to T at position (column) index - /// using the specified . - /// - /// The of the field. - /// The zero based index of the field. - /// The used to convert the field to T. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(Type type, int index, ITypeConverter converter, out object field); - - /// - /// Gets the field converted to T at position (column) name - /// using the specified . - /// - /// The of the field. - /// The named index of the field. - /// The used to convert the field to T. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(Type type, string name, ITypeConverter converter, out object field); - - /// - /// Gets the field converted to T at position (column) name - /// using the specified . - /// - /// The of the field. - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The used to convert the field to T. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(Type type, string name, int index, ITypeConverter converter, out object field); - - /// - /// Gets the field converted to T at position (column) index. - /// - /// The of the field. - /// The zero based index of the field. - /// The field converted to type T. - /// A value indicating if the get was successful. - bool TryGetField(int index, out T field); - - /// - /// Gets the field converted to T at position (column) name. - /// - /// The of the field. - /// The named index of the field. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(string name, out T field); - - /// - /// Gets the field converted to T at position - /// (column) name and the index instance of that field. The index - /// is used when there are multiple columns with the same header name. - /// - /// - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(string name, int index, out T field); - - /// - /// Gets the field converted to T at position (column) index - /// using the specified . - /// - /// The of the field. - /// The zero based index of the field. - /// The used to convert the field to T. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(int index, ITypeConverter converter, out T field); - - /// - /// Gets the field converted to T at position (column) name - /// using the specified . - /// - /// The of the field. - /// The named index of the field. - /// The used to convert the field to T. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(string name, ITypeConverter converter, out T field); - - /// - /// Gets the field converted to T at position (column) name - /// using the specified . - /// - /// The of the field. - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The used to convert the field to T. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(string name, int index, ITypeConverter converter, out T field); - - /// - /// Gets the field converted to T at position (column) index - /// using the specified . - /// - /// The of the field. - /// The used to convert the field to T. - /// The zero based index of the field. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(int index, out T field) where TConverter : ITypeConverter; - - /// - /// Gets the field converted to T at position (column) name - /// using the specified . - /// - /// The of the field. - /// The used to convert the field to T. - /// The named index of the field. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(string name, out T field) where TConverter : ITypeConverter; - - /// - /// Gets the field converted to T at position (column) name - /// using the specified . - /// - /// The of the field. - /// The used to convert the field to T. - /// The named index of the field. - /// The zero based index of the instance of the field. - /// The field converted to T. - /// A value indicating if the get was successful. - bool TryGetField(string name, int index, out T field) where TConverter : ITypeConverter; - - /// - /// Gets the record converted into T. - /// - /// The of the record. - /// The record converted to T. - T GetRecord(); - - /// - /// Get the record converted into T. - /// - /// The of the record. - /// The anonymous type definition to use for the record. - /// The record converted to T. - T GetRecord(T anonymousTypeDefinition); - - /// - /// Gets the record. - /// - /// The of the record. - /// The record. - object GetRecord(Type type); - } + /// Gets the column count of the current row. + /// This should match . + /// + int ColumnCount { get; } + + /// + /// Gets the field index the reader is currently on. + /// + int CurrentIndex { get; } + + /// + /// Gets the header record. + /// + string[]? HeaderRecord { get; } + + /// + /// Gets the parser. + /// + IParser Parser { get; } + + /// + /// Gets the reading context. + /// + CsvContext Context { get; } + + /// + /// Gets or sets the configuration. + /// + IReaderConfiguration Configuration { get; } + + /// + /// Gets the raw field at position (column) index. + /// + /// The zero based index of the field. + /// The raw field. + string? this[int index] { get; } + + /// + /// Gets the raw field at position (column) name. + /// + /// The named index of the field. + /// The raw field. + string? this[string name] { get; } + + /// + /// Gets the raw field at position (column) name. + /// + /// The named index of the field. + /// The zero based index of the field. + /// The raw field. + string? this[string name, int index] { get; } + + /// + /// Gets the raw field at position (column) index. + /// + /// The zero based index of the field. + /// The raw field. + string? GetField(int index); + + /// + /// Gets the raw field at position (column) name. + /// + /// The named index of the field. + /// The raw field. + string? GetField(string name); + + /// + /// Gets the raw field at position (column) name and the index + /// instance of that field. The index is used when there are + /// multiple columns with the same header name. + /// + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The raw field. + string? GetField(string name, int index); + + /// + /// Gets the field converted to using + /// the specified . + /// + /// The type of the field. + /// The index of the field. + /// The field converted to . + object? GetField(Type type, int index); + + /// + /// Gets the field converted to using + /// the specified . + /// + /// The type of the field. + /// The named index of the field. + /// The field converted to . + object? GetField(Type type, string name); + + /// + /// Gets the field converted to using + /// the specified . + /// + /// The type of the field. + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The field converted to . + object? GetField(Type type, string name, int index); + + /// + /// Gets the field converted to using + /// the specified . + /// + /// The type of the field. + /// The index of the field. + /// The used to convert the field to . + /// The field converted to . + object? GetField(Type type, int index, ITypeConverter converter); + + /// + /// Gets the field converted to using + /// the specified . + /// + /// The type of the field. + /// The named index of the field. + /// The used to convert the field to . + /// The field converted to . + object? GetField(Type type, string name, ITypeConverter converter); + + /// + /// Gets the field converted to using + /// the specified . + /// + /// The type of the field. + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The used to convert the field to . + /// The field converted to . + object? GetField(Type type, string name, int index, ITypeConverter converter); + + /// + /// Gets the field converted to T at position (column) index. + /// + /// The of the field. + /// The zero based index of the field. + /// The field converted to T. + T? GetField(int index); + + /// + /// Gets the field converted to T at position (column) name. + /// + /// The of the field. + /// The named index of the field. + /// The field converted to T. + T? GetField(string name); + + /// + /// Gets the field converted to T at position + /// (column) name and the index instance of that field. The index + /// is used when there are multiple columns with the same header name. + /// + /// + /// The named index of the field. + /// The zero based index of the instance of the field. + /// + T? GetField(string name, int index); + + /// + /// Gets the field converted to T at position (column) index using + /// the given . + /// + /// The of the field. + /// The zero based index of the field. + /// The used to convert the field to T. + /// The field converted to T. + T? GetField(int index, ITypeConverter converter); + + /// + /// Gets the field converted to T at position (column) name using + /// the given . + /// + /// The of the field. + /// The named index of the field. + /// The used to convert the field to T. + /// The field converted to T. + T? GetField(string name, ITypeConverter converter); + + /// + /// Gets the field converted to T at position + /// (column) name and the index instance of that field. The index + /// is used when there are multiple columns with the same header name. + /// + /// The of the field. + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The used to convert the field to T. + /// The field converted to T. + T? GetField(string name, int index, ITypeConverter converter); + + /// + /// Gets the field converted to T at position (column) index using + /// the given . + /// + /// The of the field. + /// The used to convert the field to T. + /// The zero based index of the field. + /// The field converted to T. + T? GetField(int index) where TConverter : ITypeConverter; + + /// + /// Gets the field converted to T at position (column) name using + /// the given . + /// + /// The of the field. + /// The used to convert the field to T. + /// The named index of the field. + /// The field converted to T. + T? GetField(string name) where TConverter : ITypeConverter; + + /// + /// Gets the field converted to T at position + /// (column) name and the index instance of that field. The index + /// is used when there are multiple columns with the same header name. + /// + /// The of the field. + /// The used to convert the field to T. + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The field converted to T. + T? GetField(string name, int index) where TConverter : ITypeConverter; + + /// + /// Gets the field converted to T at position (column) index. + /// + /// The of the field. + /// The zero based index of the field. + /// The field converted to type T. + /// A value indicating if the get was successful. + bool TryGetField(Type type, int index, out object? field); + + /// + /// Gets the field converted to T at position (column) name. + /// + /// The of the field. + /// The named index of the field. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(Type type, string name, out object? field); + + /// + /// Gets the field converted to T at position + /// (column) name and the index instance of that field. The index + /// is used when there are multiple columns with the same header name. + /// + /// The of the field. + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(Type type, string name, int index, out object? field); + + /// + /// Gets the field converted to T at position (column) index + /// using the specified . + /// + /// The of the field. + /// The zero based index of the field. + /// The used to convert the field to T. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(Type type, int index, ITypeConverter converter, out object? field); + + /// + /// Gets the field converted to T at position (column) name + /// using the specified . + /// + /// The of the field. + /// The named index of the field. + /// The used to convert the field to T. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(Type type, string name, ITypeConverter converter, out object? field); + + /// + /// Gets the field converted to T at position (column) name + /// using the specified . + /// + /// The of the field. + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The used to convert the field to T. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(Type type, string name, int index, ITypeConverter converter, out object? field); + + /// + /// Gets the field converted to T at position (column) index. + /// + /// The of the field. + /// The zero based index of the field. + /// The field converted to type T. + /// A value indicating if the get was successful. + bool TryGetField(int index, out T? field); + + /// + /// Gets the field converted to T at position (column) name. + /// + /// The of the field. + /// The named index of the field. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(string name, out T? field); + + /// + /// Gets the field converted to T at position + /// (column) name and the index instance of that field. The index + /// is used when there are multiple columns with the same header name. + /// + /// + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(string name, int index, out T? field); + + /// + /// Gets the field converted to T at position (column) index + /// using the specified . + /// + /// The of the field. + /// The zero based index of the field. + /// The used to convert the field to T. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(int index, ITypeConverter converter, out T? field); + + /// + /// Gets the field converted to T at position (column) name + /// using the specified . + /// + /// The of the field. + /// The named index of the field. + /// The used to convert the field to T. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(string name, ITypeConverter converter, out T? field); + + /// + /// Gets the field converted to T at position (column) name + /// using the specified . + /// + /// The of the field. + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The used to convert the field to T. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(string name, int index, ITypeConverter converter, out T? field); + + /// + /// Gets the field converted to T at position (column) index + /// using the specified . + /// + /// The of the field. + /// The used to convert the field to T. + /// The zero based index of the field. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(int index, out T? field) where TConverter : ITypeConverter; + + /// + /// Gets the field converted to T at position (column) name + /// using the specified . + /// + /// The of the field. + /// The used to convert the field to T. + /// The named index of the field. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(string name, out T? field) where TConverter : ITypeConverter; + + /// + /// Gets the field converted to T at position (column) name + /// using the specified . + /// + /// The of the field. + /// The used to convert the field to T. + /// The named index of the field. + /// The zero based index of the instance of the field. + /// The field converted to T. + /// A value indicating if the get was successful. + bool TryGetField(string name, int index, out T? field) where TConverter : ITypeConverter; + + /// + /// Gets the record converted into T. + /// + /// The of the record. + /// The record converted to T. + T? GetRecord(); + + /// + /// Get the record converted into T. + /// + /// The of the record. + /// The anonymous type definition to use for the record. + /// The record converted to T. + T? GetRecord(T anonymousTypeDefinition); + + /// + /// Gets the record. + /// + /// The of the record. + /// The record. + object? GetRecord(Type type); } diff --git a/src/CsvHelper/IWriter.cs b/src/CsvHelper/IWriter.cs index aaa4a2cdb..291998877 100644 --- a/src/CsvHelper/IWriter.cs +++ b/src/CsvHelper/IWriter.cs @@ -2,80 +2,74 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Collections; -using System.IO; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Threading; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Defines methods used to write to a CSV file. +/// +public interface IWriter : IWriterRow, IDisposable, IAsyncDisposable { /// - /// Defines methods used to write to a CSV file. + /// Flushes the internal buffer to the then + /// flushes the . /// - public interface IWriter : IWriterRow, IDisposable, IAsyncDisposable - { - /// - /// Flushes the internal buffer to the then - /// flushes the . - /// - void Flush(); + void Flush(); - /// - /// Flushes the internal buffer to the then - /// flushes the . - /// - Task FlushAsync(); + /// + /// Flushes the internal buffer to the then + /// flushes the . + /// + Task FlushAsync(); - /// - /// Ends writing of the current record and starts a new record. - /// This flushes the buffer to the but - /// does not flush the . - /// - void NextRecord(); + /// + /// Ends writing of the current record and starts a new record. + /// This flushes the buffer to the but + /// does not flush the . + /// + void NextRecord(); - /// - /// Ends writing of the current record and starts a new record. - /// This flushes the buffer to the but - /// does not flush the . - /// - Task NextRecordAsync(); + /// + /// Ends writing of the current record and starts a new record. + /// This flushes the buffer to the but + /// does not flush the . + /// + Task NextRecordAsync(); - /// - /// Writes the list of records to the CSV file. - /// - /// The records to write. - void WriteRecords(IEnumerable records); + /// + /// Writes the list of records to the CSV file. + /// + /// The records to write. + void WriteRecords(IEnumerable records); - /// - /// Writes the list of records to the CSV file. - /// - /// Record type. - /// The records to write. - void WriteRecords(IEnumerable records); + /// + /// Writes the list of records to the CSV file. + /// + /// Record type. + /// The records to write. + void WriteRecords(IEnumerable records); - /// - /// Writes the list of records to the CSV file. - /// - /// The records to write. - /// The cancellation token to stop the writing. - Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default); + /// + /// Writes the list of records to the CSV file. + /// + /// The records to write. + /// The cancellation token to stop the writing. + Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default); - /// - /// Writes the list of records to the CSV file. - /// - /// Record type. - /// The records to write. - /// The cancellation token to stop the writing. - Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default); + /// + /// Writes the list of records to the CSV file. + /// + /// Record type. + /// The records to write. + /// The cancellation token to stop the writing. + Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default); - /// - /// Writes the list of records to the CSV file. - /// - /// Record type. - /// The records to write. - /// The cancellation token to stop the writing. - Task WriteRecordsAsync(IAsyncEnumerable records, CancellationToken cancellationToken = default); - } + /// + /// Writes the list of records to the CSV file. + /// + /// Record type. + /// The records to write. + /// The cancellation token to stop the writing. + Task WriteRecordsAsync(IAsyncEnumerable records, CancellationToken cancellationToken = default); } diff --git a/src/CsvHelper/IWriterRow.cs b/src/CsvHelper/IWriterRow.cs index 808ded8ac..9b0478f6c 100644 --- a/src/CsvHelper/IWriterRow.cs +++ b/src/CsvHelper/IWriterRow.cs @@ -2,134 +2,132 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using CsvHelper.Configuration; using CsvHelper.TypeConversion; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Defines methods used to write a CSV row. +/// +public interface IWriterRow { /// - /// Defines methods used to write a CSV row. + /// The header record. + /// + string?[]? HeaderRecord { get; } + + /// + /// The current row. + /// + int Row { get; } + + /// + /// The current field index. + /// + int Index { get; } + + /// + /// Gets the writing context. + /// + CsvContext Context { get; } + + /// + /// Gets or sets the configuration. + /// + IWriterConfiguration Configuration { get; } + + /// + /// Writes a field that has already been converted to a + /// from an . + /// If the field is null, it won't get written. A type converter + /// will always return a string, even if field is null. If the + /// converter returns a null, it means that the converter has already + /// written data, and the returned value should not be written. + /// + /// The converted field to write. + /// The type of the field before it was converted into a string. + void WriteConvertedField(string? field, Type fieldType); + + /// + /// Writes the field to the CSV file. The field + /// may get quotes added to it. + /// When all fields are written for a record, + /// must be called + /// to complete writing of the current record. + /// + /// The field to write. + void WriteField(string? field); + + /// + /// Writes the field to the CSV file. This will + /// ignore any need to quote and ignore + /// + /// and just quote based on the shouldQuote + /// parameter. + /// When all fields are written for a record, + /// must be called + /// to complete writing of the current record. + /// + /// The field to write. + /// True to quote the field, otherwise false. + void WriteField(string? field, bool shouldQuote); + + /// + /// Writes the field to the CSV file. + /// When all fields are written for a record, + /// must be called + /// to complete writing of the current record. + /// + /// The type of the field. + /// The field to write. + void WriteField(T? field); + + /// + /// Writes the field to the CSV file. + /// When all fields are written for a record, + /// must be called + /// to complete writing of the current record. + /// + /// The type of the field. + /// The field to write. + /// The converter used to convert the field into a string. + void WriteField(T? field, ITypeConverter converter); + + /// + /// Writes the field to the CSV file + /// using the given . + /// When all fields are written for a record, + /// must be called + /// to complete writing of the current record. + /// + /// The type of the field. + /// The type of the converter. + /// The field to write. + void WriteField(T? field); + + /// + /// Writes a comment. + /// + /// The comment to write. + void WriteComment(string? comment); + + /// + /// Writes the header record from the given members. + /// + /// The type of the record. + void WriteHeader(); + + /// + /// Writes the header record from the given members. + /// + /// The type of the record. + void WriteHeader(Type type); + + /// + /// Writes the record to the CSV file. /// - public interface IWriterRow - { - /// - /// The header record. - /// - string[] HeaderRecord { get; } - - /// - /// The current row. - /// - int Row { get; } - - /// - /// The current field index. - /// - int Index { get; } - - /// - /// Gets the writing context. - /// - CsvContext Context { get; } - - /// - /// Gets or sets the configuration. - /// - IWriterConfiguration Configuration { get; } - - /// - /// Writes a field that has already been converted to a - /// from an . - /// If the field is null, it won't get written. A type converter - /// will always return a string, even if field is null. If the - /// converter returns a null, it means that the converter has already - /// written data, and the returned value should not be written. - /// - /// The converted field to write. - /// The type of the field before it was converted into a string. - void WriteConvertedField(string field, Type fieldType); - - /// - /// Writes the field to the CSV file. The field - /// may get quotes added to it. - /// When all fields are written for a record, - /// must be called - /// to complete writing of the current record. - /// - /// The field to write. - void WriteField(string field); - - /// - /// Writes the field to the CSV file. This will - /// ignore any need to quote and ignore - /// - /// and just quote based on the shouldQuote - /// parameter. - /// When all fields are written for a record, - /// must be called - /// to complete writing of the current record. - /// - /// The field to write. - /// True to quote the field, otherwise false. - void WriteField(string field, bool shouldQuote); - - /// - /// Writes the field to the CSV file. - /// When all fields are written for a record, - /// must be called - /// to complete writing of the current record. - /// - /// The type of the field. - /// The field to write. - void WriteField(T field); - - /// - /// Writes the field to the CSV file. - /// When all fields are written for a record, - /// must be called - /// to complete writing of the current record. - /// - /// The type of the field. - /// The field to write. - /// The converter used to convert the field into a string. - void WriteField(T field, ITypeConverter converter); - - /// - /// Writes the field to the CSV file - /// using the given . - /// When all fields are written for a record, - /// must be called - /// to complete writing of the current record. - /// - /// The type of the field. - /// The type of the converter. - /// The field to write. - void WriteField(T field); - - /// - /// Writes a comment. - /// - /// The comment to write. - void WriteComment(string comment); - - /// - /// Writes the header record from the given members. - /// - /// The type of the record. - void WriteHeader(); - - /// - /// Writes the header record from the given members. - /// - /// The type of the record. - void WriteHeader(Type type); - - /// - /// Writes the record to the CSV file. - /// - /// The type of the record. - /// The record to write. - void WriteRecord(T record); - } + /// The type of the record. + /// The record to write. + void WriteRecord(T record); } diff --git a/src/CsvHelper/InvalidHeader.cs b/src/CsvHelper/InvalidHeader.cs index 827efef5c..1d4d1b178 100644 --- a/src/CsvHelper/InvalidHeader.cs +++ b/src/CsvHelper/InvalidHeader.cs @@ -2,27 +2,20 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace CsvHelper; -namespace CsvHelper +/// +/// Invalid header information. +/// +public class InvalidHeader { /// - /// Invalid header information. + /// Header names mapped to a CSV field that couldn't be found. /// - public class InvalidHeader - { - /// - /// Header names mapped to a CSV field that couldn't be found. - /// - public List Names { get; set; } = new List(); + public List Names { get; set; } = new List(); - /// - /// Header name index maped to a CSV field that couldn't be found. - /// - public int Index { get; set; } - } + /// + /// Header name index maped to a CSV field that couldn't be found. + /// + public int Index { get; set; } } diff --git a/src/CsvHelper/LinkedListExtensions.cs b/src/CsvHelper/LinkedListExtensions.cs index ce806688c..9e1186dcc 100644 --- a/src/CsvHelper/LinkedListExtensions.cs +++ b/src/CsvHelper/LinkedListExtensions.cs @@ -2,27 +2,24 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Collections.Generic; +namespace CsvHelper; -namespace CsvHelper +internal static class LinkedListExtensions { - internal static class LinkedListExtensions + public static void Drop(this LinkedList list, LinkedListNode? node) { - public static void Drop(this LinkedList list, LinkedListNode node) + if (list.Count == 0 || node == null) { - if (list.Count == 0) - { - return; - } + return; + } - while (list.Count > 0) + while (list.Count > 0) + { + var nodeToRemove = list.Last; + list.RemoveLast(); + if (nodeToRemove == node) { - var nodeToRemove = list.Last; - list.RemoveLast(); - if (nodeToRemove == node) - { - break; - } + break; } } } diff --git a/src/CsvHelper/MaxFieldSizeException.cs b/src/CsvHelper/MaxFieldSizeException.cs index e7d400e42..f4e52fedf 100644 --- a/src/CsvHelper/MaxFieldSizeException.cs +++ b/src/CsvHelper/MaxFieldSizeException.cs @@ -2,38 +2,35 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents an error due to a field that is too large. +/// +[Serializable] +public class MaxFieldSizeException : CsvHelperException { /// - /// Represents an error due to a field that is too large. + /// Initializes a new instance of the class. /// - [Serializable] - public class MaxFieldSizeException : CsvHelperException - { - /// - /// Initializes a new instance of the class. - /// - /// The reading context. - public MaxFieldSizeException(CsvContext context) : base(context) { } + /// The reading context. + public MaxFieldSizeException(CsvContext context) : base(context) { } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The reading context. - /// The message that describes the error. - public MaxFieldSizeException(CsvContext context, string message) : base(context, message) { } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The reading context. + /// The message that describes the error. + public MaxFieldSizeException(CsvContext context, string message) : base(context, message) { } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The reading context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public MaxFieldSizeException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The reading context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public MaxFieldSizeException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } } diff --git a/src/CsvHelper/MissingFieldException.cs b/src/CsvHelper/MissingFieldException.cs index b99b3ebb7..070fb40f3 100644 --- a/src/CsvHelper/MissingFieldException.cs +++ b/src/CsvHelper/MissingFieldException.cs @@ -2,39 +2,36 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents an error caused because a field is missing +/// in the header while reading a CSV file. +/// +[Serializable] +public class MissingFieldException : ReaderException { /// - /// Represents an error caused because a field is missing - /// in the header while reading a CSV file. + /// Initializes a new instance of the class. /// - [Serializable] - public class MissingFieldException : ReaderException - { - /// - /// Initializes a new instance of the class. - /// - /// The reading context. - public MissingFieldException(CsvContext context) : base(context) { } + /// The reading context. + public MissingFieldException(CsvContext context) : base(context) { } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The reading context. - /// The message that describes the error. - public MissingFieldException(CsvContext context, string message) : base(context, message) { } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The reading context. + /// The message that describes the error. + public MissingFieldException(CsvContext context, string message) : base(context, message) { } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The reading context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public MissingFieldException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The reading context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public MissingFieldException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } } diff --git a/src/CsvHelper/ObjectCreator.cs b/src/CsvHelper/ObjectCreator.cs index 4d1b6145f..a6f6152d8 100644 --- a/src/CsvHelper/ObjectCreator.cs +++ b/src/CsvHelper/ObjectCreator.cs @@ -2,203 +2,197 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Efficiently creates instances of object types. +/// +public class ObjectCreator { + private readonly Dictionary> cache = new Dictionary>(); + /// - /// Efficiently creates instances of object types. + /// Creates an instance of type T using the given arguments. /// - public class ObjectCreator - { - private readonly Dictionary> cache = new Dictionary>(); - - /// - /// Creates an instance of type T using the given arguments. - /// - /// The type to create an instance of. - /// The constrcutor arguments. - public T CreateInstance(params object[] args) + /// The type to create an instance of. + /// The constrcutor arguments. + public T CreateInstance(params object[] args) + { + return (T)CreateInstance(typeof(T), args); + } + + /// + /// Creates an instance of the given type using the given arguments. + /// + /// The type to create an instance of. + /// The constructor arguments. + public object CreateInstance(Type type, params object[] args) + { + var func = GetFunc(type, args); + + return func(args); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Func GetFunc(Type type, object[] args) + { + var argTypes = GetArgTypes(args); + var key = GetConstructorCacheKey(type, argTypes); + if (!cache.TryGetValue(key, out var func)) { - return (T)CreateInstance(typeof(T), args); + cache[key] = func = CreateInstanceFunc(type, argTypes); } - /// - /// Creates an instance of the given type using the given arguments. - /// - /// The type to create an instance of. - /// The constructor arguments. - public object CreateInstance(Type type, params object[] args) - { - var func = GetFunc(type, args); + return func; + } - return func(args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Type[] GetArgTypes(object[] args) + { + var argTypes = new Type[args.Length]; + for (var i = 0; i < args.Length; i++) + { + argTypes[i] = args[i]?.GetType() ?? typeof(object); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Func GetFunc(Type type, object[] args) - { - var argTypes = GetArgTypes(args); - var key = GetConstructorCacheKey(type, argTypes); - if (!cache.TryGetValue(key, out var func)) - { - cache[key] = func = CreateInstanceFunc(type, argTypes); - } + return argTypes; + } - return func; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetConstructorCacheKey(Type type, Type[] args) + { + var hashCode = new HashCode(); + hashCode.Add(type.GetHashCode()); + for (var i = 0; i < args.Length; i++) + { + hashCode.Add(args[i].GetHashCode()); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Type[] GetArgTypes(object[] args) - { - var argTypes = new Type[args.Length]; - for (var i = 0; i < args.Length; i++) - { - argTypes[i] = args[i]?.GetType() ?? typeof(object); - } + return hashCode.ToHashCode(); + } - return argTypes; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Func CreateInstanceFunc(Type type, Type[] argTypes) + { + var parameterExpression = Expression.Parameter(typeof(object[]), "args"); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetConstructorCacheKey(Type type, Type[] args) + Expression body; + if (type.IsValueType) { - var hashCode = new HashCode(); - hashCode.Add(type.GetHashCode()); - for (var i = 0; i < args.Length; i++) + if (argTypes.Length > 0) { - hashCode.Add(args[i].GetHashCode()); + throw GetConstructorNotFoundException(type, argTypes); } - return hashCode.ToHashCode(); + body = Expression.Convert(Expression.Default(type), typeof(object)); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Func CreateInstanceFunc(Type type, Type[] argTypes) + else { - var parameterExpression = Expression.Parameter(typeof(object[]), "args"); + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var constructor = GetConstructor(constructors, type, argTypes); - Expression body; - if (type.IsValueType) + var parameters = constructor.GetParameters(); + var parameterTypes = new Type[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) { - if (argTypes.Length > 0) - { - throw GetConstructorNotFoundException(type, argTypes); - } - - body = Expression.Convert(Expression.Default(type), typeof(object)); + parameterTypes[i] = parameters[i].ParameterType; } - else + + var arguments = new List(); + for (var i = 0; i < parameterTypes.Length; i++) { - var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - var constructor = GetConstructor(constructors, type, argTypes); + var parameterType = parameterTypes[i]; + var arrayIndexExpression = Expression.ArrayIndex(parameterExpression, Expression.Constant(i)); + var convertExpression = Expression.Convert(arrayIndexExpression, parameterType); + arguments.Add(convertExpression); + } - var parameters = constructor.GetParameters(); - var parameterTypes = new Type[parameters.Length]; - for (var i = 0; i < parameters.Length; i++) - { - parameterTypes[i] = parameters[i].ParameterType; - } + body = Expression.New(constructor, arguments); + } - var arguments = new List(); - for (var i = 0; i < parameterTypes.Length; i++) - { - var parameterType = parameterTypes[i]; - var arrayIndexExpression = Expression.ArrayIndex(parameterExpression, Expression.Constant(i)); - var convertExpression = Expression.Convert(arrayIndexExpression, parameterType); - arguments.Add(convertExpression); - } + var lambda = Expression.Lambda>(body, new[] { parameterExpression }); + var func = lambda.Compile(); - body = Expression.New(constructor, arguments); - } + return func; + } - var lambda = Expression.Lambda>(body, new[] { parameterExpression }); - var func = lambda.Compile(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ConstructorInfo GetConstructor(ConstructorInfo[] constructors, Type type, Type[] argTypes) + { + var matchType = MatchType.Exact; + var fuzzyMatches = new List(); + for (var i = 0; i < constructors.Length; i++) + { + var constructor = constructors[i]; + var parameters = constructors[i].GetParameters(); - return func; - } + if (parameters.Length != argTypes.Length) + { + continue; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ConstructorInfo GetConstructor(ConstructorInfo[] constructors, Type type, Type[] argTypes) - { - var matchType = MatchType.Exact; - var fuzzyMatches = new List(); - for (var i = 0; i < constructors.Length; i++) + for (var j = 0; j < parameters.Length && j < argTypes.Length; j++) { - var constructor = constructors[i]; - var parameters = constructors[i].GetParameters(); + var parameterType = parameters[j].ParameterType; + var argType = argTypes[j]; - if (parameters.Length != argTypes.Length) + if (argType == parameterType) { + matchType = MatchType.Exact; continue; } - for (var j = 0; j < parameters.Length && j < argTypes.Length; j++) + if (!parameterType.IsValueType && (parameterType.IsAssignableFrom(argType) || argType == typeof(object))) { - var parameterType = parameters[j].ParameterType; - var argType = argTypes[j]; - - if (argType == parameterType) - { - matchType = MatchType.Exact; - continue; - } - - if (!parameterType.IsValueType && (parameterType.IsAssignableFrom(argType) || argType == typeof(object))) - { - matchType = MatchType.Fuzzy; - continue; - } - - matchType = MatchType.None; - break; - } - - if (matchType == MatchType.Exact) - { - // Only possible to have one exact match. - return constructor; + matchType = MatchType.Fuzzy; + continue; } - if (matchType == MatchType.Fuzzy) - { - fuzzyMatches.Add(constructor); - } + matchType = MatchType.None; + break; } - if (fuzzyMatches.Count == 1) + if (matchType == MatchType.Exact) { - return fuzzyMatches[0]; + // Only possible to have one exact match. + return constructor; } - if (fuzzyMatches.Count > 1) + if (matchType == MatchType.Fuzzy) { - throw new AmbiguousMatchException(); + fuzzyMatches.Add(constructor); } - - throw GetConstructorNotFoundException(type, argTypes); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static MissingMethodException GetConstructorNotFoundException(Type type, Type[] argTypes) + if (fuzzyMatches.Count == 1) { - var signature = $"{type.FullName}({string.Join(", ", argTypes.Select(a => a.FullName))})"; - - throw new MissingMethodException($"Constructor '{signature}' was not found."); + return fuzzyMatches[0]; } - private enum MatchType + if (fuzzyMatches.Count > 1) { - None = 0, - Exact = 1, - Fuzzy = 2 + throw new AmbiguousMatchException(); } + + throw GetConstructorNotFoundException(type, argTypes); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static MissingMethodException GetConstructorNotFoundException(Type type, Type[] argTypes) + { + var signature = $"{type.FullName}({string.Join(", ", argTypes.Select(a => a.FullName))})"; + + throw new MissingMethodException($"Constructor '{signature}' was not found."); + } + + private enum MatchType + { + None = 0, + Exact = 1, + Fuzzy = 2 } } diff --git a/src/CsvHelper/ObjectResolver.cs b/src/CsvHelper/ObjectResolver.cs index cd354ad23..93a618dc5 100644 --- a/src/CsvHelper/ObjectResolver.cs +++ b/src/CsvHelper/ObjectResolver.cs @@ -2,142 +2,139 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Creates objects from a given type. +/// +public class ObjectResolver : IObjectResolver { + private static IObjectResolver current; + private readonly ObjectCreator objectCreator = new ObjectCreator(); + /// - /// Creates objects from a given type. + /// Gets or sets the current resolver. + /// Use an instance of this instead if at all possible. /// - public class ObjectResolver : IObjectResolver + public static IObjectResolver Current { - private static IObjectResolver current; - private readonly ObjectCreator objectCreator = new ObjectCreator(); - - /// - /// Gets or sets the current resolver. - /// Use an instance of this instead if at all possible. - /// - public static IObjectResolver Current + get + { + return current; + } + set { - get + if (value == null) { - return current; + throw new InvalidOperationException($"{nameof(IObjectResolver)} cannot be null."); } - set - { - if (value == null) - { - throw new InvalidOperationException($"{nameof(IObjectResolver)} cannot be null."); - } - current = value; - } + current = value; } + } - /// - /// A value indicating if the resolver's - /// returns false that an object will still be created using - /// CsvHelper's object creation. True to fallback, otherwise false. - /// Default value is true. - /// - public bool UseFallback { get; private set; } - - /// - /// A function that returns a value indicating if the resolver - /// is able to resolve the given type. True if the type can be - /// resolved, otherwise false. - /// - public Func CanResolve { get; private set; } - - /// - /// The function that creates an object from a given type. - /// - public Func ResolveFunction { get; private set; } - - static ObjectResolver() - { - var objectCreator = new ObjectCreator(); - var locker = new object(); - current = new ObjectResolver(type => true, (type, args) => - { - lock (locker) - { - return objectCreator.CreateInstance(type, args); - } - }); - } + /// + /// A value indicating if the resolver's + /// returns false that an object will still be created using + /// CsvHelper's object creation. True to fallback, otherwise false. + /// Default value is true. + /// + public bool UseFallback { get; private set; } - /// - /// Creates an instance of the object resolver using default values. - /// - public ObjectResolver() - { - CanResolve = type => true; - ResolveFunction = ResolveWithObjectCreator; - UseFallback = true; - } + /// + /// A function that returns a value indicating if the resolver + /// is able to resolve the given type. True if the type can be + /// resolved, otherwise false. + /// + public Func CanResolve { get; private set; } - /// - /// Creates an instance of the object resolver using the given can create function - /// and create function. - /// - /// A function that returns a value indicating if the resolver - /// is able to resolve the given type. True if the type can be - /// resolved, otherwise false. - /// The function that creates an object from a given type. - /// A value indicating if the resolver's - /// returns false that an object will still be created using - /// CsvHelper's object creation. True to fallback, otherwise false. - /// Default value is true. - public ObjectResolver(Func canResolve, Func resolveFunction, bool useFallback = true) - { - CanResolve = canResolve ?? throw new ArgumentNullException(nameof(canResolve)); - ResolveFunction = resolveFunction ?? throw new ArgumentNullException(nameof(resolveFunction)); - UseFallback = useFallback; - } + /// + /// The function that creates an object from a given type. + /// + public Func ResolveFunction { get; private set; } - /// - /// Creates an object from the given type using the - /// function. If is false, the object will be - /// created using CsvHelper's default object creation. If - /// is false, an exception is thrown. - /// - /// The type to create an instance from. The created object - /// may not be the same type as the given type. - /// Constructor arguments used to create the type. - public object Resolve(Type type, params object[] constructorArgs) + static ObjectResolver() + { + var objectCreator = new ObjectCreator(); + var locker = new object(); + current = new ObjectResolver(type => true, (type, args) => { - if (CanResolve(type)) + lock (locker) { - return ResolveFunction(type, constructorArgs); + return objectCreator.CreateInstance(type, args); } + }); + } - if (UseFallback) - { - return objectCreator.CreateInstance(type, constructorArgs); - } + /// + /// Creates an instance of the object resolver using default values. + /// + public ObjectResolver() + { + CanResolve = type => true; + ResolveFunction = ResolveWithObjectCreator; + UseFallback = true; + } - throw new CsvHelperException($"Type '{type.FullName}' can't be resolved and fallback is turned off."); - } + /// + /// Creates an instance of the object resolver using the given can create function + /// and create function. + /// + /// A function that returns a value indicating if the resolver + /// is able to resolve the given type. True if the type can be + /// resolved, otherwise false. + /// The function that creates an object from a given type. + /// A value indicating if the resolver's + /// returns false that an object will still be created using + /// CsvHelper's object creation. True to fallback, otherwise false. + /// Default value is true. + public ObjectResolver(Func canResolve, Func resolveFunction, bool useFallback = true) + { + CanResolve = canResolve ?? throw new ArgumentNullException(nameof(canResolve)); + ResolveFunction = resolveFunction ?? throw new ArgumentNullException(nameof(resolveFunction)); + UseFallback = useFallback; + } - /// - /// Creates an object from the given type using the - /// function. If is false, the object will be - /// created using CsvHelper's default object creation. If - /// is false, an exception is thrown. - /// - /// The type to create an instance from. The created object - /// may not be the same type as the given type. - /// Constructor arguments used to create the type. - public T Resolve(params object[] constructorArgs) + /// + /// Creates an object from the given type using the + /// function. If is false, the object will be + /// created using CsvHelper's default object creation. If + /// is false, an exception is thrown. + /// + /// The type to create an instance from. The created object + /// may not be the same type as the given type. + /// Constructor arguments used to create the type. + public object Resolve(Type type, params object[] constructorArgs) + { + if (CanResolve(type)) { - return (T)Resolve(typeof(T), constructorArgs); + return ResolveFunction(type, constructorArgs); } - private object ResolveWithObjectCreator(Type type, params object[] args) + if (UseFallback) { - return objectCreator.CreateInstance(type, args); + return objectCreator.CreateInstance(type, constructorArgs); } + + throw new CsvHelperException($"Type '{type.FullName}' can't be resolved and fallback is turned off."); + } + + /// + /// Creates an object from the given type using the + /// function. If is false, the object will be + /// created using CsvHelper's default object creation. If + /// is false, an exception is thrown. + /// + /// The type to create an instance from. The created object + /// may not be the same type as the given type. + /// Constructor arguments used to create the type. + public T Resolve(params object[] constructorArgs) + { + return (T)Resolve(typeof(T), constructorArgs); + } + + private object ResolveWithObjectCreator(Type type, params object[] args) + { + return objectCreator.CreateInstance(type, args); } } diff --git a/src/CsvHelper/ParserException.cs b/src/CsvHelper/ParserException.cs index 5cc6cf054..3b20a45d6 100644 --- a/src/CsvHelper/ParserException.cs +++ b/src/CsvHelper/ParserException.cs @@ -2,38 +2,35 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents errors that occur while parsing a CSV file. +/// +[Serializable] +public class ParserException : CsvHelperException { /// - /// Represents errors that occur while parsing a CSV file. + /// Initializes a new instance of the class. /// - [Serializable] - public class ParserException : CsvHelperException - { - /// - /// Initializes a new instance of the class. - /// - /// The reading context. - public ParserException(CsvContext context) : base(context) { } + /// The reading context. + public ParserException(CsvContext context) : base(context) { } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The reading context. - /// The message that describes the error. - public ParserException(CsvContext context, string message) : base(context, message) { } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The reading context. + /// The message that describes the error. + public ParserException(CsvContext context, string message) : base(context, message) { } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The reading context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public ParserException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The reading context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public ParserException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } } diff --git a/src/CsvHelper/ReaderException.cs b/src/CsvHelper/ReaderException.cs index 442e62a6d..701214457 100644 --- a/src/CsvHelper/ReaderException.cs +++ b/src/CsvHelper/ReaderException.cs @@ -2,38 +2,35 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents errors that occur while reading a CSV file. +/// +[Serializable] +public class ReaderException : CsvHelperException { /// - /// Represents errors that occur while reading a CSV file. + /// Initializes a new instance of the class. /// - [Serializable] - public class ReaderException : CsvHelperException - { - /// - /// Initializes a new instance of the class. - /// - /// The reading context. - public ReaderException(CsvContext context) : base(context) { } + /// The reading context. + public ReaderException(CsvContext context) : base(context) { } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The reading context. - /// The message that describes the error. - public ReaderException(CsvContext context, string message) : base(context, message) { } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The reading context. + /// The message that describes the error. + public ReaderException(CsvContext context, string message) : base(context, message) { } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The reading context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public ReaderException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The reading context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public ReaderException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } } diff --git a/src/CsvHelper/RecordTypeInfo.cs b/src/CsvHelper/RecordTypeInfo.cs index 98a9d0b44..b2731ac70 100644 --- a/src/CsvHelper/RecordTypeInfo.cs +++ b/src/CsvHelper/RecordTypeInfo.cs @@ -1,40 +1,41 @@ -using System; +// Copyright 2009-2024 Josh Close +// This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. +// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. +// https://github.com/JoshClose/CsvHelper +namespace CsvHelper; -namespace CsvHelper +/// +/// Type information for a record. +/// +public struct RecordTypeInfo { /// - /// Type information for a record. + /// The type of the record. /// - public struct RecordTypeInfo - { - /// - /// The type of the record. - /// - public Type RecordType { get; } + public Type RecordType { get; } - /// - /// A value indicating if the type was an object and GetType() was used over typeof. - /// true if the type is an object, otherwise false. - /// - public bool IsObject { get; } + /// + /// A value indicating if the type was an object and GetType() was used over typeof. + /// true if the type is an object, otherwise false. + /// + public bool IsObject { get; } - /// - /// The hash code for the type. - /// - public int HashCode { get; } + /// + /// The hash code for the type. + /// + public int HashCode { get; } - /// - /// Initializes a new instance using the given and . - /// - /// The type of the record. - /// A value indicating if the type was an object and GetType() was used over typeof. - /// true if the type is an object, otherwise false. - public RecordTypeInfo(Type recordType, bool isObject) - { - RecordType = recordType; - IsObject = isObject; + /// + /// Initializes a new instance using the given and . + /// + /// The type of the record. + /// A value indicating if the type was an object and GetType() was used over typeof. + /// true if the type is an object, otherwise false. + public RecordTypeInfo(Type recordType, bool isObject) + { + RecordType = recordType; + IsObject = isObject; - HashCode = recordType.GetHashCode(); - } + HashCode = recordType.GetHashCode(); } } diff --git a/src/CsvHelper/ReflectionExtensions.cs b/src/CsvHelper/ReflectionExtensions.cs index 60fdfa8ee..622f5c32f 100644 --- a/src/CsvHelper/ReflectionExtensions.cs +++ b/src/CsvHelper/ReflectionExtensions.cs @@ -2,154 +2,151 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Reflection; using System.Linq.Expressions; +using System.Reflection; using System.Runtime.CompilerServices; -using System.Linq; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Extensions to help with reflection. +/// +public static class ReflectionExtensions { /// - /// Extensions to help with reflection. + /// Gets the type from the member. /// - public static class ReflectionExtensions + /// The member to get the type from. + /// The type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Type MemberType(this MemberInfo member) { - /// - /// Gets the type from the member. - /// - /// The member to get the type from. - /// The type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Type MemberType(this MemberInfo member) + var property = member as PropertyInfo; + if (property != null) { - var property = member as PropertyInfo; - if (property != null) - { - return property.PropertyType; - } - - var field = member as FieldInfo; - if (field != null) - { - return field.FieldType; - } - - throw new InvalidOperationException("Member is not a property or a field."); + return property.PropertyType; } - /// - /// Gets a member expression for the member. - /// - /// The member to get the expression for. - /// The member expression. - /// The member expression. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemberExpression GetMemberExpression(this MemberInfo member, Expression expression) + var field = member as FieldInfo; + if (field != null) { - var property = member as PropertyInfo; - if (property != null) - { - return Expression.Property(expression, property); - } - - var field = member as FieldInfo; - if (field != null) - { - return Expression.Field(expression, field); - } - - throw new InvalidOperationException("Member is not a property or a field."); + return field.FieldType; } - /// - /// Gets a value indicating if the given type is anonymous. - /// True for anonymous, otherwise false. - /// - /// The type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsAnonymous(this Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - // https://stackoverflow.com/a/2483054/68499 - var isAnonymous = Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) - && type.IsGenericType - && type.Name.Contains("AnonymousType") - && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) - && (type.Attributes & TypeAttributes.Public) != TypeAttributes.Public; - - return isAnonymous; - } + throw new InvalidOperationException("Member is not a property or a field."); + } - /// - /// Gets a value indicating if the given type has a parameterless constructor. - /// True if it has a parameterless constructor, otherwise false. - /// - /// The type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasParameterlessConstructor(this Type type) + /// + /// Gets a member expression for the member. + /// + /// The member to get the expression for. + /// The member expression. + /// The member expression. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemberExpression GetMemberExpression(this MemberInfo member, Expression expression) + { + var property = member as PropertyInfo; + if (property != null) { - return type.GetConstructor(new Type[0]) != null; + return Expression.Property(expression, property); } - /// - /// Gets a value indicating if the given type has any constructors. - /// - /// The type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasConstructor(this Type type) + var field = member as FieldInfo; + if (field != null) { - return type.GetConstructors().Length > 0; + return Expression.Field(expression, field); } - /// - /// Gets the constructor that contains the most parameters. - /// - /// The type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ConstructorInfo GetConstructorWithMostParameters(this Type type) - { - return type.GetConstructors() - .OrderByDescending(c => c.GetParameters().Length) - .First(); - } + throw new InvalidOperationException("Member is not a property or a field."); + } - /// - /// Gets a value indicating if the type is a user defined struct. - /// True if it is a user defined struct, otherwise false. - /// - /// The type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsUserDefinedStruct(this Type type) + /// + /// Gets a value indicating if the given type is anonymous. + /// True for anonymous, otherwise false. + /// + /// The type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAnonymous(this Type type) + { + if (type == null) { - return type.IsValueType && !type.IsPrimitive && !type.IsEnum; + throw new ArgumentNullException(nameof(type)); } - /// - /// Gets a string representation of the constructor. - /// - /// The constructor. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetDefinition(this ConstructorInfo constructor) - { - var parameters = constructor.GetParameters(); - var definition = $"{constructor.Name}({string.Join(", ", parameters.Select(p => p.GetDefinition()))})"; + // https://stackoverflow.com/a/2483054/68499 + var isAnonymous = Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) + && type.IsGenericType + && type.Name.Contains("AnonymousType") + && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) + && (type.Attributes & TypeAttributes.Public) != TypeAttributes.Public; - return definition; - } + return isAnonymous; + } - /// - /// Gets a string representation of the parameter. - /// - /// The parameter. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetDefinition(this ParameterInfo parameter) - { - return $"{parameter.ParameterType.Name} {parameter.Name}"; - } + /// + /// Gets a value indicating if the given type has a parameterless constructor. + /// True if it has a parameterless constructor, otherwise false. + /// + /// The type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasParameterlessConstructor(this Type type) + { + return type.GetConstructor(new Type[0]) != null; + } + + /// + /// Gets a value indicating if the given type has any constructors. + /// + /// The type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasConstructor(this Type type) + { + return type.GetConstructors().Length > 0; + } + + /// + /// Gets the constructor that contains the most parameters. + /// + /// The type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConstructorInfo GetConstructorWithMostParameters(this Type type) + { + return type.GetConstructors() + .OrderByDescending(c => c.GetParameters().Length) + .First(); + } + + /// + /// Gets a value indicating if the type is a user defined struct. + /// True if it is a user defined struct, otherwise false. + /// + /// The type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsUserDefinedStruct(this Type type) + { + return type.IsValueType && !type.IsPrimitive && !type.IsEnum; + } + + /// + /// Gets a string representation of the constructor. + /// + /// The constructor. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDefinition(this ConstructorInfo constructor) + { + var parameters = constructor.GetParameters(); + var definition = $"{constructor.Name}({string.Join(", ", parameters.Select(p => p.GetDefinition()))})"; + + return definition; + } + + /// + /// Gets a string representation of the parameter. + /// + /// The parameter. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDefinition(this ParameterInfo parameter) + { + return $"{parameter.ParameterType.Name} {parameter.Name}"; } } diff --git a/src/CsvHelper/ReflectionHelper.cs b/src/CsvHelper/ReflectionHelper.cs index 9181d969c..3dfe87e38 100644 --- a/src/CsvHelper/ReflectionHelper.cs +++ b/src/CsvHelper/ReflectionHelper.cs @@ -2,203 +2,199 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; +using CsvHelper.Configuration; +using CsvHelper.Configuration.Attributes; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -using CsvHelper.Configuration; -using CsvHelper.Configuration.Attributes; -namespace CsvHelper +namespace CsvHelper; + +/// +/// Common reflection tasks. +/// +internal static class ReflectionHelper { /// - /// Common reflection tasks. + /// Gets the from the type where the property was declared. /// - internal static class ReflectionHelper + /// The type the property belongs to. + /// The property to search. + /// Flags for how the property is retrieved. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyInfo GetDeclaringProperty(Type type, PropertyInfo property, BindingFlags flags) { - /// - /// Gets the from the type where the property was declared. - /// - /// The type the property belongs to. - /// The property to search. - /// Flags for how the property is retrieved. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PropertyInfo GetDeclaringProperty(Type type, PropertyInfo property, BindingFlags flags) + if (property.DeclaringType != type) { - if (property.DeclaringType != type) - { - var declaringProperty = property.DeclaringType.GetProperty(property.Name, flags); - return GetDeclaringProperty(property.DeclaringType, declaringProperty, flags); - } - - return property; + var declaringProperty = property.DeclaringType!.GetProperty(property.Name, flags)!; + return GetDeclaringProperty(property.DeclaringType, declaringProperty, flags); } - /// - /// Gets the from the type where the field was declared. - /// - /// The type the field belongs to. - /// The field to search. - /// Flags for how the field is retrieved. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static FieldInfo GetDeclaringField(Type type, FieldInfo field, BindingFlags flags) - { - if (field.DeclaringType != type) - { - var declaringField = field.DeclaringType.GetField(field.Name, flags); - return GetDeclaringField(field.DeclaringType, declaringField, flags); - } + return property; + } - return field; + /// + /// Gets the from the type where the field was declared. + /// + /// The type the field belongs to. + /// The field to search. + /// Flags for how the field is retrieved. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FieldInfo GetDeclaringField(Type type, FieldInfo field, BindingFlags flags) + { + if (field.DeclaringType != type) + { + var declaringField = field.DeclaringType!.GetField(field.Name, flags)!; + return GetDeclaringField(field.DeclaringType, declaringField, flags); } - /// - /// Walk up the inheritance tree collecting properties. This will get a unique set of properties in the - /// case where parents have the same property names as children. - /// - /// The to get properties for. - /// The flags for getting the properties. - /// If true, parent class properties that are hidden by `new` child properties will be overwritten. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List GetUniqueProperties(Type type, BindingFlags flags, bool overwrite = false) - { - var ignoreBase = type.GetCustomAttribute(typeof(IgnoreBaseAttribute)) != null; + return field; + } - var properties = new Dictionary(); + /// + /// Walk up the inheritance tree collecting properties. This will get a unique set of properties in the + /// case where parents have the same property names as children. + /// + /// The to get properties for. + /// The flags for getting the properties. + /// If true, parent class properties that are hidden by `new` child properties will be overwritten. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List GetUniqueProperties(Type type, BindingFlags flags, bool overwrite = false) + { + var ignoreBase = type.GetCustomAttribute(typeof(IgnoreBaseAttribute)) != null; - flags |= BindingFlags.DeclaredOnly; - var currentType = type; - while (currentType != null) - { - var currentProperties = currentType.GetProperties(flags); - foreach (var property in currentProperties) - { - if (!properties.ContainsKey(property.Name) || overwrite) - { - properties[property.Name] = property; - } - } + var properties = new Dictionary(); - if (ignoreBase) + flags |= BindingFlags.DeclaredOnly; + var currentType = type; + while (currentType != null) + { + var currentProperties = currentType.GetProperties(flags); + foreach (var property in currentProperties) + { + if (!properties.ContainsKey(property.Name) || overwrite) { - break; + properties[property.Name] = property; } + } - currentType = currentType.BaseType; + if (ignoreBase) + { + break; } - return properties.Values.ToList(); + currentType = currentType.BaseType; } - /// - /// Walk up the inheritance tree collecting fields. This will get a unique set of fields in the - /// case where parents have the same field names as children. - /// - /// The to get fields for. - /// The flags for getting the fields. - /// If true, parent class fields that are hidden by `new` child fields will be overwritten. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List GetUniqueFields(Type type, BindingFlags flags, bool overwrite = false) - { - var ignoreBase = type.GetCustomAttribute(typeof(IgnoreBaseAttribute)) != null; - - var fields = new Dictionary(); - - flags |= BindingFlags.DeclaredOnly; - var currentType = type; - while (currentType != null) - { - var currentFields = currentType.GetFields(flags); - foreach (var field in currentFields) - { - if (!fields.ContainsKey(field.Name) || overwrite) - { - fields[field.Name] = field; - } - } - - if (ignoreBase) - { - break; - } + return properties.Values.ToList(); + } - currentType = currentType.BaseType; - } + /// + /// Walk up the inheritance tree collecting fields. This will get a unique set of fields in the + /// case where parents have the same field names as children. + /// + /// The to get fields for. + /// The flags for getting the fields. + /// If true, parent class fields that are hidden by `new` child fields will be overwritten. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List GetUniqueFields(Type type, BindingFlags flags, bool overwrite = false) + { + var ignoreBase = type.GetCustomAttribute(typeof(IgnoreBaseAttribute)) != null; - return fields.Values.ToList(); - } + var fields = new Dictionary(); - /// - /// Gets the property from the expression. - /// - /// The type of the model. - /// The type of the property. - /// The expression. - /// The for the expression. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemberInfo GetMember(Expression> expression) + flags |= BindingFlags.DeclaredOnly; + var currentType = type; + while (currentType != null) { - var member = GetMemberExpression(expression.Body).Member; - var property = member as PropertyInfo; - if (property != null) + var currentFields = currentType.GetFields(flags); + foreach (var field in currentFields) { - return property; + if (!fields.ContainsKey(field.Name) || overwrite) + { + fields[field.Name] = field; + } } - var field = member as FieldInfo; - if (field != null) + if (ignoreBase) { - return field; + break; } - throw new ConfigurationException($"'{member.Name}' is not a member."); + currentType = currentType.BaseType; } - /// - /// Gets the member inheritance chain as a stack. - /// - /// The type of the model. - /// The type of the property. - /// The member expression. - /// The inheritance chain for the given member expression as a stack. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Stack GetMembers(Expression> expression) + return fields.Values.ToList(); + } + + /// + /// Gets the property from the expression. + /// + /// The type of the model. + /// The type of the property. + /// The expression. + /// The for the expression. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemberInfo GetMember(Expression> expression) + { + var member = GetMemberExpression(expression.Body)?.Member; + var property = member as PropertyInfo; + if (property != null) { - var stack = new Stack(); + return property; + } - var currentExpression = expression.Body; - while (true) - { - var memberExpression = GetMemberExpression(currentExpression); - if (memberExpression == null) - { - break; - } + var field = member as FieldInfo; + if (field != null) + { + return field; + } - stack.Push(memberExpression.Member); - currentExpression = memberExpression.Expression; - } + throw new ConfigurationException($"'{member?.Name}' is not a member."); + } - return stack; - } + /// + /// Gets the member inheritance chain as a stack. + /// + /// The type of the model. + /// The type of the property. + /// The member expression. + /// The inheritance chain for the given member expression as a stack. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Stack GetMembers(Expression> expression) + { + var stack = new Stack(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static MemberExpression GetMemberExpression(Expression expression) + var currentExpression = expression.Body!; + while (true) { - MemberExpression memberExpression = null; - if (expression.NodeType == ExpressionType.Convert) - { - var body = (UnaryExpression)expression; - memberExpression = body.Operand as MemberExpression; - } - else if (expression.NodeType == ExpressionType.MemberAccess) + var memberExpression = GetMemberExpression(currentExpression); + if (memberExpression == null) { - memberExpression = expression as MemberExpression; + break; } - return memberExpression; + stack.Push(memberExpression.Member); + currentExpression = memberExpression.Expression!; } + + return stack; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static MemberExpression? GetMemberExpression(Expression expression) + { + MemberExpression? memberExpression = null; + if (expression.NodeType == ExpressionType.Convert) + { + var body = (UnaryExpression)expression; + memberExpression = body.Operand as MemberExpression; + } + else if (expression.NodeType == ExpressionType.MemberAccess) + { + memberExpression = expression as MemberExpression; + } + + return memberExpression; } } diff --git a/src/CsvHelper/TypeConversion/ArrayConverter.cs b/src/CsvHelper/TypeConversion/ArrayConverter.cs index 349b5df3e..ad2f9baef 100644 --- a/src/CsvHelper/TypeConversion/ArrayConverter.cs +++ b/src/CsvHelper/TypeConversion/ArrayConverter.cs @@ -2,72 +2,68 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; using CsvHelper.Configuration; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts an to and from a . +/// +public class ArrayConverter : IEnumerableConverter { /// - /// Converts an to and from a . + /// Converts the string to an object. /// - public class ArrayConverter : IEnumerableConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - Array array; - var type = memberMapData.Member.MemberType().GetElementType(); - var converter = row.Context.TypeConverterCache.GetConverter(type); + Array array; + var type = memberMapData.Member!.MemberType().GetElementType()!; + var converter = row.Context.TypeConverterCache.GetConverter(type); - if (memberMapData.IsNameSet || row.Configuration.HasHeaderRecord && !memberMapData.IsIndexSet) + if (memberMapData.IsNameSet || row.Configuration.HasHeaderRecord && !memberMapData.IsIndexSet) + { + // Use the name. + var list = new List(); + var nameIndex = 0; + while (true) { - // Use the name. - var list = new List(); - var nameIndex = 0; - while (true) + if (!row.TryGetField(type, memberMapData.Names.FirstOrDefault() ?? string.Empty, nameIndex, out var field)) { - if (!row.TryGetField(type, memberMapData.Names.FirstOrDefault(), nameIndex, out var field)) - { - break; - } - - list.Add(field); - nameIndex++; + break; } - array = (Array)ObjectResolver.Current.Resolve(memberMapData.Member.MemberType(), list.Count); - for (var i = 0; i < list.Count; i++) - { - array.SetValue(list[i], i); - } + list.Add(field); + nameIndex++; } - else - { - // Use the index. - var indexEnd = memberMapData.IndexEnd < memberMapData.Index - ? row.Parser.Count - 1 - : memberMapData.IndexEnd; - var arraySize = indexEnd - memberMapData.Index + 1; - array = (Array)ObjectResolver.Current.Resolve(memberMapData.Member.MemberType(), arraySize); - var arrayIndex = 0; - for (var i = memberMapData.Index; i <= indexEnd; i++) - { - var field = converter.ConvertFromString(row.GetField(i), row, memberMapData); - array.SetValue(field, arrayIndex); - arrayIndex++; - } + array = (Array)ObjectResolver.Current.Resolve(memberMapData.Member!.MemberType(), list.Count); + for (var i = 0; i < list.Count; i++) + { + array.SetValue(list[i], i); } + } + else + { + // Use the index. + var indexEnd = memberMapData.IndexEnd < memberMapData.Index + ? row.Parser.Count - 1 + : memberMapData.IndexEnd; - return array; + var arraySize = indexEnd - memberMapData.Index + 1; + array = (Array)ObjectResolver.Current.Resolve(memberMapData.Member!.MemberType(), arraySize); + var arrayIndex = 0; + for (var i = memberMapData.Index; i <= indexEnd; i++) + { + var field = converter.ConvertFromString(row.GetField(i), row, memberMapData); + array.SetValue(field, arrayIndex); + arrayIndex++; + } } + + return array; } } diff --git a/src/CsvHelper/TypeConversion/BigIntegerConverter.cs b/src/CsvHelper/TypeConversion/BigIntegerConverter.cs index 857d9f60b..e163f4180 100644 --- a/src/CsvHelper/TypeConversion/BigIntegerConverter.cs +++ b/src/CsvHelper/TypeConversion/BigIntegerConverter.cs @@ -3,55 +3,49 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Numerics; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class BigIntegerConverter : DefaultTypeConverter { - /// - /// Converts a to and from a . - /// - public class BigIntegerConverter : DefaultTypeConverter - { - /// - /// Converts the object to a string. - /// - /// The object to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the object. - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) - { - if (value is BigInteger bi && memberMapData.TypeConverterOptions.Formats?.FirstOrDefault() == null) - { - return bi.ToString("R", memberMapData.TypeConverterOptions.CultureInfo); - } + /// + /// Converts the object to a string. + /// + /// The object to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the object. + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) + { + if (value is BigInteger bi && memberMapData.TypeConverterOptions.Formats?.FirstOrDefault() == null) + { + return bi.ToString("R", memberMapData.TypeConverterOptions.CultureInfo); + } - return base.ConvertToString(value, row, memberMapData); - } + return base.ConvertToString(value, row, memberMapData); + } - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + /// + /// Converts the string to an object. + /// + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (BigInteger.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var bi)) - { - return bi; - } + if (BigInteger.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var bi)) + { + return bi; + } - return base.ConvertFromString(text, row, memberMapData); - } - } + return base.ConvertFromString(text, row, memberMapData); + } } diff --git a/src/CsvHelper/TypeConversion/BooleanConverter.cs b/src/CsvHelper/TypeConversion/BooleanConverter.cs index f5c07b1b4..e11e1f5fa 100644 --- a/src/CsvHelper/TypeConversion/BooleanConverter.cs +++ b/src/CsvHelper/TypeConversion/BooleanConverter.cs @@ -2,71 +2,69 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; -using System.Linq; using CsvHelper.Configuration; +using System.Globalization; + +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Converts a to and from a . +/// +public class BooleanConverter : DefaultTypeConverter { - /// - /// Converts a to and from a . - /// - public class BooleanConverter : DefaultTypeConverter + /// + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + if (bool.TryParse(text, out var b)) { - if (bool.TryParse(text, out var b)) - { - return b; - } + return b; + } - if (short.TryParse(text, out var sh)) + if (short.TryParse(text, out var sh)) + { + if (sh == 0) { - if (sh == 0) - { - return false; - } - if (sh == 1) - { - return true; - } + return false; } - - var t = (text ?? string.Empty).Trim(); - foreach (var trueValue in memberMapData.TypeConverterOptions.BooleanTrueValues) + if (sh == 1) { - if (memberMapData.TypeConverterOptions.CultureInfo.CompareInfo.Compare(trueValue, t, CompareOptions.IgnoreCase) == 0) - { - return true; - } + return true; } + } - foreach (var falseValue in memberMapData.TypeConverterOptions.BooleanFalseValues) + var t = (text ?? string.Empty).Trim(); + foreach (var trueValue in memberMapData.TypeConverterOptions.BooleanTrueValues) + { + if (memberMapData.TypeConverterOptions.CultureInfo!.CompareInfo.Compare(trueValue, t, CompareOptions.IgnoreCase) == 0) { - if (memberMapData.TypeConverterOptions.CultureInfo.CompareInfo.Compare(falseValue, t, CompareOptions.IgnoreCase) == 0) - { - return false; - } + return true; } - - return base.ConvertFromString(text, row, memberMapData); } - /// - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) + foreach (var falseValue in memberMapData.TypeConverterOptions.BooleanFalseValues) { - var b = value as bool?; - if (b == true && memberMapData.TypeConverterOptions.BooleanTrueValues.Count > 0) + if (memberMapData.TypeConverterOptions.CultureInfo!.CompareInfo.Compare(falseValue, t, CompareOptions.IgnoreCase) == 0) { - return memberMapData.TypeConverterOptions.BooleanTrueValues.First(); - } - else if (b == false && memberMapData.TypeConverterOptions.BooleanFalseValues.Count > 0) - { - return memberMapData.TypeConverterOptions.BooleanFalseValues.First(); + return false; } + } + + return base.ConvertFromString(text, row, memberMapData); + } - return base.ConvertToString(value, row, memberMapData); + /// + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) + { + var b = value as bool?; + if (b == true && memberMapData.TypeConverterOptions.BooleanTrueValues.Count > 0) + { + return memberMapData.TypeConverterOptions.BooleanTrueValues.First(); } + else if (b == false && memberMapData.TypeConverterOptions.BooleanFalseValues.Count > 0) + { + return memberMapData.TypeConverterOptions.BooleanFalseValues.First(); + } + + return base.ConvertToString(value, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/ByteArrayConverter.cs b/src/CsvHelper/TypeConversion/ByteArrayConverter.cs index 0d82092d9..9878c0f7f 100644 --- a/src/CsvHelper/TypeConversion/ByteArrayConverter.cs +++ b/src/CsvHelper/TypeConversion/ByteArrayConverter.cs @@ -2,124 +2,122 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Text; using CsvHelper.Configuration; +using System.Text; + +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Converts a to and from a . +/// +public class ByteArrayConverter : DefaultTypeConverter { + private readonly ByteArrayConverterOptions options; + private readonly string HexStringPrefix; + private readonly byte ByteLength; + /// - /// Converts a to and from a . + /// Creates a new ByteArrayConverter using the given . /// - public class ByteArrayConverter : DefaultTypeConverter + /// The options. + public ByteArrayConverter(ByteArrayConverterOptions options = ByteArrayConverterOptions.Hexadecimal | ByteArrayConverterOptions.HexInclude0x) { - private readonly ByteArrayConverterOptions options; - private readonly string HexStringPrefix; - private readonly byte ByteLength; - - /// - /// Creates a new ByteArrayConverter using the given . - /// - /// The options. - public ByteArrayConverter(ByteArrayConverterOptions options = ByteArrayConverterOptions.Hexadecimal | ByteArrayConverterOptions.HexInclude0x) - { - // Defaults to the literal format used by C# for whole numbers, and SQL Server for binary data. - this.options = options; - ValidateOptions(); + // Defaults to the literal format used by C# for whole numbers, and SQL Server for binary data. + this.options = options; + ValidateOptions(); - HexStringPrefix = (options & ByteArrayConverterOptions.HexDashes) == ByteArrayConverterOptions.HexDashes ? "-" : string.Empty; - ByteLength = (options & ByteArrayConverterOptions.HexDashes) == ByteArrayConverterOptions.HexDashes ? (byte)3 : (byte)2; - } + HexStringPrefix = (options & ByteArrayConverterOptions.HexDashes) == ByteArrayConverterOptions.HexDashes ? "-" : string.Empty; + ByteLength = (options & ByteArrayConverterOptions.HexDashes) == ByteArrayConverterOptions.HexDashes ? (byte)3 : (byte)2; + } - /// - /// Converts the object to a string. - /// - /// The object to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the object. - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) + /// + /// Converts the object to a string. + /// + /// The object to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the object. + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) + { + if (value is byte[] byteArray) { - if (value is byte[] byteArray) - { - return (options & ByteArrayConverterOptions.Base64) == ByteArrayConverterOptions.Base64 - ? Convert.ToBase64String(byteArray) - : ByteArrayToHexString(byteArray); - } - - return base.ConvertToString(value, row, memberMapData); + return (options & ByteArrayConverterOptions.Base64) == ByteArrayConverterOptions.Base64 + ? Convert.ToBase64String(byteArray) + : ByteArrayToHexString(byteArray); } - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - if (text != null) - { - return (options & ByteArrayConverterOptions.Base64) == ByteArrayConverterOptions.Base64 - ? Convert.FromBase64String(text) - : HexStringToByteArray(text); - } - - return base.ConvertFromString(text, row, memberMapData); - } + return base.ConvertToString(value, row, memberMapData); + } - private string ByteArrayToHexString(byte[] byteArray) + /// + /// Converts the string to an object. + /// + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + if (text != null) { - var hexString = new StringBuilder(); + return (options & ByteArrayConverterOptions.Base64) == ByteArrayConverterOptions.Base64 + ? Convert.FromBase64String(text) + : HexStringToByteArray(text); + } - if ((options & ByteArrayConverterOptions.HexInclude0x) == ByteArrayConverterOptions.HexInclude0x) - { - hexString.Append("0x"); - } + return base.ConvertFromString(text, row, memberMapData); + } - if (byteArray.Length >= 1) - { - hexString.Append(byteArray[0].ToString("X2")); - } + private string ByteArrayToHexString(byte[] byteArray) + { + var hexString = new StringBuilder(); - for (var i = 1; i < byteArray.Length; i++) - { - hexString.Append(HexStringPrefix + byteArray[i].ToString("X2")); - } + if ((options & ByteArrayConverterOptions.HexInclude0x) == ByteArrayConverterOptions.HexInclude0x) + { + hexString.Append("0x"); + } - return hexString.ToString(); + if (byteArray.Length >= 1) + { + hexString.Append(byteArray[0].ToString("X2")); } - private byte[] HexStringToByteArray(string hex) + for (var i = 1; i < byteArray.Length; i++) { - var has0x = hex.StartsWith("0x"); + hexString.Append(HexStringPrefix + byteArray[i].ToString("X2")); + } - var length = has0x - ? (hex.Length - 1) / ByteLength - : hex.Length + 1 / ByteLength; - var byteArray = new byte[length]; - var has0xOffset = has0x ? 1 : 0; + return hexString.ToString(); + } - for (var stringIndex = has0xOffset * 2; stringIndex < hex.Length; stringIndex += ByteLength) - { - byteArray[(stringIndex - has0xOffset) / ByteLength] = Convert.ToByte(hex.Substring(stringIndex, 2), 16); - } + private byte[] HexStringToByteArray(string hex) + { + var has0x = hex.StartsWith("0x"); - return byteArray; + var length = has0x + ? (hex.Length - 1) / ByteLength + : hex.Length + 1 / ByteLength; + var byteArray = new byte[length]; + var has0xOffset = has0x ? 1 : 0; + + for (var stringIndex = has0xOffset * 2; stringIndex < hex.Length; stringIndex += ByteLength) + { + byteArray[(stringIndex - has0xOffset) / ByteLength] = Convert.ToByte(hex.Substring(stringIndex, 2), 16); } - private void ValidateOptions() + return byteArray; + } + + private void ValidateOptions() + { + if ((options & ByteArrayConverterOptions.Base64) == ByteArrayConverterOptions.Base64) { - if ((options & ByteArrayConverterOptions.Base64) == ByteArrayConverterOptions.Base64) + if ((options & (ByteArrayConverterOptions.HexInclude0x | ByteArrayConverterOptions.HexDashes | ByteArrayConverterOptions.Hexadecimal)) != ByteArrayConverterOptions.None) { - if ((options & (ByteArrayConverterOptions.HexInclude0x | ByteArrayConverterOptions.HexDashes | ByteArrayConverterOptions.Hexadecimal)) != ByteArrayConverterOptions.None) + throw new ConfigurationException($"{nameof(ByteArrayConverter)} must be configured exclusively with HexDecimal options, or exclusively with Base64 options. Was {options.ToString()}") { - throw new ConfigurationException($"{nameof(ByteArrayConverter)} must be configured exclusively with HexDecimal options, or exclusively with Base64 options. Was {options.ToString()}") - { - Data = { { "options", options } } - }; - } + Data = { { "options", options } } + }; } } } diff --git a/src/CsvHelper/TypeConversion/ByteArrayConverterOptions.cs b/src/CsvHelper/TypeConversion/ByteArrayConverterOptions.cs index 20753aa4f..2523d11ea 100644 --- a/src/CsvHelper/TypeConversion/ByteArrayConverterOptions.cs +++ b/src/CsvHelper/TypeConversion/ByteArrayConverterOptions.cs @@ -2,43 +2,40 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Options for converting byte arrays. +/// +[Flags] +public enum ByteArrayConverterOptions { /// - /// Options for converting byte arrays. + /// No options. /// - [Flags] - public enum ByteArrayConverterOptions - { - /// - /// No options. - /// - None = 0, + None = 0, - // TypeOptions + // TypeOptions - /// - /// Hexadecimal encoding. - /// - Hexadecimal = 1, + /// + /// Hexadecimal encoding. + /// + Hexadecimal = 1, - /// - /// Base64 encoding. - /// - Base64 = 2, + /// + /// Base64 encoding. + /// + Base64 = 2, - // HexFormattingOptions + // HexFormattingOptions - /// - /// Use dashes in between hex values. - /// - HexDashes = 4, + /// + /// Use dashes in between hex values. + /// + HexDashes = 4, - /// - /// Prefix hex number with 0x. - /// - HexInclude0x = 8, - } + /// + /// Prefix hex number with 0x. + /// + HexInclude0x = 8, } diff --git a/src/CsvHelper/TypeConversion/ByteConverter.cs b/src/CsvHelper/TypeConversion/ByteConverter.cs index 4a784720d..b4b3099b4 100644 --- a/src/CsvHelper/TypeConversion/ByteConverter.cs +++ b/src/CsvHelper/TypeConversion/ByteConverter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class ByteConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class ByteConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (byte.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var b)) - { - return b; - } - - return base.ConvertFromString(text, row, memberMapData); + if (byte.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var b)) + { + return b; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/CharConverter.cs b/src/CsvHelper/TypeConversion/CharConverter.cs index 74314352f..9767614dc 100644 --- a/src/CsvHelper/TypeConversion/CharConverter.cs +++ b/src/CsvHelper/TypeConversion/CharConverter.cs @@ -4,33 +4,32 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class CharConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class CharConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + if (text != null && text.Length > 1) { - if (text != null && text.Length > 1) - { - text = text.Trim(); - } - - if (char.TryParse(text, out var c)) - { - return c; - } + text = text.Trim(); + } - return base.ConvertFromString(text, row, memberMapData); + if (char.TryParse(text, out var c)) + { + return c; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/CollectionConverterFactory.cs b/src/CsvHelper/TypeConversion/CollectionConverterFactory.cs index 6b51ea568..8a4412eb3 100644 --- a/src/CsvHelper/TypeConversion/CollectionConverterFactory.cs +++ b/src/CsvHelper/TypeConversion/CollectionConverterFactory.cs @@ -2,174 +2,171 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using System.Collections; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reflection; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +public class CollectionConverterFactory : ITypeConverterFactory { + private int dictionaryTypeHashCode = typeof(IDictionary).GetHashCode(); + private List enumerableTypeHashCodes = new List + { + typeof(IList).GetHashCode(), + typeof(ICollection).GetHashCode(), + typeof(IEnumerable).GetHashCode(), + }; + /// - public class CollectionConverterFactory : ITypeConverterFactory + public bool CanCreate(Type type) { - private int dictionaryTypeHashCode = typeof(IDictionary).GetHashCode(); - private List enumerableTypeHashCodes = new List + switch (type) { - typeof(IList).GetHashCode(), - typeof(ICollection).GetHashCode(), - typeof(IEnumerable).GetHashCode(), - }; + case IList: + case IDictionary: + case ICollection: + case IEnumerable: + return true; + } - /// - public bool CanCreate(Type type) + if (type.IsArray) { - switch (type) - { - case IList: - case IDictionary: - case ICollection: - case IEnumerable: - return true; - } + // ArrayConverter + return true; + } - if (type.IsArray) - { - // ArrayConverter - return true; - } + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + // IDictionaryGenericConverter + return true; + } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) - { - // IDictionaryGenericConverter - return true; - } + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + { + // IDictionaryGenericConverter + return true; + } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>)) - { - // IDictionaryGenericConverter - return true; - } + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) + { + // CollectionGenericConverter + return true; + } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) - { - // CollectionGenericConverter - return true; - } + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Collection<>)) + { + // CollectionGenericConverter + return true; + } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Collection<>)) - { - // CollectionGenericConverter - return true; - } + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>)) + { + // IEnumerableGenericConverter + return true; + } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>)) - { - // IEnumerableGenericConverter - return true; - } + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(ICollection<>)) + { + // IEnumerableGenericConverter + return true; + } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(ICollection<>)) - { - // IEnumerableGenericConverter - return true; - } + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + // IEnumerableGenericConverter + return true; + } + + // A specific IEnumerable converter doesn't exist. + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + // EnumerableConverter + return true; + } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + return false; + } + + /// + public bool Create(Type type, TypeConverterCache cache, out ITypeConverter typeConverter) + { + var typeHashCode = type.GetHashCode(); + + if (typeHashCode == dictionaryTypeHashCode) + { + typeConverter = new IDictionaryConverter(); + return true; + } + + if (enumerableTypeHashCodes.Contains(typeHashCode)) + { + typeConverter = new IEnumerableConverter(); + return true; + } + + if (type.IsArray) + { + typeConverter = new ArrayConverter(); + return true; + } + + var isGenericType = type.GetTypeInfo().IsGenericType; + if (isGenericType) + { + var genericTypeDefinition = type.GetGenericTypeDefinition(); + + if (genericTypeDefinition == typeof(Dictionary<,>)) { - // IEnumerableGenericConverter + typeConverter = new IDictionaryGenericConverter(); return true; } - // A specific IEnumerable converter doesn't exist. - if (typeof(IEnumerable).IsAssignableFrom(type)) + if (genericTypeDefinition == typeof(IDictionary<,>)) { - // EnumerableConverter + typeConverter = new IDictionaryGenericConverter(); return true; } - return false; - } - - /// - public bool Create(Type type, TypeConverterCache cache, out ITypeConverter typeConverter) - { - var typeHashCode = type.GetHashCode(); - - if (typeHashCode == dictionaryTypeHashCode) + if (genericTypeDefinition == typeof(List<>)) { - typeConverter = new IDictionaryConverter(); + typeConverter = new CollectionGenericConverter(); return true; } - if (enumerableTypeHashCodes.Contains(typeHashCode)) + if (genericTypeDefinition == typeof(Collection<>)) { - typeConverter = new IEnumerableConverter(); + typeConverter = new CollectionGenericConverter(); return true; } - if (type.IsArray) + if (genericTypeDefinition == typeof(IList<>)) { - typeConverter = new ArrayConverter(); + typeConverter = new IEnumerableGenericConverter(); return true; } - var isGenericType = type.GetTypeInfo().IsGenericType; - if (isGenericType) + if (genericTypeDefinition == typeof(ICollection<>)) { - var genericTypeDefinition = type.GetGenericTypeDefinition(); - - if (genericTypeDefinition == typeof(Dictionary<,>)) - { - typeConverter = new IDictionaryGenericConverter(); - return true; - } - - if (genericTypeDefinition == typeof(IDictionary<,>)) - { - typeConverter = new IDictionaryGenericConverter(); - return true; - } - - if (genericTypeDefinition == typeof(List<>)) - { - typeConverter = new CollectionGenericConverter(); - return true; - } - - if (genericTypeDefinition == typeof(Collection<>)) - { - typeConverter = new CollectionGenericConverter(); - return true; - } - - if (genericTypeDefinition == typeof(IList<>)) - { - typeConverter = new IEnumerableGenericConverter(); - return true; - } - - if (genericTypeDefinition == typeof(ICollection<>)) - { - typeConverter = new IEnumerableGenericConverter(); - return true; - } - - if (genericTypeDefinition == typeof(IEnumerable<>)) - { - typeConverter = new IEnumerableGenericConverter(); - return true; - } + typeConverter = new IEnumerableGenericConverter(); + return true; } - // A specific IEnumerable converter doesn't exist. - if (typeof(IEnumerable).IsAssignableFrom(type)) + if (genericTypeDefinition == typeof(IEnumerable<>)) { - typeConverter = new EnumerableConverter(); + typeConverter = new IEnumerableGenericConverter(); return true; } + } - throw new InvalidOperationException($"Cannot create collection converter for type '{type.FullName}'."); + // A specific IEnumerable converter doesn't exist. + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + typeConverter = new EnumerableConverter(); + return true; } + + throw new InvalidOperationException($"Cannot create collection converter for type '{type.FullName}'."); } } diff --git a/src/CsvHelper/TypeConversion/CollectionGenericConverter.cs b/src/CsvHelper/TypeConversion/CollectionGenericConverter.cs index 0d95243a9..eabdc1ea9 100644 --- a/src/CsvHelper/TypeConversion/CollectionGenericConverter.cs +++ b/src/CsvHelper/TypeConversion/CollectionGenericConverter.cs @@ -2,63 +2,61 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper +using CsvHelper.Configuration; using System.Collections; using System.Collections.ObjectModel; -using System.Linq; -using CsvHelper.Configuration; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class CollectionGenericConverter : IEnumerableConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class CollectionGenericConverter : IEnumerableConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - // Since we're using the MemberType here, this converter can be used for multiple types - // as long as they implement IList. - var list = (IList)ObjectResolver.Current.Resolve(memberMapData.Member.MemberType()); - var type = memberMapData.Member.MemberType().GetGenericArguments()[0]; - var converter = row.Context.TypeConverterCache.GetConverter(type); + // Since we're using the MemberType here, this converter can be used for multiple types + // as long as they implement IList. + var list = (IList)ObjectResolver.Current.Resolve(memberMapData.Member!.MemberType()); + var type = memberMapData.Member!.MemberType().GetGenericArguments()[0]; + var converter = row.Context.TypeConverterCache.GetConverter(type); - if (memberMapData.IsNameSet || row.Configuration.HasHeaderRecord && !memberMapData.IsIndexSet) + if (memberMapData.IsNameSet || row.Configuration.HasHeaderRecord && !memberMapData.IsIndexSet) + { + // Use the name. + var nameIndex = 0; + while (true) { - // Use the name. - var nameIndex = 0; - while (true) + if (!row.TryGetField(type, memberMapData.Names.FirstOrDefault() ?? string.Empty, nameIndex, out var field)) { - if (!row.TryGetField(type, memberMapData.Names.FirstOrDefault(), nameIndex, out var field)) - { - break; - } - - list.Add(field); - nameIndex++; + break; } - } - else - { - // Use the index. - var indexEnd = memberMapData.IndexEnd < memberMapData.Index - ? row.Parser.Count - 1 - : memberMapData.IndexEnd; - for (var i = memberMapData.Index; i <= indexEnd; i++) - { - var field = converter.ConvertFromString(row.GetField(i), row, memberMapData); - list.Add(field); - } + list.Add(field); + nameIndex++; } + } + else + { + // Use the index. + var indexEnd = memberMapData.IndexEnd < memberMapData.Index + ? row.Parser.Count - 1 + : memberMapData.IndexEnd; - return list; + for (var i = memberMapData.Index; i <= indexEnd; i++) + { + var field = converter.ConvertFromString(row.GetField(i), row, memberMapData); + list.Add(field); + } } + + return list; } } diff --git a/src/CsvHelper/TypeConversion/DateOnlyConverter.cs b/src/CsvHelper/TypeConversion/DateOnlyConverter.cs index 12ee7499a..72776400a 100644 --- a/src/CsvHelper/TypeConversion/DateOnlyConverter.cs +++ b/src/CsvHelper/TypeConversion/DateOnlyConverter.cs @@ -7,34 +7,33 @@ using System; using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class DateOnlyConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class DateOnlyConverter : DefaultTypeConverter - { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + if (text == null) { - if (text == null) - { - return base.ConvertFromString(null, row, memberMapData); - } + return base.ConvertFromString(null, row, memberMapData); + } - var formatProvider = (IFormatProvider)memberMapData.TypeConverterOptions.CultureInfo.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo; - var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None; + var formatProvider = (IFormatProvider?)memberMapData.TypeConverterOptions.CultureInfo?.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo; + var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None; - return memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0 - ? DateOnly.Parse(text, formatProvider, dateTimeStyle) - : DateOnly.ParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle); - } + return memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0 + ? DateOnly.Parse(text, formatProvider, dateTimeStyle) + : DateOnly.ParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle); } } #endif diff --git a/src/CsvHelper/TypeConversion/DateTimeConverter.cs b/src/CsvHelper/TypeConversion/DateTimeConverter.cs index f2a1f8a21..4d8448a3c 100644 --- a/src/CsvHelper/TypeConversion/DateTimeConverter.cs +++ b/src/CsvHelper/TypeConversion/DateTimeConverter.cs @@ -3,41 +3,39 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class DateTimeConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class DateTimeConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + if (text == null) { - if (text == null) - { - return base.ConvertFromString(null, row, memberMapData); - } + return base.ConvertFromString(null, row, memberMapData); + } - var formatProvider = (IFormatProvider)memberMapData.TypeConverterOptions.CultureInfo.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo; - var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None; + var formatProvider = (IFormatProvider?)memberMapData.TypeConverterOptions.CultureInfo?.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo; + var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None; - DateTime dateTime; - var success = memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0 - ? DateTime.TryParse(text, formatProvider, dateTimeStyle, out dateTime) - : DateTime.TryParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle, out dateTime); + DateTime dateTime; + var success = memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0 + ? DateTime.TryParse(text, formatProvider, dateTimeStyle, out dateTime) + : DateTime.TryParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle, out dateTime); - return success - ? dateTime - : base.ConvertFromString(text, row, memberMapData); - } + return success + ? dateTime + : base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/DateTimeOffsetConverter.cs b/src/CsvHelper/TypeConversion/DateTimeOffsetConverter.cs index 007da6c86..5986bdfdc 100644 --- a/src/CsvHelper/TypeConversion/DateTimeOffsetConverter.cs +++ b/src/CsvHelper/TypeConversion/DateTimeOffsetConverter.cs @@ -2,42 +2,40 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; + +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Converts a to and from a . +/// +public class DateTimeOffsetConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class DateTimeOffsetConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + if (text == null) { - if (text == null) - { - return base.ConvertFromString(null, row, memberMapData); - } + return base.ConvertFromString(null, row, memberMapData); + } - var formatProvider = (IFormatProvider)memberMapData.TypeConverterOptions.CultureInfo.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo; - var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None; + var formatProvider = (IFormatProvider?)memberMapData.TypeConverterOptions.CultureInfo?.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo; + var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None; - DateTimeOffset dateTimeOffset; - var success = memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0 - ? DateTimeOffset.TryParse(text, formatProvider, dateTimeStyle, out dateTimeOffset) - : DateTimeOffset.TryParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle, out dateTimeOffset); + DateTimeOffset dateTimeOffset; + var success = memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0 + ? DateTimeOffset.TryParse(text, formatProvider, dateTimeStyle, out dateTimeOffset) + : DateTimeOffset.TryParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle, out dateTimeOffset); - return success - ? dateTimeOffset - : base.ConvertFromString(null, row, memberMapData); - } + return success + ? dateTimeOffset + : base.ConvertFromString(null, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/DecimalConverter.cs b/src/CsvHelper/TypeConversion/DecimalConverter.cs index ef0643e5e..bbdaf660b 100644 --- a/src/CsvHelper/TypeConversion/DecimalConverter.cs +++ b/src/CsvHelper/TypeConversion/DecimalConverter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class DecimalConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class DecimalConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Number; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Number; - if (decimal.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var d)) - { - return d; - } - - return base.ConvertFromString(text, row, memberMapData); + if (decimal.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var d)) + { + return d; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/DefaultTypeConverter.cs b/src/CsvHelper/TypeConversion/DefaultTypeConverter.cs index 763d5082b..c9be7f4d9 100644 --- a/src/CsvHelper/TypeConversion/DefaultTypeConverter.cs +++ b/src/CsvHelper/TypeConversion/DefaultTypeConverter.cs @@ -2,61 +2,58 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using CsvHelper.Configuration; -using System.Linq; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts an to and from a . +/// +public class DefaultTypeConverter : ITypeConverter { - /// - /// Converts an to and from a . - /// - public class DefaultTypeConverter : ITypeConverter - { - /// - public virtual object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + /// + public virtual object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + if (memberMapData.UseDefaultOnConversionFailure && memberMapData.IsDefaultSet && memberMapData.Member!.MemberType() == memberMapData.Default?.GetType()) { - if (memberMapData.UseDefaultOnConversionFailure && memberMapData.IsDefaultSet && memberMapData.Member.MemberType() == memberMapData.Default?.GetType()) - { - return memberMapData.Default; - } + return memberMapData.Default; + } + + if (!row.Configuration.ExceptionMessagesContainRawData) + { + text = $"Hidden because {nameof(IParserConfiguration.ExceptionMessagesContainRawData)} is false."; + } - if (!row.Configuration.ExceptionMessagesContainRawData) + text ??= string.Empty; + + var message = + $"The conversion cannot be performed.{Environment.NewLine}" + + $" Text: '{text}'{Environment.NewLine}" + + $" MemberName: {memberMapData.Member?.Name}{Environment.NewLine}" + + $" MemberType: {memberMapData.Member?.MemberType().FullName}{Environment.NewLine}" + + $" TypeConverter: '{memberMapData.TypeConverter?.GetType().FullName}'"; + throw new TypeConverterException(this, memberMapData, text, row.Context, message); + } + + /// + public virtual string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) + { + if (value == null) + { + if (memberMapData.TypeConverterOptions.NullValues.Count > 0) { - text = $"Hidden because {nameof(IParserConfiguration.ExceptionMessagesContainRawData)} is false."; + return memberMapData.TypeConverterOptions.NullValues.First(); } - text ??= string.Empty; + return string.Empty; + } - var message = - $"The conversion cannot be performed.{Environment.NewLine}" + - $" Text: '{text}'{Environment.NewLine}" + - $" MemberName: {memberMapData.Member?.Name}{Environment.NewLine}" + - $" MemberType: {memberMapData.Member?.MemberType().FullName}{Environment.NewLine}" + - $" TypeConverter: '{memberMapData.TypeConverter?.GetType().FullName}'"; - throw new TypeConverterException(this, memberMapData, text, row.Context, message); + if (value is IFormattable formattable) + { + var format = memberMapData.TypeConverterOptions.Formats?.FirstOrDefault(); + return formattable.ToString(format, memberMapData.TypeConverterOptions.CultureInfo); } - /// - public virtual string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) - { - if (value == null) - { - if (memberMapData.TypeConverterOptions.NullValues.Count > 0) - { - return memberMapData.TypeConverterOptions.NullValues.First(); - } - - return string.Empty; - } - - if (value is IFormattable formattable) - { - var format = memberMapData.TypeConverterOptions.Formats?.FirstOrDefault(); - return formattable.ToString(format, memberMapData.TypeConverterOptions.CultureInfo); - } - - return value?.ToString() ?? string.Empty; - } - } + return value?.ToString() ?? string.Empty; + } } diff --git a/src/CsvHelper/TypeConversion/DoubleConverter.cs b/src/CsvHelper/TypeConversion/DoubleConverter.cs index b0ab28e87..402dddb24 100644 --- a/src/CsvHelper/TypeConversion/DoubleConverter.cs +++ b/src/CsvHelper/TypeConversion/DoubleConverter.cs @@ -2,56 +2,53 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Globalization; -using System.Linq; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class DoubleConverter : DefaultTypeConverter { + private Lazy defaultFormat = new Lazy(() => double.TryParse(double.MaxValue.ToString("R"), out var _) ? "R" : "G17"); + /// - /// Converts a to and from a . + /// Converts the object to a string. /// - public class DoubleConverter : DefaultTypeConverter + /// The object to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the object. + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) { - private Lazy defaultFormat = new Lazy(() => double.TryParse(double.MaxValue.ToString("R"), out var _) ? "R" : "G17"); - - /// - /// Converts the object to a string. - /// - /// The object to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the object. - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) - { - var format = memberMapData.TypeConverterOptions.Formats?.FirstOrDefault() ?? defaultFormat.Value; - - if (value is double d) - { - return d.ToString(format, memberMapData.TypeConverterOptions.CultureInfo); - } + var format = memberMapData.TypeConverterOptions.Formats?.FirstOrDefault() ?? defaultFormat.Value; - return base.ConvertToString(value, row, memberMapData); + if (value is double d) + { + return d.ToString(format, memberMapData.TypeConverterOptions.CultureInfo); } - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Float | NumberStyles.AllowThousands; + return base.ConvertToString(value, row, memberMapData); + } - if (double.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var d)) - { - return d; - } + /// + /// Converts the string to an object. + /// + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Float | NumberStyles.AllowThousands; - return base.ConvertFromString(text, row, memberMapData); + if (double.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var d)) + { + return d; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/EnumConverter.cs b/src/CsvHelper/TypeConversion/EnumConverter.cs index d89669e43..264a5ee08 100644 --- a/src/CsvHelper/TypeConversion/EnumConverter.cs +++ b/src/CsvHelper/TypeConversion/EnumConverter.cs @@ -2,120 +2,115 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using CsvHelper.Configuration; using CsvHelper.Configuration.Attributes; +using System.Reflection; + +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Converts an to and from a . +/// +public class EnumConverter : DefaultTypeConverter { + private readonly Type type; + private readonly Dictionary enumNamesByAttributeNames = new Dictionary(); + private readonly Dictionary enumNamesByAttributeNamesIgnoreCase = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary attributeNamesByEnumValues = new Dictionary(); + + // enumNamesByAttributeNames + // enumNamesByAttributeNamesIgnoreCase + // [Name("Foo")]:One + + // attributeNamesByEnumValues + // 1:[Name("Foo")] + /// - /// Converts an to and from a . + /// Creates a new for the given . /// - public class EnumConverter : DefaultTypeConverter + /// The type of the Enum. + public EnumConverter(Type type) { - private readonly Type type; - private readonly Dictionary enumNamesByAttributeNames = new Dictionary(); - private readonly Dictionary enumNamesByAttributeNamesIgnoreCase = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary attributeNamesByEnumValues = new Dictionary(); - - // enumNamesByAttributeNames - // enumNamesByAttributeNamesIgnoreCase - // [Name("Foo")]:One + if (!typeof(Enum).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + throw new ArgumentException($"'{type.FullName}' is not an Enum."); + } - // attributeNamesByEnumValues - // 1:[Name("Foo")] + this.type = type; - /// - /// Creates a new for the given . - /// - /// The type of the Enum. - public EnumConverter(Type type) + foreach (var value in Enum.GetValues(type)) { - if (!typeof(Enum).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) - { - throw new ArgumentException($"'{type.FullName}' is not an Enum."); - } - - this.type = type; + var enumName = Enum.GetName(type, value) ?? string.Empty; - foreach (var value in Enum.GetValues(type)) + var nameAttribute = type.GetField(enumName)?.GetCustomAttribute(); + if (nameAttribute != null && nameAttribute.Names.Length > 0) { - var enumName = Enum.GetName(type, value) ?? string.Empty; - - var nameAttribute = type.GetField(enumName)?.GetCustomAttribute(); - if (nameAttribute != null && nameAttribute.Names.Length > 0) + foreach (var attributeName in nameAttribute.Names) { - foreach (var attributeName in nameAttribute.Names) + if (!enumNamesByAttributeNames.ContainsKey(attributeName)) { - if (!enumNamesByAttributeNames.ContainsKey(attributeName)) - { - enumNamesByAttributeNames.Add(attributeName, enumName); - } + enumNamesByAttributeNames.Add(attributeName, enumName); + } - if (!enumNamesByAttributeNamesIgnoreCase.ContainsKey(attributeName)) - { - enumNamesByAttributeNamesIgnoreCase.Add(attributeName, enumName); - } + if (!enumNamesByAttributeNamesIgnoreCase.ContainsKey(attributeName)) + { + enumNamesByAttributeNamesIgnoreCase.Add(attributeName, enumName); + } - if (!attributeNamesByEnumValues.ContainsKey(value)) - { - attributeNamesByEnumValues.Add(value, attributeName); - } + if (!attributeNamesByEnumValues.ContainsKey(value)) + { + attributeNamesByEnumValues.Add(value, attributeName); } } } } + } - /// - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var ignoreCase = memberMapData.TypeConverterOptions.EnumIgnoreCase ?? false; + /// + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + var ignoreCase = memberMapData.TypeConverterOptions.EnumIgnoreCase ?? false; - if (text != null) + if (text != null) + { + var dict = ignoreCase + ? enumNamesByAttributeNamesIgnoreCase + : enumNamesByAttributeNames; + if (dict.TryGetValue(text, out var name)) { - var dict = ignoreCase - ? enumNamesByAttributeNamesIgnoreCase - : enumNamesByAttributeNames; - if (dict.TryGetValue(text, out var name)) - { - return Enum.Parse(type, name); - } + return Enum.Parse(type, name); } + } #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER - if (Enum.TryParse(type, text, ignoreCase, out var value)) - { - return value; - } - else - { - return base.ConvertFromString(text, row, memberMapData); - } + if (Enum.TryParse(type, text, ignoreCase, out var value)) + { + return value; + } + else + { + return base.ConvertFromString(text, row, memberMapData); + } #else - try - { - return Enum.Parse(type, text, ignoreCase); - } - catch - { - return base.ConvertFromString(text, row, memberMapData); - } -#endif + try + { + return Enum.Parse(type, text, ignoreCase); } - - /// - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) + catch { - if (value != null && attributeNamesByEnumValues.TryGetValue(value, out var name)) - { - return name; - } + return base.ConvertFromString(text, row, memberMapData); + } +#endif + } - return base.ConvertToString(value, row, memberMapData); + /// + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) + { + if (value != null && attributeNamesByEnumValues.TryGetValue(value, out var name)) + { + return name; } + + return base.ConvertToString(value, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/EnumConverterFactory.cs b/src/CsvHelper/TypeConversion/EnumConverterFactory.cs index 8409e227f..197f27fdc 100644 --- a/src/CsvHelper/TypeConversion/EnumConverterFactory.cs +++ b/src/CsvHelper/TypeConversion/EnumConverterFactory.cs @@ -2,34 +2,31 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +public class EnumConverterFactory : ITypeConverterFactory { /// - public class EnumConverterFactory : ITypeConverterFactory + public bool CanCreate(Type type) { - /// - public bool CanCreate(Type type) - { - return typeof(Enum).IsAssignableFrom(type); - } + return typeof(Enum).IsAssignableFrom(type); + } - /// - public bool Create(Type type, TypeConverterCache cache, out ITypeConverter typeConverter) + /// + public bool Create(Type type, TypeConverterCache cache, out ITypeConverter typeConverter) + { + if (cache.Contains(typeof(Enum))) { - if (cache.Contains(typeof(Enum))) - { - // If the user has registered a converter for the generic Enum type, - // that converter will be used as a default for all enums. - typeConverter = cache.GetConverter(); + // If the user has registered a converter for the generic Enum type, + // that converter will be used as a default for all enums. + typeConverter = cache.GetConverter(); - return false; - } + return false; + } - typeConverter = new EnumConverter(type); + typeConverter = new EnumConverter(type); - return true; - } + return true; } } diff --git a/src/CsvHelper/TypeConversion/EnumerableConverter.cs b/src/CsvHelper/TypeConversion/EnumerableConverter.cs index 42f0d290a..abc66d06d 100644 --- a/src/CsvHelper/TypeConversion/EnumerableConverter.cs +++ b/src/CsvHelper/TypeConversion/EnumerableConverter.cs @@ -2,47 +2,46 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Collections; using CsvHelper.Configuration; +using System.Collections; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Throws an exception when used. This is here so that it's apparent +/// that there is no support for type conversion. A custom +/// converter will need to be created to have a field convert to and +/// from an IEnumerable. +/// +public class EnumerableConverter : DefaultTypeConverter { /// - /// Throws an exception when used. This is here so that it's apparent - /// that there is no support for type conversion. A custom - /// converter will need to be created to have a field convert to and - /// from an IEnumerable. + /// Throws an exception. /// - public class EnumerableConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Throws an exception. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var message = "Converting IEnumerable types is not supported for a single field. " + - "If you want to do this, create your own ITypeConverter and register " + - "it in the TypeConverterFactory by calling AddConverter."; - throw new TypeConverterException(this, memberMapData, text, row.Context, message); - } + var message = "Converting IEnumerable types is not supported for a single field. " + + "If you want to do this, create your own ITypeConverter and register " + + "it in the TypeConverterFactory by calling AddConverter."; + throw new TypeConverterException(this, memberMapData, text, row.Context, message); + } - /// - /// Throws an exception. - /// - /// The object to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the object. - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) - { - var message = "Converting IEnumerable types is not supported for a single field. " + - "If you want to do this, create your own ITypeConverter and register " + - "it in the TypeConverterFactory by calling AddConverter."; - throw new TypeConverterException(this, memberMapData, value, row.Context, message); - } + /// + /// Throws an exception. + /// + /// The object to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the object. + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) + { + var message = "Converting IEnumerable types is not supported for a single field. " + + "If you want to do this, create your own ITypeConverter and register " + + "it in the TypeConverterFactory by calling AddConverter."; + throw new TypeConverterException(this, memberMapData, value, row.Context, message); } } diff --git a/src/CsvHelper/TypeConversion/GuidConverter.cs b/src/CsvHelper/TypeConversion/GuidConverter.cs index d014f1b00..158b9382e 100644 --- a/src/CsvHelper/TypeConversion/GuidConverter.cs +++ b/src/CsvHelper/TypeConversion/GuidConverter.cs @@ -2,31 +2,29 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using CsvHelper.Configuration; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class GuidConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class GuidConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + if (Guid.TryParse(text, out var g)) { - if (Guid.TryParse(text, out var g)) - { - return g; - } - - return base.ConvertFromString(text, row, memberMapData); + return g; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/IDictionaryConverter.cs b/src/CsvHelper/TypeConversion/IDictionaryConverter.cs index cea2b4b45..32745f69a 100644 --- a/src/CsvHelper/TypeConversion/IDictionaryConverter.cs +++ b/src/CsvHelper/TypeConversion/IDictionaryConverter.cs @@ -2,64 +2,62 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Collections; -using System.Collections.Generic; using CsvHelper.Configuration; +using System.Collections; + +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Converts an to and from a . +/// +public class IDictionaryConverter : DefaultTypeConverter { /// - /// Converts an to and from a . + /// Converts the object to a string. /// - public class IDictionaryConverter : DefaultTypeConverter + /// The object to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the object. + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) { - /// - /// Converts the object to a string. - /// - /// The object to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the object. - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) + var dictionary = value as IDictionary; + if (dictionary == null) { - var dictionary = value as IDictionary; - if (dictionary == null) - { - return base.ConvertToString(value, row, memberMapData); - } - - foreach (DictionaryEntry entry in dictionary) - { - row.WriteField(entry.Value); - } - - return null; + return base.ConvertToString(value, row, memberMapData); } - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + foreach (DictionaryEntry entry in dictionary) { - var dictionary = new Dictionary(); + row.WriteField(entry.Value); + } - var indexEnd = memberMapData.IndexEnd < memberMapData.Index - ? row.Parser.Count - 1 - : memberMapData.IndexEnd; + return null; + } + + /// + /// Converts the string to an object. + /// + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + var dictionary = new Dictionary(); - for (var i = memberMapData.Index; i <= indexEnd; i++) + var indexEnd = memberMapData.IndexEnd < memberMapData.Index + ? row.Parser.Count - 1 + : memberMapData.IndexEnd; + + for (var i = memberMapData.Index; i <= indexEnd; i++) + { + if (row.TryGetField(i, out string? field)) { - if (row.TryGetField(i, out string field)) - { - dictionary.Add(row.HeaderRecord[i], field); - } + dictionary.Add(row.HeaderRecord![i], field); } - - return dictionary; } + + return dictionary; } } diff --git a/src/CsvHelper/TypeConversion/IDictionaryGenericConverter.cs b/src/CsvHelper/TypeConversion/IDictionaryGenericConverter.cs index 42a764f31..2e863fa84 100644 --- a/src/CsvHelper/TypeConversion/IDictionaryGenericConverter.cs +++ b/src/CsvHelper/TypeConversion/IDictionaryGenericConverter.cs @@ -2,45 +2,43 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Collections; -using System.Collections.Generic; using CsvHelper.Configuration; +using System.Collections; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts an to and from a . +/// +public class IDictionaryGenericConverter : IDictionaryConverter { /// - /// Converts an to and from a . + /// Converts the string to an object. /// - public class IDictionaryGenericConverter : IDictionaryConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var keyType = memberMapData.Member.MemberType().GetGenericArguments()[0]; - var valueType = memberMapData.Member.MemberType().GetGenericArguments()[1]; - var dictionaryType = typeof(Dictionary<,>); - dictionaryType = dictionaryType.MakeGenericType(keyType, valueType); - var dictionary = (IDictionary)ObjectResolver.Current.Resolve(dictionaryType); - var converter = row.Context.TypeConverterCache.GetConverter(valueType); + var keyType = memberMapData.Member!.MemberType().GetGenericArguments()[0]; + var valueType = memberMapData.Member!.MemberType().GetGenericArguments()[1]; + var dictionaryType = typeof(Dictionary<,>); + dictionaryType = dictionaryType.MakeGenericType(keyType, valueType); + var dictionary = (IDictionary)ObjectResolver.Current.Resolve(dictionaryType); + var converter = row.Context.TypeConverterCache.GetConverter(valueType); - var indexEnd = memberMapData.IndexEnd < memberMapData.Index - ? row.Parser.Count - 1 - : memberMapData.IndexEnd; + var indexEnd = memberMapData.IndexEnd < memberMapData.Index + ? row.Parser.Count - 1 + : memberMapData.IndexEnd; - for (var i = memberMapData.Index; i <= indexEnd; i++) - { - var field = converter.ConvertFromString(row.GetField(i), row, memberMapData); - - dictionary.Add(row.HeaderRecord[i], field); - } + for (var i = memberMapData.Index; i <= indexEnd; i++) + { + var field = converter.ConvertFromString(row.GetField(i), row, memberMapData); - return dictionary; + dictionary.Add(row.HeaderRecord![i], field); } + + return dictionary; } } diff --git a/src/CsvHelper/TypeConversion/IEnumerableConverter.cs b/src/CsvHelper/TypeConversion/IEnumerableConverter.cs index b7a9cf7c7..44bb8aeeb 100644 --- a/src/CsvHelper/TypeConversion/IEnumerableConverter.cs +++ b/src/CsvHelper/TypeConversion/IEnumerableConverter.cs @@ -2,84 +2,81 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Collections; -using System.Collections.Generic; -using System.Linq; using CsvHelper.Configuration; +using System.Collections; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts an to and from a . +/// +public class IEnumerableConverter : DefaultTypeConverter { /// - /// Converts an to and from a . + /// Converts the object to a string. /// - public class IEnumerableConverter : DefaultTypeConverter + /// The object to convert to a string. + /// + /// + /// The string representation of the object. + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) { - /// - /// Converts the object to a string. - /// - /// The object to convert to a string. - /// - /// - /// The string representation of the object. - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) + var list = value as IEnumerable; + if (list == null) { - var list = value as IEnumerable; - if (list == null) - { - return base.ConvertToString(value, row, memberMapData); - } - - foreach (var item in list) - { - row.WriteField(item.ToString()); - } - - return null; + return base.ConvertToString(value, row, memberMapData); } - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + foreach (var item in list) { - var list = new List(); + row.WriteField(item.ToString()); + } - if (memberMapData.IsNameSet || row.Configuration.HasHeaderRecord && !memberMapData.IsIndexSet) + return null; + } + + /// + /// Converts the string to an object. + /// + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + var list = new List(); + + if (memberMapData.IsNameSet || row.Configuration.HasHeaderRecord && !memberMapData.IsIndexSet) + { + // Use the name. + var nameIndex = 0; + while (true) { - // Use the name. - var nameIndex = 0; - while (true) + if (!row.TryGetField(memberMapData.Names.FirstOrDefault()!, nameIndex, out string? field)) { - if (!row.TryGetField(memberMapData.Names.FirstOrDefault(), nameIndex, out string field)) - { - break; - } - - list.Add(field); - nameIndex++; + break; } + + list.Add(field); + nameIndex++; } - else - { - // Use the index. - var indexEnd = memberMapData.IndexEnd < memberMapData.Index - ? row.Parser.Count - 1 - : memberMapData.IndexEnd; + } + else + { + // Use the index. + var indexEnd = memberMapData.IndexEnd < memberMapData.Index + ? row.Parser.Count - 1 + : memberMapData.IndexEnd; - for (var i = memberMapData.Index; i <= indexEnd; i++) + for (var i = memberMapData.Index; i <= indexEnd; i++) + { + if (row.TryGetField(i, out string? field)) { - if (row.TryGetField(i, out string field)) - { - list.Add(field); - } + list.Add(field); } } - - return list; } + + return list; } } diff --git a/src/CsvHelper/TypeConversion/IEnumerableGenericConverter.cs b/src/CsvHelper/TypeConversion/IEnumerableGenericConverter.cs index 71d383dcb..4590efa9d 100644 --- a/src/CsvHelper/TypeConversion/IEnumerableGenericConverter.cs +++ b/src/CsvHelper/TypeConversion/IEnumerableGenericConverter.cs @@ -2,64 +2,61 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Collections; -using System.Collections.Generic; -using System.Linq; using CsvHelper.Configuration; +using System.Collections; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts an to and from a . +/// +public class IEnumerableGenericConverter : IEnumerableConverter { /// - /// Converts an to and from a . + /// Converts the string to an object. /// - public class IEnumerableGenericConverter : IEnumerableConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var type = memberMapData.Member.MemberType().GetGenericArguments()[0]; - var listType = typeof(List<>); - listType = listType.MakeGenericType(type); - var list = (IList)ObjectResolver.Current.Resolve(listType); - var converter = row.Context.TypeConverterCache.GetConverter(type); + var type = memberMapData.Member!.MemberType().GetGenericArguments()[0]; + var listType = typeof(List<>); + listType = listType.MakeGenericType(type); + var list = (IList)ObjectResolver.Current.Resolve(listType); + var converter = row.Context.TypeConverterCache.GetConverter(type); - if (memberMapData.IsNameSet || row.Configuration.HasHeaderRecord && !memberMapData.IsIndexSet) + if (memberMapData.IsNameSet || row.Configuration.HasHeaderRecord && !memberMapData.IsIndexSet) + { + // Use the name. + var nameIndex = 0; + while (true) { - // Use the name. - var nameIndex = 0; - while (true) + if (!row.TryGetField(type, memberMapData.Names.FirstOrDefault()!, nameIndex, out var field)) { - if (!row.TryGetField(type, memberMapData.Names.FirstOrDefault(), nameIndex, out var field)) - { - break; - } - - list.Add(field); - nameIndex++; + break; } + + list.Add(field); + nameIndex++; } - else - { - // Use the index. - var indexEnd = memberMapData.IndexEnd < memberMapData.Index - ? row.Parser.Count - 1 - : memberMapData.IndexEnd; + } + else + { + // Use the index. + var indexEnd = memberMapData.IndexEnd < memberMapData.Index + ? row.Parser.Count - 1 + : memberMapData.IndexEnd; - for (var i = memberMapData.Index; i <= indexEnd; i++) - { - var field = converter.ConvertFromString(row.GetField(i), row, memberMapData); + for (var i = memberMapData.Index; i <= indexEnd; i++) + { + var field = converter.ConvertFromString(row.GetField(i), row, memberMapData); - list.Add(field); - } + list.Add(field); } - - return list; } + + return list; } } diff --git a/src/CsvHelper/TypeConversion/ITypeConverter.cs b/src/CsvHelper/TypeConversion/ITypeConverter.cs index 03babaa64..f2b5ec7bb 100644 --- a/src/CsvHelper/TypeConversion/ITypeConverter.cs +++ b/src/CsvHelper/TypeConversion/ITypeConverter.cs @@ -4,29 +4,28 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts objects to and from strings. +/// +public interface ITypeConverter { /// - /// Converts objects to and from strings. + /// Converts the string to an object. /// - public interface ITypeConverter - { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData); + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData); - /// - /// Converts the object to a string. - /// - /// The object to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the object. - string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData); - } + /// + /// Converts the object to a string. + /// + /// The object to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the object. + string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData); } diff --git a/src/CsvHelper/TypeConversion/ITypeConverterFactory.cs b/src/CsvHelper/TypeConversion/ITypeConverterFactory.cs index 57c76d6ee..9466c97c3 100644 --- a/src/CsvHelper/TypeConversion/ITypeConverterFactory.cs +++ b/src/CsvHelper/TypeConversion/ITypeConverterFactory.cs @@ -2,32 +2,26 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Produces for the specified +/// +public interface ITypeConverterFactory { /// - /// Produces for the specified + /// Determines if the factory can create a type converter for the given type. /// - public interface ITypeConverterFactory - { - /// - /// Determines if the factory can create a type converter for the given type. - /// - /// The to be checked - /// true if the factory can create the type, otherwise false. - bool CanCreate(Type type); + /// The to be checked + /// true if the factory can create the type, otherwise false. + bool CanCreate(Type type); - /// - /// Creates a type converter for the given type and assigns it to the given out typeConverter parameter. - /// - /// The type to create the converter for. - /// The type converter cache. - /// The parameter to set the converter to. - /// true if the converter should be added to the cache, otherwise false. - bool Create(Type type, TypeConverterCache cache, out ITypeConverter typeConverter); - } + /// + /// Creates a type converter for the given type and assigns it to the given out typeConverter parameter. + /// + /// The type to create the converter for. + /// The type converter cache. + /// The parameter to set the converter to. + /// true if the converter should be added to the cache, otherwise false. + bool Create(Type type, TypeConverterCache cache, out ITypeConverter typeConverter); } diff --git a/src/CsvHelper/TypeConversion/Int16Converter.cs b/src/CsvHelper/TypeConversion/Int16Converter.cs index f774690f9..fa4cfcdf1 100644 --- a/src/CsvHelper/TypeConversion/Int16Converter.cs +++ b/src/CsvHelper/TypeConversion/Int16Converter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class Int16Converter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class Int16Converter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (short.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var s)) - { - return s; - } - - return base.ConvertFromString(text, row, memberMapData); + if (short.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var s)) + { + return s; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/Int32Converter.cs b/src/CsvHelper/TypeConversion/Int32Converter.cs index 57ab66872..8f0d6e895 100644 --- a/src/CsvHelper/TypeConversion/Int32Converter.cs +++ b/src/CsvHelper/TypeConversion/Int32Converter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts an to and from a . +/// +public class Int32Converter : DefaultTypeConverter { /// - /// Converts an to and from a . + /// Converts the string to an object. /// - public class Int32Converter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (int.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var i)) - { - return i; - } - - return base.ConvertFromString(text, row, memberMapData); + if (int.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var i)) + { + return i; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/Int64Converter.cs b/src/CsvHelper/TypeConversion/Int64Converter.cs index f2295d0c4..eae19f1db 100644 --- a/src/CsvHelper/TypeConversion/Int64Converter.cs +++ b/src/CsvHelper/TypeConversion/Int64Converter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts an to and from a . +/// +public class Int64Converter : DefaultTypeConverter { /// - /// Converts an to and from a . + /// Converts the string to an object. /// - public class Int64Converter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (long.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var l)) - { - return l; - } - - return base.ConvertFromString(text, row, memberMapData); + if (long.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var l)) + { + return l; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/NotSupportedTypeConverter.cs b/src/CsvHelper/TypeConversion/NotSupportedTypeConverter.cs index 0058bb484..8ff6c7d59 100644 --- a/src/CsvHelper/TypeConversion/NotSupportedTypeConverter.cs +++ b/src/CsvHelper/TypeConversion/NotSupportedTypeConverter.cs @@ -3,48 +3,46 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Throws an exception when used. This is here so that it's apparent +/// that there is no support for type conversion. A custom +/// converter will need to be created to have a field convert to and +/// from . +/// +public class NotSupportedTypeConverter : TypeConverter { /// - /// Throws an exception when used. This is here so that it's apparent - /// that there is no support for type conversion. A custom - /// converter will need to be created to have a field convert to and - /// from . + /// Throws an exception. /// - public class NotSupportedTypeConverter : TypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override T ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Throws an exception. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override T ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var message = - $"Converting {typeof(T).FullName} is not supported. " + - "If you want to do this, create your own ITypeConverter and register " + - "it in the TypeConverterFactory by calling AddConverter."; - throw new TypeConverterException(this, memberMapData, text ?? string.Empty, row.Context, message); - } + var message = + $"Converting {typeof(T).FullName} is not supported. " + + "If you want to do this, create your own ITypeConverter and register " + + "it in the TypeConverterFactory by calling AddConverter."; + throw new TypeConverterException(this, memberMapData, text ?? string.Empty, row.Context, message); + } - /// - /// Throws an exception. - /// - /// The object to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the object. - public override string ConvertToString(T value, IWriterRow row, MemberMapData memberMapData) - { - var message = - $"Converting {typeof(T).FullName} is not supported. " + - "If you want to do this, create your own ITypeConverter and register " + - "it in the TypeConverterFactory by calling AddConverter."; - throw new TypeConverterException(this, memberMapData, value, row.Context, message); - } + /// + /// Throws an exception. + /// + /// The object to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the object. + public override string? ConvertToString(T? value, IWriterRow row, MemberMapData memberMapData) + { + var message = + $"Converting {typeof(T).FullName} is not supported. " + + "If you want to do this, create your own ITypeConverter and register " + + "it in the TypeConverterFactory by calling AddConverter."; + throw new TypeConverterException(this, memberMapData, value, row.Context, message); } } diff --git a/src/CsvHelper/TypeConversion/NullableConverter.cs b/src/CsvHelper/TypeConversion/NullableConverter.cs index b2fc3ecee..db77bfd9f 100644 --- a/src/CsvHelper/TypeConversion/NullableConverter.cs +++ b/src/CsvHelper/TypeConversion/NullableConverter.cs @@ -2,93 +2,91 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; using CsvHelper.Configuration; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class NullableConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Gets the type of the nullable. /// - public class NullableConverter : DefaultTypeConverter - { - /// - /// Gets the type of the nullable. - /// - /// - /// The type of the nullable. - /// - public Type NullableType { get; private set; } + /// + /// The type of the nullable. + /// + public Type NullableType { get; private set; } - /// - /// Gets the underlying type of the nullable. - /// - /// - /// The underlying type. - /// - public Type UnderlyingType { get; private set; } + /// + /// Gets the underlying type of the nullable. + /// + /// + /// The underlying type. + /// + public Type? UnderlyingType { get; private set; } - /// - /// Gets the type converter for the underlying type. - /// - /// - /// The type converter. - /// - public ITypeConverter UnderlyingTypeConverter { get; private set; } + /// + /// Gets the type converter for the underlying type. + /// + /// + /// The type converter. + /// + public ITypeConverter UnderlyingTypeConverter { get; private set; } - /// - /// Creates a new for the given . - /// - /// The nullable type. - /// The type converter factory. - /// type is not a nullable type. - public NullableConverter(Type type, TypeConverterCache typeConverterFactory) + /// + /// Creates a new for the given . + /// + /// The nullable type. + /// The type converter factory. + /// type is not a nullable type. + public NullableConverter(Type type, TypeConverterCache typeConverterFactory) + { + NullableType = type; + UnderlyingType = Nullable.GetUnderlyingType(type); + if (UnderlyingType == null) { - NullableType = type; - UnderlyingType = Nullable.GetUnderlyingType(type); - if (UnderlyingType == null) - { - throw new ArgumentException("type is not a nullable type."); - } + throw new ArgumentException("type is not a nullable type."); + } - UnderlyingTypeConverter = typeConverterFactory.GetConverter(UnderlyingType); + UnderlyingTypeConverter = typeConverterFactory.GetConverter(UnderlyingType); + } + + /// + /// Converts the string to an object. + /// + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + if (string.IsNullOrEmpty(text)) + { + return null; } - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + foreach (var nullValue in memberMapData.TypeConverterOptions.NullValues) { - if (string.IsNullOrEmpty(text)) + if (text == nullValue) { return null; } - - foreach (var nullValue in memberMapData.TypeConverterOptions.NullValues) - { - if (text == nullValue) - { - return null; - } - } - - return UnderlyingTypeConverter.ConvertFromString(text, row, memberMapData); } - /// - /// Converts the object to a string. - /// - /// The object to convert to a string. - /// - /// - /// The string representation of the object. - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) - { - return UnderlyingTypeConverter.ConvertToString(value, row, memberMapData); - } + return UnderlyingTypeConverter.ConvertFromString(text, row, memberMapData); + } + + /// + /// Converts the object to a string. + /// + /// The object to convert to a string. + /// + /// + /// The string representation of the object. + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) + { + return UnderlyingTypeConverter.ConvertToString(value, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/NullableConverterFactory.cs b/src/CsvHelper/TypeConversion/NullableConverterFactory.cs index 93d5b8c49..8aa6cd4bd 100644 --- a/src/CsvHelper/TypeConversion/NullableConverterFactory.cs +++ b/src/CsvHelper/TypeConversion/NullableConverterFactory.cs @@ -2,27 +2,22 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; -using System.Linq; +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +public class NullableConverterFactory : ITypeConverterFactory { /// - public class NullableConverterFactory : ITypeConverterFactory + public bool CanCreate(Type type) { - /// - public bool CanCreate(Type type) - { - return (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>))); - } + return (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>))); + } - /// - public bool Create(Type type, TypeConverterCache cache, out ITypeConverter typeConverter) - { - typeConverter = new NullableConverter(type, cache); + /// + public bool Create(Type type, TypeConverterCache cache, out ITypeConverter typeConverter) + { + typeConverter = new NullableConverter(type, cache); - return true; - } + return true; } } diff --git a/src/CsvHelper/TypeConversion/SByteConverter.cs b/src/CsvHelper/TypeConversion/SByteConverter.cs index 3d648f3c1..1b8a9ccf8 100644 --- a/src/CsvHelper/TypeConversion/SByteConverter.cs +++ b/src/CsvHelper/TypeConversion/SByteConverter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class SByteConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class SByteConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (sbyte.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var sb)) - { - return sb; - } - - return base.ConvertFromString(text, row, memberMapData); + if (sbyte.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var sb)) + { + return sb; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/SingleConverter.cs b/src/CsvHelper/TypeConversion/SingleConverter.cs index ea93e1d9a..89a682bcc 100644 --- a/src/CsvHelper/TypeConversion/SingleConverter.cs +++ b/src/CsvHelper/TypeConversion/SingleConverter.cs @@ -2,56 +2,53 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; -using System; -using System.Linq; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class SingleConverter : DefaultTypeConverter { + private Lazy defaultFormat = new Lazy(() => float.TryParse(float.MaxValue.ToString("R"), out var _) ? "R" : "G9"); + /// - /// Converts a to and from a . + /// Converts the object to a string. /// - public class SingleConverter : DefaultTypeConverter + /// The object to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the object. + public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) { - private Lazy defaultFormat = new Lazy(() => float.TryParse(float.MaxValue.ToString("R"), out var _) ? "R" : "G9"); - - /// - /// Converts the object to a string. - /// - /// The object to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the object. - public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) - { - var format = memberMapData.TypeConverterOptions.Formats?.FirstOrDefault() ?? defaultFormat.Value; - - if (value is float f) - { - return f.ToString(format, memberMapData.TypeConverterOptions.CultureInfo); - } + var format = memberMapData.TypeConverterOptions.Formats?.FirstOrDefault() ?? defaultFormat.Value; - return base.ConvertToString(value, row, memberMapData); + if (value is float f) + { + return f.ToString(format, memberMapData.TypeConverterOptions.CultureInfo); } - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Float | NumberStyles.AllowThousands; + return base.ConvertToString(value, row, memberMapData); + } - if (float.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var f)) - { - return f; - } + /// + /// Converts the string to an object. + /// + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Float | NumberStyles.AllowThousands; - return base.ConvertFromString(text, row, memberMapData); + if (float.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var f)) + { + return f; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/StringConverter.cs b/src/CsvHelper/TypeConversion/StringConverter.cs index 3bc04158c..041689e22 100644 --- a/src/CsvHelper/TypeConversion/StringConverter.cs +++ b/src/CsvHelper/TypeConversion/StringConverter.cs @@ -4,36 +4,35 @@ // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class StringConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class StringConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + if (text == null) { - if (text == null) - { - return string.Empty; - } + return string.Empty; + } - foreach (var nullValue in memberMapData.TypeConverterOptions.NullValues) + foreach (var nullValue in memberMapData.TypeConverterOptions.NullValues) + { + if (text == nullValue) { - if (text == nullValue) - { - return null; - } + return null; } - - return text; } + + return text; } } diff --git a/src/CsvHelper/TypeConversion/TimeOnlyConverter.cs b/src/CsvHelper/TypeConversion/TimeOnlyConverter.cs index f1141be54..28f564579 100644 --- a/src/CsvHelper/TypeConversion/TimeOnlyConverter.cs +++ b/src/CsvHelper/TypeConversion/TimeOnlyConverter.cs @@ -7,34 +7,33 @@ using System; using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class TimeOnlyConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class TimeOnlyConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) + if (text == null) { - if (text == null) - { - return base.ConvertFromString(null, row, memberMapData); - } + return base.ConvertFromString(null, row, memberMapData); + } - var formatProvider = (IFormatProvider)memberMapData.TypeConverterOptions.CultureInfo.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo; - var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None; + var formatProvider = (IFormatProvider?)memberMapData.TypeConverterOptions.CultureInfo?.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo; + var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None; - return memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0 - ? TimeOnly.Parse(text, formatProvider, dateTimeStyle) - : TimeOnly.ParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle); - } + return memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0 + ? TimeOnly.Parse(text, formatProvider, dateTimeStyle) + : TimeOnly.ParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle); } } #endif diff --git a/src/CsvHelper/TypeConversion/TimeSpanConverter.cs b/src/CsvHelper/TypeConversion/TimeSpanConverter.cs index c88f7e94e..44c151afb 100644 --- a/src/CsvHelper/TypeConversion/TimeSpanConverter.cs +++ b/src/CsvHelper/TypeConversion/TimeSpanConverter.cs @@ -2,40 +2,38 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; + +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Converts a to and from a . +/// +public class TimeSpanConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class TimeSpanConverter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var formatProvider = (IFormatProvider)memberMapData.TypeConverterOptions.CultureInfo; - - var timeSpanStyle = memberMapData.TypeConverterOptions.TimeSpanStyle ?? TimeSpanStyles.None; - if (memberMapData.TypeConverterOptions.Formats != null && TimeSpan.TryParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, timeSpanStyle, out var span)) - { - return span; - } + var formatProvider = (IFormatProvider?)memberMapData.TypeConverterOptions.CultureInfo ?? memberMapData.TypeConverterOptions.CultureInfo; - if (memberMapData.TypeConverterOptions.Formats == null && TimeSpan.TryParse(text, formatProvider, out span)) - { - return span; - } + var timeSpanStyle = memberMapData.TypeConverterOptions.TimeSpanStyle ?? TimeSpanStyles.None; + if (memberMapData.TypeConverterOptions.Formats != null && TimeSpan.TryParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, timeSpanStyle, out var span)) + { + return span; + } - return base.ConvertFromString(text, row, memberMapData); + if (memberMapData.TypeConverterOptions.Formats == null && TimeSpan.TryParse(text, formatProvider, out span)) + { + return span; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/TypeConverter.cs b/src/CsvHelper/TypeConversion/TypeConverter.cs index 0d591dadb..a0ffdebae 100644 --- a/src/CsvHelper/TypeConversion/TypeConverter.cs +++ b/src/CsvHelper/TypeConversion/TypeConverter.cs @@ -3,35 +3,33 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts values to and from strings. +/// +public abstract class TypeConverter : ITypeConverter { /// - /// Converts values to and from strings. + /// Converts the string to a (T) value. /// - public abstract class TypeConverter : ITypeConverter - { - /// - /// Converts the string to a (T) value. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The value created from the string. - public abstract T ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData); + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The value created from the string. + public abstract T? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData); - /// - /// Converts the value to a string. - /// - /// The value to convert to a string. - /// The for the current record. - /// The for the member being written. - /// The string representation of the value. - public abstract string ConvertToString(T value, IWriterRow row, MemberMapData memberMapData); + /// + /// Converts the value to a string. + /// + /// The value to convert to a string. + /// The for the current record. + /// The for the member being written. + /// The string representation of the value. + public abstract string? ConvertToString(T? value, IWriterRow row, MemberMapData memberMapData); - object ITypeConverter.ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) => ConvertFromString(text, row, memberMapData); + object? ITypeConverter.ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) => ConvertFromString(text, row, memberMapData); - string ITypeConverter.ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) => ConvertToString((T)value, row, memberMapData); - } + string? ITypeConverter.ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) => ConvertToString((T?)value, row, memberMapData); } diff --git a/src/CsvHelper/TypeConversion/TypeConverterCache.cs b/src/CsvHelper/TypeConversion/TypeConverterCache.cs index afda50062..42dbaadd2 100644 --- a/src/CsvHelper/TypeConversion/TypeConverterCache.cs +++ b/src/CsvHelper/TypeConversion/TypeConverterCache.cs @@ -3,247 +3,243 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration.Attributes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; using System.Reflection; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Caches s for a given type. +/// +public class TypeConverterCache { + private readonly Dictionary typeConverters = new Dictionary(); + private readonly List defaultTypeConverterFactories = new List(); + private readonly List typeConverterFactories = new List(); + private readonly Dictionary typeConverterFactoryCache = new Dictionary(); + + /// + /// Initializes the class. + /// + public TypeConverterCache() + { + CreateDefaultConverters(); + } + + /// + /// Determines if there is a converter registered for the given type. + /// + /// The type to check. + /// true if the converter is registered, otherwise false. + public bool Contains(Type type) + { + return typeConverters.ContainsKey(type); + } + /// - /// Caches s for a given type. + /// Adds the . + /// Factories are queried in order of being added and first factory that handles the type is used for creating the . /// - public class TypeConverterCache + /// Type converter factory + public void AddConverterFactory(ITypeConverterFactory typeConverterFactory) { - private readonly Dictionary typeConverters = new Dictionary(); - private readonly List defaultTypeConverterFactories = new List(); - private readonly List typeConverterFactories = new List(); - private readonly Dictionary typeConverterFactoryCache = new Dictionary(); - - /// - /// Initializes the class. - /// - public TypeConverterCache() + if (typeConverterFactory == null) { - CreateDefaultConverters(); + throw new ArgumentNullException(nameof(typeConverterFactory)); } - /// - /// Determines if there is a converter registered for the given type. - /// - /// The type to check. - /// true if the converter is registered, otherwise false. - public bool Contains(Type type) + typeConverterFactories.Add(typeConverterFactory); + } + + /// + /// Adds the for the given . + /// + /// The type the converter converts. + /// The type converter that converts the type. + public void AddConverter(Type type, ITypeConverter typeConverter) + { + if (type == null) { - return typeConverters.ContainsKey(type); + throw new ArgumentNullException(nameof(type)); } - /// - /// Adds the . - /// Factories are queried in order of being added and first factory that handles the type is used for creating the . - /// - /// Type converter factory - public void AddConverterFactory(ITypeConverterFactory typeConverterFactory) + if (typeConverter == null) { - if (typeConverterFactory == null) - { - throw new ArgumentNullException(nameof(typeConverterFactory)); - } - - typeConverterFactories.Add(typeConverterFactory); + throw new ArgumentNullException(nameof(typeConverter)); } - /// - /// Adds the for the given . - /// - /// The type the converter converts. - /// The type converter that converts the type. - public void AddConverter(Type type, ITypeConverter typeConverter) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } + typeConverters[type] = typeConverter; + } - if (typeConverter == null) - { - throw new ArgumentNullException(nameof(typeConverter)); - } + /// + /// Adds the for the given . + /// + /// The type the converter converts. + /// The type converter that converts the type. + public void AddConverter(TypeConverter typeConverter) => + AddConverter(typeConverter as ITypeConverter); - typeConverters[type] = typeConverter; + /// + /// Adds the for the given . + /// + /// The type the converter converts. + /// The type converter that converts the type. + public void AddConverter(ITypeConverter typeConverter) + { + if (typeConverter == null) + { + throw new ArgumentNullException(nameof(typeConverter)); } - /// - /// Adds the for the given . - /// - /// The type the converter converts. - /// The type converter that converts the type. - public void AddConverter(TypeConverter typeConverter) => - AddConverter(typeConverter as ITypeConverter); - - /// - /// Adds the for the given . - /// - /// The type the converter converts. - /// The type converter that converts the type. - public void AddConverter(ITypeConverter typeConverter) - { - if (typeConverter == null) - { - throw new ArgumentNullException(nameof(typeConverter)); - } + typeConverters[typeof(T)] = typeConverter; + } - typeConverters[typeof(T)] = typeConverter; + /// + /// Adds the given to all registered types. + /// + /// The type converter. + public void AddConverter(ITypeConverter typeConverter) + { + foreach (var type in typeConverters.Keys) + { + typeConverters[type] = typeConverter; } + } - /// - /// Adds the given to all registered types. - /// - /// The type converter. - public void AddConverter(ITypeConverter typeConverter) + /// + /// Removes the for the given . + /// + /// The type to remove the converter for. + public void RemoveConverter(Type type) + { + if (type == null) { - foreach (var type in typeConverters.Keys) - { - typeConverters[type] = typeConverter; - } + throw new ArgumentNullException(nameof(type)); } - /// - /// Removes the for the given . - /// - /// The type to remove the converter for. - public void RemoveConverter(Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } + typeConverters.Remove(type); + } - typeConverters.Remove(type); - } + /// + /// Removes the for the given . + /// + /// The type to remove the converter for. + public void RemoveConverter() + { + RemoveConverter(typeof(T)); + } - /// - /// Removes the for the given . - /// - /// The type to remove the converter for. - public void RemoveConverter() + /// + /// Removes the ITypeConverterFactory. + /// + /// The ITypeConverterFactory to remove. + public void RemoveConverterFactory(ITypeConverterFactory typeConverterFactory) + { + typeConverterFactories.Remove(typeConverterFactory); + var toRemove = typeConverterFactoryCache.Where(pair => pair.Value == typeConverterFactory); + foreach (var pair in toRemove) { - RemoveConverter(typeof(T)); + typeConverterFactoryCache.Remove(pair.Key); } + } - /// - /// Removes the ITypeConverterFactory. - /// - /// The ITypeConverterFactory to remove. - public void RemoveConverterFactory(ITypeConverterFactory typeConverterFactory) + /// + /// Gets the converter for the given . + /// + /// The type to get the converter for. + /// The for the given . + public ITypeConverter GetConverter(Type type) + { + if (type == null) { - typeConverterFactories.Remove(typeConverterFactory); - var toRemove = typeConverterFactoryCache.Where(pair => pair.Value == typeConverterFactory); - foreach (var pair in toRemove) - { - typeConverterFactoryCache.Remove(pair.Key); - } + throw new ArgumentNullException(nameof(type)); } - /// - /// Gets the converter for the given . - /// - /// The type to get the converter for. - /// The for the given . - public ITypeConverter GetConverter(Type type) + if (typeConverters.TryGetValue(type, out ITypeConverter? typeConverter)) { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - if (typeConverters.TryGetValue(type, out ITypeConverter typeConverter)) - { - return typeConverter; - } - - if (!typeConverterFactoryCache.TryGetValue(type, out var factory)) - { - factory = typeConverterFactories.Concat(defaultTypeConverterFactories).FirstOrDefault(f => f.CanCreate(type)); - if (factory != null) - { - typeConverterFactoryCache[type] = factory; - } - } + return typeConverter; + } + if (!typeConverterFactoryCache.TryGetValue(type, out var factory)) + { + factory = typeConverterFactories.Concat(defaultTypeConverterFactories).FirstOrDefault(f => f.CanCreate(type)); if (factory != null) { - if (factory.Create(type, this, out typeConverter)) - { - AddConverter(type, typeConverter); - } - - return typeConverter; + typeConverterFactoryCache[type] = factory; } - - return new DefaultTypeConverter(); } - /// - /// Gets the converter for the given member. If an attribute is - /// found on the member, that will be used, otherwise the cache - /// will be used. - /// - /// The member to get the converter for. - public ITypeConverter GetConverter(MemberInfo member) + if (factory != null) { - var typeConverterAttribute = member.GetCustomAttribute(); - if (typeConverterAttribute != null) + if (factory.Create(type, this, out typeConverter)) { - return typeConverterAttribute.TypeConverter; + AddConverter(type, typeConverter); } - return GetConverter(member.MemberType()); + return typeConverter; } - /// - /// Gets the converter for the given . - /// - /// The type to get the converter for. - /// The for the given . - public ITypeConverter GetConverter() + return new DefaultTypeConverter(); + } + + /// + /// Gets the converter for the given member. If an attribute is + /// found on the member, that will be used, otherwise the cache + /// will be used. + /// + /// The member to get the converter for. + public ITypeConverter GetConverter(MemberInfo member) + { + var typeConverterAttribute = member.GetCustomAttribute(); + if (typeConverterAttribute != null) { - return GetConverter(typeof(T)); + return typeConverterAttribute.TypeConverter; } - private void CreateDefaultConverters() - { - AddConverter(typeof(BigInteger), new BigIntegerConverter()); - AddConverter(typeof(bool), new BooleanConverter()); - AddConverter(typeof(byte), new ByteConverter()); - AddConverter(typeof(byte[]), new ByteArrayConverter()); - AddConverter(typeof(char), new CharConverter()); - AddConverter(typeof(DateTime), new DateTimeConverter()); - AddConverter(typeof(DateTimeOffset), new DateTimeOffsetConverter()); - AddConverter(typeof(decimal), new DecimalConverter()); - AddConverter(typeof(double), new DoubleConverter()); - AddConverter(typeof(float), new SingleConverter()); - AddConverter(typeof(Guid), new GuidConverter()); - AddConverter(typeof(short), new Int16Converter()); - AddConverter(typeof(int), new Int32Converter()); - AddConverter(typeof(long), new Int64Converter()); - AddConverter(typeof(sbyte), new SByteConverter()); - AddConverter(typeof(string), new StringConverter()); - AddConverter(typeof(TimeSpan), new TimeSpanConverter()); - AddConverter(new NotSupportedTypeConverter()); - AddConverter(typeof(ushort), new UInt16Converter()); - AddConverter(typeof(uint), new UInt32Converter()); - AddConverter(typeof(ulong), new UInt64Converter()); - AddConverter(typeof(Uri), new UriConverter()); + return GetConverter(member.MemberType()); + } + + /// + /// Gets the converter for the given . + /// + /// The type to get the converter for. + /// The for the given . + public ITypeConverter GetConverter() + { + return GetConverter(typeof(T)); + } + + private void CreateDefaultConverters() + { + AddConverter(typeof(BigInteger), new BigIntegerConverter()); + AddConverter(typeof(bool), new BooleanConverter()); + AddConverter(typeof(byte), new ByteConverter()); + AddConverter(typeof(byte[]), new ByteArrayConverter()); + AddConverter(typeof(char), new CharConverter()); + AddConverter(typeof(DateTime), new DateTimeConverter()); + AddConverter(typeof(DateTimeOffset), new DateTimeOffsetConverter()); + AddConverter(typeof(decimal), new DecimalConverter()); + AddConverter(typeof(double), new DoubleConverter()); + AddConverter(typeof(float), new SingleConverter()); + AddConverter(typeof(Guid), new GuidConverter()); + AddConverter(typeof(short), new Int16Converter()); + AddConverter(typeof(int), new Int32Converter()); + AddConverter(typeof(long), new Int64Converter()); + AddConverter(typeof(sbyte), new SByteConverter()); + AddConverter(typeof(string), new StringConverter()); + AddConverter(typeof(TimeSpan), new TimeSpanConverter()); + AddConverter(new NotSupportedTypeConverter()); + AddConverter(typeof(ushort), new UInt16Converter()); + AddConverter(typeof(uint), new UInt32Converter()); + AddConverter(typeof(ulong), new UInt64Converter()); + AddConverter(typeof(Uri), new UriConverter()); #if NET6_0_OR_GREATER - AddConverter(typeof(DateOnly), new DateOnlyConverter()); - AddConverter(typeof(TimeOnly), new TimeOnlyConverter()); + AddConverter(typeof(DateOnly), new DateOnlyConverter()); + AddConverter(typeof(TimeOnly), new TimeOnlyConverter()); #endif - defaultTypeConverterFactories.Add(new EnumConverterFactory()); - defaultTypeConverterFactories.Add(new NullableConverterFactory()); - defaultTypeConverterFactories.Add(new CollectionConverterFactory()); - } + defaultTypeConverterFactories.Add(new EnumConverterFactory()); + defaultTypeConverterFactories.Add(new NullableConverterFactory()); + defaultTypeConverterFactories.Add(new CollectionConverterFactory()); } } diff --git a/src/CsvHelper/TypeConversion/TypeConverterException.cs b/src/CsvHelper/TypeConversion/TypeConverterException.cs index c7e80912b..6baca97d7 100644 --- a/src/CsvHelper/TypeConversion/TypeConverterException.cs +++ b/src/CsvHelper/TypeConversion/TypeConverterException.cs @@ -3,130 +3,128 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Represents errors that occur while reading a CSV file. +/// +[Serializable] +public class TypeConverterException : CsvHelperException { /// - /// Represents errors that occur while reading a CSV file. + /// The text used in ConvertFromString. /// - [Serializable] - public class TypeConverterException : CsvHelperException - { - /// - /// The text used in ConvertFromString. - /// - public string Text { get; private set; } + public string? Text { get; private set; } - /// - /// The value used in ConvertToString. - /// - public object Value { get; private set; } + /// + /// The value used in ConvertToString. + /// + public object? Value { get; private set; } - /// - /// The type converter. - /// - public ITypeConverter TypeConverter { get; private set; } + /// + /// The type converter. + /// + public ITypeConverter TypeConverter { get; private set; } - /// - /// The member map data used in ConvertFromString and ConvertToString. - /// - public MemberMapData MemberMapData { get; private set; } + /// + /// The member map data used in ConvertFromString and ConvertToString. + /// + public MemberMapData MemberMapData { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The type converter. - /// The member map data. - /// The text. - /// The reading context. - public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, string text, CsvContext context) : base(context) - { - TypeConverter = typeConverter; - MemberMapData = memberMapData; - Text = text; - } + /// + /// Initializes a new instance of the class. + /// + /// The type converter. + /// The member map data. + /// The text. + /// The reading context. + public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, string? text, CsvContext context) : base(context) + { + TypeConverter = typeConverter; + MemberMapData = memberMapData; + Text = text; + } - /// - /// Initializes a new instance of the class. - /// - /// The type converter. - /// The member map data. - /// The value. - /// The writing context. - public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, object value, CsvContext context) : base(context) - { - TypeConverter = typeConverter; - MemberMapData = memberMapData; - Value = value; - } + /// + /// Initializes a new instance of the class. + /// + /// The type converter. + /// The member map data. + /// The value. + /// The writing context. + public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, object? value, CsvContext context) : base(context) + { + TypeConverter = typeConverter; + MemberMapData = memberMapData; + Value = value; + } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The type converter. - /// The member map data. - /// The text. - /// The reading context. - /// The message that describes the error. - public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, string text, CsvContext context, string message) : base(context, message) - { - TypeConverter = typeConverter; - MemberMapData = memberMapData; - Text = text; - } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The type converter. + /// The member map data. + /// The text. + /// The reading context. + /// The message that describes the error. + public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, string? text, CsvContext context, string message) : base(context, message) + { + TypeConverter = typeConverter; + MemberMapData = memberMapData; + Text = text; + } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The type converter. - /// The member map data. - /// The value. - /// The writing context. - /// The message that describes the error. - public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, object value, CsvContext context, string message) : base(context, message) - { - TypeConverter = typeConverter; - MemberMapData = memberMapData; - Value = value; - } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The type converter. + /// The member map data. + /// The value. + /// The writing context. + /// The message that describes the error. + public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, object? value, CsvContext context, string message) : base(context, message) + { + TypeConverter = typeConverter; + MemberMapData = memberMapData; + Value = value; + } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The type converter. - /// The member map data. - /// The text. - /// The reading context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, string text, CsvContext context, string message, Exception innerException) : base(context, message, innerException) - { - TypeConverter = typeConverter; - MemberMapData = memberMapData; - Text = text; - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The type converter. + /// The member map data. + /// The text. + /// The reading context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, string? text, CsvContext context, string message, Exception innerException) : base(context, message, innerException) + { + TypeConverter = typeConverter; + MemberMapData = memberMapData; + Text = text; + } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The type converter. - /// The member map data. - /// The value. - /// The writing context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, object value, CsvContext context, string message, Exception innerException) : base(context, message, innerException) - { - TypeConverter = typeConverter; - MemberMapData = memberMapData; - Value = value; - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The type converter. + /// The member map data. + /// The value. + /// The writing context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public TypeConverterException(ITypeConverter typeConverter, MemberMapData memberMapData, object? value, CsvContext context, string message, Exception innerException) : base(context, message, innerException) + { + TypeConverter = typeConverter; + MemberMapData = memberMapData; + Value = value; } } diff --git a/src/CsvHelper/TypeConversion/TypeConverterOptions.cs b/src/CsvHelper/TypeConversion/TypeConverterOptions.cs index 6b6d53080..ec4f4355f 100644 --- a/src/CsvHelper/TypeConversion/TypeConverterOptions.cs +++ b/src/CsvHelper/TypeConversion/TypeConverterOptions.cs @@ -2,156 +2,152 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Options used when doing type conversion. +/// +public class TypeConverterOptions { + private static readonly string[] defaultBooleanTrueValues = { }; + private static readonly string[] defaultBooleanFalseValues = { }; + private static readonly string[] defaultNullValues = { }; + + /// + /// Gets or sets the culture info. + /// + public CultureInfo? CultureInfo { get; set; } + + /// + /// Gets or sets the date time style. + /// + public DateTimeStyles? DateTimeStyle { get; set; } + + /// + /// Gets or sets the time span style. + /// + public TimeSpanStyles? TimeSpanStyle { get; set; } + + /// + /// Gets or sets the number style. + /// + public NumberStyles? NumberStyles { get; set; } + + /// + /// Gets or sets the string format. + /// + public string[]? Formats { get; set; } + + /// + /// Gets or sets the . + /// + public UriKind? UriKind { get; set; } + + /// + /// Ingore case when parsing enums. Default is false. + /// + public bool? EnumIgnoreCase { get; set; } + + /// + /// Gets the list of values that can be + /// used to represent a boolean of true. + /// + public List BooleanTrueValues { get; } = new List(defaultBooleanTrueValues); + + /// + /// Gets the list of values that can be + /// used to represent a boolean of false. + /// + public List BooleanFalseValues { get; } = new List(defaultBooleanFalseValues); + + /// + /// Gets the list of values that can be used to represent a null value. + /// + public List NullValues { get; } = new List(defaultNullValues); + /// - /// Options used when doing type conversion. + /// Merges TypeConverterOptions by applying the values of sources in order on to each other. + /// The first object is the source object. /// - public class TypeConverterOptions + /// The sources that will be applied. + /// The updated source object. + public static TypeConverterOptions Merge(params TypeConverterOptions[] sources) { - private static readonly string[] defaultBooleanTrueValues = { }; - private static readonly string[] defaultBooleanFalseValues = { }; - private static readonly string[] defaultNullValues = { }; - - /// - /// Gets or sets the culture info. - /// - public CultureInfo CultureInfo { get; set; } - - /// - /// Gets or sets the date time style. - /// - public DateTimeStyles? DateTimeStyle { get; set; } - - /// - /// Gets or sets the time span style. - /// - public TimeSpanStyles? TimeSpanStyle { get; set; } - - /// - /// Gets or sets the number style. - /// - public NumberStyles? NumberStyles { get; set; } - - /// - /// Gets or sets the string format. - /// - public string[] Formats { get; set; } - - /// - /// Gets or sets the . - /// - public UriKind? UriKind { get; set; } - - /// - /// Ingore case when parsing enums. Default is false. - /// - public bool? EnumIgnoreCase { get; set; } - - /// - /// Gets the list of values that can be - /// used to represent a boolean of true. - /// - public List BooleanTrueValues { get; } = new List(defaultBooleanTrueValues); - - /// - /// Gets the list of values that can be - /// used to represent a boolean of false. - /// - public List BooleanFalseValues { get; } = new List(defaultBooleanFalseValues); - - /// - /// Gets the list of values that can be used to represent a null value. - /// - public List NullValues { get; } = new List(defaultNullValues); - - /// - /// Merges TypeConverterOptions by applying the values of sources in order on to each other. - /// The first object is the source object. - /// - /// The sources that will be applied. - /// The updated source object. - public static TypeConverterOptions Merge(params TypeConverterOptions[] sources) + if (sources.Length == 0) { - if (sources == null || sources.Length == 0) + throw new InvalidOperationException("At least one source must be provided."); + } + + var options = sources[0]; + + for (var i = 1; i < sources.Length; i++) + { + var source = sources[i]; + + if (source == null) + { + continue; + } + + if (source.CultureInfo != null) + { + options.CultureInfo = source.CultureInfo; + } + + if (source.DateTimeStyle != null) + { + options.DateTimeStyle = source.DateTimeStyle; + } + + if (source.TimeSpanStyle != null) + { + options.TimeSpanStyle = source.TimeSpanStyle; + } + + if (source.NumberStyles != null) + { + options.NumberStyles = source.NumberStyles; + } + + if (source.Formats != null) + { + options.Formats = source.Formats; + } + + if (source.UriKind != null) + { + options.UriKind = source.UriKind; + } + + if (source.EnumIgnoreCase != null) { - return null; + options.EnumIgnoreCase = source.EnumIgnoreCase; } - var options = sources[0]; + // Only change the values if they are different than the defaults. + // This means there were explicit changes made to the options. + + if (!defaultBooleanTrueValues.SequenceEqual(source.BooleanTrueValues)) + { + options.BooleanTrueValues.Clear(); + options.BooleanTrueValues.AddRange(source.BooleanTrueValues); + } - for (var i = 1; i < sources.Length; i++) + if (!defaultBooleanFalseValues.SequenceEqual(source.BooleanFalseValues)) { - var source = sources[i]; - - if (source == null) - { - continue; - } - - if (source.CultureInfo != null) - { - options.CultureInfo = source.CultureInfo; - } - - if (source.DateTimeStyle != null) - { - options.DateTimeStyle = source.DateTimeStyle; - } - - if (source.TimeSpanStyle != null) - { - options.TimeSpanStyle = source.TimeSpanStyle; - } - - if (source.NumberStyles != null) - { - options.NumberStyles = source.NumberStyles; - } - - if (source.Formats != null) - { - options.Formats = source.Formats; - } - - if (source.UriKind != null) - { - options.UriKind = source.UriKind; - } - - if (source.EnumIgnoreCase != null) - { - options.EnumIgnoreCase = source.EnumIgnoreCase; - } - - // Only change the values if they are different than the defaults. - // This means there were explicit changes made to the options. - - if (!defaultBooleanTrueValues.SequenceEqual(source.BooleanTrueValues)) - { - options.BooleanTrueValues.Clear(); - options.BooleanTrueValues.AddRange(source.BooleanTrueValues); - } - - if (!defaultBooleanFalseValues.SequenceEqual(source.BooleanFalseValues)) - { - options.BooleanFalseValues.Clear(); - options.BooleanFalseValues.AddRange(source.BooleanFalseValues); - } - - if (!defaultNullValues.SequenceEqual(source.NullValues)) - { - options.NullValues.Clear(); - options.NullValues.AddRange(source.NullValues); - } + options.BooleanFalseValues.Clear(); + options.BooleanFalseValues.AddRange(source.BooleanFalseValues); } - return options; + if (!defaultNullValues.SequenceEqual(source.NullValues)) + { + options.NullValues.Clear(); + options.NullValues.AddRange(source.NullValues); + } } + + return options; } } diff --git a/src/CsvHelper/TypeConversion/TypeConverterOptionsCache.cs b/src/CsvHelper/TypeConversion/TypeConverterOptionsCache.cs index 9ce528af9..116e70fe3 100644 --- a/src/CsvHelper/TypeConversion/TypeConverterOptionsCache.cs +++ b/src/CsvHelper/TypeConversion/TypeConverterOptionsCache.cs @@ -2,107 +2,103 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; -using System.Collections.Generic; +namespace CsvHelper.TypeConversion; -namespace CsvHelper.TypeConversion +/// +/// Caches for a given type. +/// +public class TypeConverterOptionsCache { + private Dictionary typeConverterOptions = new Dictionary(); + /// - /// Caches for a given type. + /// Adds the for the given . /// - public class TypeConverterOptionsCache + /// The type the options are for. + /// The options. + public void AddOptions(Type type, TypeConverterOptions options) { - private Dictionary typeConverterOptions = new Dictionary(); - - /// - /// Adds the for the given . - /// - /// The type the options are for. - /// The options. - public void AddOptions(Type type, TypeConverterOptions options) + if (type == null) { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - typeConverterOptions[type] = options ?? throw new ArgumentNullException(nameof(options)); + throw new ArgumentNullException(nameof(type)); } - /// - /// Adds the for the given . - /// - /// The type the options are for. - /// The options. - public void AddOptions(TypeConverterOptions options) - { - AddOptions(typeof(T), options); - } + typeConverterOptions[type] = options ?? throw new ArgumentNullException(nameof(options)); + } - /// - /// Adds the given to all registered types. - /// - /// - public void AddOptions(TypeConverterOptions options) - { - foreach (var type in typeConverterOptions.Keys) - { - typeConverterOptions[type] = options; - } - } + /// + /// Adds the for the given . + /// + /// The type the options are for. + /// The options. + public void AddOptions(TypeConverterOptions options) + { + AddOptions(typeof(T), options); + } - /// - /// Removes the for the given type. - /// - /// The type to remove the options for. - public void RemoveOptions(Type type) + /// + /// Adds the given to all registered types. + /// + /// + public void AddOptions(TypeConverterOptions options) + { + foreach (var type in typeConverterOptions.Keys) { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - typeConverterOptions.Remove(type); + typeConverterOptions[type] = options; } + } - /// - /// Removes the for the given type. - /// - /// The type to remove the options for. - public void RemoveOptions() + /// + /// Removes the for the given type. + /// + /// The type to remove the options for. + public void RemoveOptions(Type type) + { + if (type == null) { - RemoveOptions(typeof(T)); + throw new ArgumentNullException(nameof(type)); } - /// - /// Get the for the given . - /// - /// The type the options are for. - /// The options for the given type. - public TypeConverterOptions GetOptions(Type type) - { - if (type == null) - { - throw new ArgumentNullException(); - } + typeConverterOptions.Remove(type); + } - if (!typeConverterOptions.TryGetValue(type, out var options)) - { - options = new TypeConverterOptions(); - typeConverterOptions.Add(type, options); - } + /// + /// Removes the for the given type. + /// + /// The type to remove the options for. + public void RemoveOptions() + { + RemoveOptions(typeof(T)); + } - return options; + /// + /// Get the for the given . + /// + /// The type the options are for. + /// The options for the given type. + public TypeConverterOptions GetOptions(Type type) + { + if (type == null) + { + throw new ArgumentNullException(); } - /// - /// Get the for the given . - /// - /// The type the options are for. - /// The options for the given type. - public TypeConverterOptions GetOptions() + if (!typeConverterOptions.TryGetValue(type, out var options)) { - return GetOptions(typeof(T)); + options = new TypeConverterOptions(); + typeConverterOptions.Add(type, options); } + + return options; + } + + /// + /// Get the for the given . + /// + /// The type the options are for. + /// The options for the given type. + public TypeConverterOptions GetOptions() + { + return GetOptions(typeof(T)); } } diff --git a/src/CsvHelper/TypeConversion/UInt16Converter.cs b/src/CsvHelper/TypeConversion/UInt16Converter.cs index 771624ac6..ae7046f67 100644 --- a/src/CsvHelper/TypeConversion/UInt16Converter.cs +++ b/src/CsvHelper/TypeConversion/UInt16Converter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class UInt16Converter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class UInt16Converter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (ushort.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var us)) - { - return us; - } - - return base.ConvertFromString(text, row, memberMapData); + if (ushort.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var us)) + { + return us; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/UInt32Converter.cs b/src/CsvHelper/TypeConversion/UInt32Converter.cs index 0997ea120..a8723d01e 100644 --- a/src/CsvHelper/TypeConversion/UInt32Converter.cs +++ b/src/CsvHelper/TypeConversion/UInt32Converter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class UInt32Converter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class UInt32Converter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (uint.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var ui)) - { - return ui; - } - - return base.ConvertFromString(text, row, memberMapData); + if (uint.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var ui)) + { + return ui; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/UInt64Converter.cs b/src/CsvHelper/TypeConversion/UInt64Converter.cs index 3eb837e34..6cb5fe088 100644 --- a/src/CsvHelper/TypeConversion/UInt64Converter.cs +++ b/src/CsvHelper/TypeConversion/UInt64Converter.cs @@ -2,33 +2,32 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System.Globalization; using CsvHelper.Configuration; +using System.Globalization; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class UInt64Converter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the string to an object. /// - public class UInt64Converter : DefaultTypeConverter + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// The object created from the string. + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { - /// - /// Converts the string to an object. - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// The object created from the string. - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; + var numberStyle = memberMapData.TypeConverterOptions.NumberStyles ?? NumberStyles.Integer; - if (ulong.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var ul)) - { - return ul; - } - - return base.ConvertFromString(text, row, memberMapData); + if (ulong.TryParse(text, numberStyle, memberMapData.TypeConverterOptions.CultureInfo, out var ul)) + { + return ul; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/TypeConversion/UriConverter.cs b/src/CsvHelper/TypeConversion/UriConverter.cs index 6a245bdec..5af3f733b 100644 --- a/src/CsvHelper/TypeConversion/UriConverter.cs +++ b/src/CsvHelper/TypeConversion/UriConverter.cs @@ -3,38 +3,32 @@ // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using CsvHelper.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CsvHelper.TypeConversion +namespace CsvHelper.TypeConversion; + +/// +/// Converts a to and from a . +/// +public class UriConverter : DefaultTypeConverter { /// - /// Converts a to and from a . + /// Converts the to a . /// - public class UriConverter : DefaultTypeConverter - { - /// - /// Converts the to a . - /// - /// The string to convert to an object. - /// The for the current record. - /// The for the member being created. - /// - /// The created from the string. - /// - public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - var uriKind = memberMapData.TypeConverterOptions.UriKind ?? default; - - if (Uri.TryCreate(text, uriKind, out var uri)) - { - return uri; - } + /// The string to convert to an object. + /// The for the current record. + /// The for the member being created. + /// + /// The created from the string. + /// + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) + { + var uriKind = memberMapData.TypeConverterOptions.UriKind ?? default; - return base.ConvertFromString(text, row, memberMapData); + if (Uri.TryCreate(text, uriKind, out var uri)) + { + return uri; } + + return base.ConvertFromString(text, row, memberMapData); } } diff --git a/src/CsvHelper/ValidationException.cs b/src/CsvHelper/ValidationException.cs index 10fc96dd9..8ffcbbc4d 100644 --- a/src/CsvHelper/ValidationException.cs +++ b/src/CsvHelper/ValidationException.cs @@ -2,38 +2,35 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents a user supplied validation failure. +/// +[Serializable] +public abstract class ValidationException : CsvHelperException { /// - /// Represents a user supplied validation failure. + /// Initializes a new instance of the class. /// - [Serializable] - public abstract class ValidationException : CsvHelperException - { - /// - /// Initializes a new instance of the class. - /// - /// The reading context. - public ValidationException(CsvContext context) : base(context) { } + /// The reading context. + public ValidationException(CsvContext context) : base(context) { } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The reading context. - /// The message that describes the error. - public ValidationException(CsvContext context, string message) : base(context, message) { } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The reading context. + /// The message that describes the error. + public ValidationException(CsvContext context, string message) : base(context, message) { } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The reading context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public ValidationException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The reading context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public ValidationException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } } diff --git a/src/CsvHelper/WriterException.cs b/src/CsvHelper/WriterException.cs index 2ef30b3f1..c0abf31f6 100644 --- a/src/CsvHelper/WriterException.cs +++ b/src/CsvHelper/WriterException.cs @@ -2,38 +2,35 @@ // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper -using System; +namespace CsvHelper; -namespace CsvHelper +/// +/// Represents errors that occur while writing a CSV file. +/// +[Serializable] +public class WriterException : CsvHelperException { /// - /// Represents errors that occur while writing a CSV file. + /// Initializes a new instance of the class. /// - [Serializable] - public class WriterException : CsvHelperException - { - /// - /// Initializes a new instance of the class. - /// - /// The writing context. - public WriterException(CsvContext context) : base(context) { } + /// The writing context. + public WriterException(CsvContext context) : base(context) { } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The writing context. - /// The message that describes the error. - public WriterException(CsvContext context, string message) : base(context, message) { } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The writing context. + /// The message that describes the error. + public WriterException(CsvContext context, string message) : base(context, message) { } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that - /// is the cause of this exception. - /// - /// The writing context. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public WriterException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that + /// is the cause of this exception. + /// + /// The writing context. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public WriterException(CsvContext context, string message, Exception innerException) : base(context, message, innerException) { } }