summaryrefslogtreecommitdiff
path: root/ThirdParty/CsvHelper-master/src/CsvHelper/CsvReader.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ThirdParty/CsvHelper-master/src/CsvHelper/CsvReader.cs')
-rw-r--r--ThirdParty/CsvHelper-master/src/CsvHelper/CsvReader.cs1419
1 files changed, 1419 insertions, 0 deletions
diff --git a/ThirdParty/CsvHelper-master/src/CsvHelper/CsvReader.cs b/ThirdParty/CsvHelper-master/src/CsvHelper/CsvReader.cs
new file mode 100644
index 0000000..2b8ee84
--- /dev/null
+++ b/ThirdParty/CsvHelper-master/src/CsvHelper/CsvReader.cs
@@ -0,0 +1,1419 @@
+// Copyright 2009-2022 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;
+using System.Collections.Generic;
+using System.IO;
+using CsvHelper.Configuration;
+using CsvHelper.TypeConversion;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using CsvHelper.Expressions;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Configuration;
+
+namespace CsvHelper
+{
+ /// <summary>
+ /// Reads data that was parsed from <see cref="IParser" />.
+ /// </summary>
+ public class CsvReader : IReader
+ {
+ private readonly Lazy<RecordManager> recordManager;
+ private readonly bool detectColumnCountChanges;
+ private readonly Dictionary<string, List<int>> namedIndexes = new Dictionary<string, List<int>>();
+ private readonly Dictionary<string, (string, int)> namedIndexCache = new Dictionary<string, (string, int)>();
+ private readonly Dictionary<Type, TypeConverterOptions> typeConverterOptionsCache = new Dictionary<Type, TypeConverterOptions>();
+ 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 columnCount;
+ private int currentIndex = -1;
+ private bool hasBeenRead;
+ private string[]? headerRecord;
+
+ /// <inheritdoc/>
+ public virtual int ColumnCount => columnCount;
+
+ /// <inheritdoc/>
+ public virtual int CurrentIndex => currentIndex;
+
+ /// <inheritdoc/>
+ public virtual string[]? HeaderRecord => headerRecord;
+
+ /// <inheritdoc/>
+ public virtual CsvContext Context => context;
+
+ /// <inheritdoc/>
+ public virtual IReaderConfiguration Configuration { get; private set; }
+
+ /// <inheritdoc/>
+ public virtual IParser Parser => parser;
+
+ /// <summary>
+ /// Creates a new CSV reader using the given <see cref="TextReader" />.
+ /// </summary>
+ /// <param name="reader">The reader.</param>
+ /// <param name="culture">The culture.</param>
+ /// <param name="leaveOpen"><c>true</c> to leave the <see cref="TextReader"/> open after the <see cref="CsvReader"/> object is disposed, otherwise <c>false</c>.</param>
+ public CsvReader(TextReader reader, CultureInfo culture, bool leaveOpen = false) : this(new CsvParser(reader, culture, leaveOpen)) { }
+
+ /// <summary>
+ /// Creates a new CSV reader using the given <see cref="TextReader" /> and
+ /// <see cref="CsvHelper.Configuration.CsvConfiguration" /> and <see cref="CsvParser" /> as the default parser.
+ /// </summary>
+ /// <param name="reader">The reader.</param>
+ /// <param name="configuration">The configuration.</param>
+ /// <param name="leaveOpen"><c>true</c> to leave the <see cref="TextReader"/> open after the <see cref="CsvReader"/> object is disposed, otherwise <c>false</c>.</param>
+ public CsvReader(TextReader reader, IReaderConfiguration configuration, bool leaveOpen = false) : this(new CsvParser(reader, configuration, leaveOpen)) { }
+
+ /// <summary>
+ /// Creates a new CSV reader using the given <see cref="IParser" />.
+ /// </summary>
+ /// <param name="parser">The <see cref="IParser" /> used to parse the CSV file.</param>
+ 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<RecordManager>(() => ObjectResolver.Current.Resolve<RecordManager>(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;
+ }
+
+ /// <inheritdoc/>
+ public virtual bool ReadHeader()
+ {
+ if (!hasHeaderRecord)
+ {
+ throw new ReaderException(context, "Configuration.HasHeaderRecord is false.");
+ }
+
+ headerRecord = parser.Record;
+ ParseNamedIndexes();
+
+ return headerRecord != null;
+ }
+
+ /// <summary>
+ /// Validates the header to be of the given type.
+ /// </summary>
+ /// <typeparam name="T">The expected type of the header</typeparam>
+ public virtual void ValidateHeader<T>()
+ {
+ ValidateHeader(typeof(T));
+ }
+
+ /// <summary>
+ /// Validates the header to be of the given type.
+ /// </summary>
+ /// <param name="type">The expected type of the header.</param>
+ 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.");
+ }
+
+ CheckHasBeenRead();
+
+ if (headerRecord == null)
+ {
+ throw new InvalidOperationException($"The header must be read before it can be validated.");
+ }
+
+ if (context.Maps[type] == null)
+ {
+ context.Maps.Add(context.AutoMap(type));
+ }
+
+ var map = context.Maps[type];
+ var invalidHeaders = new List<InvalidHeader>();
+ ValidateHeader(map, invalidHeaders);
+
+ var args = new HeaderValidatedArgs(invalidHeaders.ToArray(), context);
+ headerValidated?.Invoke(args);
+ }
+
+ /// <summary>
+ /// Validates the header to be of the given type.
+ /// </summary>
+ /// <param name="map">The mapped classes.</param>
+ /// <param name="invalidHeaders">The invalid headers.</param>
+ protected virtual void ValidateHeader(ClassMap map, List<InvalidHeader> invalidHeaders)
+ {
+ foreach (var parameter in map.ParameterMaps)
+ {
+ if (parameter.Data.Ignore)
+ {
+ continue;
+ }
+
+ if (parameter.Data.IsConstantSet)
+ {
+ // If ConvertUsing and Constant don't require a header.
+ continue;
+ }
+
+ if (parameter.Data.IsIndexSet && !parameter.Data.IsNameSet)
+ {
+ // If there is only an index set, we don't want to validate the header name.
+ continue;
+ }
+
+ 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() });
+ }
+ }
+ }
+
+ foreach (var memberMap in map.MemberMaps)
+ {
+ 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() });
+ }
+ }
+
+ foreach (var referenceMap in map.ReferenceMaps)
+ {
+ if (!CanRead(referenceMap))
+ {
+ continue;
+ }
+
+ ValidateHeader(referenceMap.Data.Mapping, invalidHeaders);
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual bool Read()
+ {
+ // Don't forget about the async method below!
+
+ bool hasMoreRecords;
+ do
+ {
+ hasMoreRecords = parser.Read();
+ hasBeenRead = true;
+ }
+ while (hasMoreRecords && (shouldSkipRecord?.Invoke(new ShouldSkipRecordArgs(this)) ?? false));
+
+ currentIndex = -1;
+
+ if (detectColumnCountChanges && hasMoreRecords)
+ {
+ if (columnCount > 0 && columnCount != 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;
+ }
+ }
+
+ columnCount = parser.Count;
+ }
+
+ return hasMoreRecords;
+ }
+
+ /// <inheritdoc/>
+ public virtual async Task<bool> ReadAsync()
+ {
+ bool hasMoreRecords;
+ do
+ {
+ hasMoreRecords = await parser.ReadAsync().ConfigureAwait(false);
+ hasBeenRead = true;
+ }
+ while (hasMoreRecords && (shouldSkipRecord?.Invoke(new ShouldSkipRecordArgs(this)) ?? false));
+
+ currentIndex = -1;
+
+ if (detectColumnCountChanges && hasMoreRecords)
+ {
+ if (columnCount > 0 && columnCount != 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;
+ }
+ }
+
+ columnCount = parser.Count;
+ }
+
+ return hasMoreRecords;
+ }
+
+ /// <inheritdoc/>
+ public virtual string? this[int index]
+ {
+ get
+ {
+ CheckHasBeenRead();
+
+ return GetField(index);
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual string? this[string name]
+ {
+ get
+ {
+ CheckHasBeenRead();
+
+ return GetField(name);
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual string? this[string name, int index]
+ {
+ get
+ {
+ CheckHasBeenRead();
+
+ return GetField(name, index);
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual string? GetField(int index)
+ {
+ CheckHasBeenRead();
+
+ // Set the current index being used so we
+ // have more information if an error occurs
+ // when reading records.
+ currentIndex = index;
+
+ if (index >= parser.Count || index < 0)
+ {
+ var args = new MissingFieldFoundArgs(null, index, context);
+ missingFieldFound?.Invoke(args);
+ return default;
+ }
+
+ var field = parser[index];
+
+ return field;
+ }
+
+ /// <inheritdoc/>
+ public virtual string? GetField(string name)
+ {
+ CheckHasBeenRead();
+
+ var index = GetFieldIndex(name);
+ if (index < 0)
+ {
+ return null;
+ }
+
+ return GetField(index);
+ }
+
+ /// <inheritdoc/>
+ public virtual string? GetField(string name, int index)
+ {
+ CheckHasBeenRead();
+
+ var fieldIndex = GetFieldIndex(name, index);
+ if (fieldIndex < 0)
+ {
+ return null;
+ }
+
+ return GetField(fieldIndex);
+ }
+
+ /// <inheritdoc/>
+ public virtual object? GetField(Type type, int index)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter(type);
+ return GetField(type, index, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual object? GetField(Type type, string name)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter(type);
+ return GetField(type, name, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual object? GetField(Type type, string name, int index)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter(type);
+ return GetField(type, name, index, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual object? GetField(Type type, int index, ITypeConverter converter)
+ {
+ CheckHasBeenRead();
+
+ 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);
+ }
+
+ reusableMemberMapData.TypeConverterOptions = typeConverterOptions;
+
+ var field = GetField(index);
+ return converter.ConvertFromString(field, this, reusableMemberMapData);
+ }
+
+ /// <inheritdoc/>
+ public virtual object? GetField(Type type, string name, ITypeConverter converter)
+ {
+ CheckHasBeenRead();
+
+ var index = GetFieldIndex(name);
+ return GetField(type, index, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual object? GetField(Type type, string name, int index, ITypeConverter converter)
+ {
+ CheckHasBeenRead();
+
+ var fieldIndex = GetFieldIndex(name, index);
+ return GetField(type, fieldIndex, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T>(int index)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter<T>();
+ return GetField<T>(index, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T>(string name)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter<T>();
+ return GetField<T>(name, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T>(string name, int index)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter<T>();
+ return GetField<T>(name, index, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T>(int index, ITypeConverter converter)
+ {
+ CheckHasBeenRead();
+
+ if (index >= parser.Count || index < 0)
+ {
+ currentIndex = index;
+ var args = new MissingFieldFoundArgs(null, index, context);
+ missingFieldFound?.Invoke(args);
+
+ return default;
+ }
+
+ return (T)GetField(typeof(T), index, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T>(string name, ITypeConverter converter)
+ {
+ CheckHasBeenRead();
+
+ var index = GetFieldIndex(name);
+ return GetField<T>(index, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T>(string name, int index, ITypeConverter converter)
+ {
+ CheckHasBeenRead();
+
+ var fieldIndex = GetFieldIndex(name, index);
+ return GetField<T>(fieldIndex, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T, TConverter>(int index) where TConverter : ITypeConverter
+ {
+ CheckHasBeenRead();
+
+ var converter = ObjectResolver.Current.Resolve<TConverter>();
+ return GetField<T>(index, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T, TConverter>(string name) where TConverter : ITypeConverter
+ {
+ CheckHasBeenRead();
+
+ var converter = ObjectResolver.Current.Resolve<TConverter>();
+ return GetField<T>(name, converter);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetField<T, TConverter>(string name, int index) where TConverter : ITypeConverter
+ {
+ CheckHasBeenRead();
+
+ var converter = ObjectResolver.Current.Resolve<TConverter>();
+ return GetField<T>(name, index, converter);
+ }
+
+ /// <inheritdoc/>
+ 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);
+ }
+
+ /// <inheritdoc/>
+ 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);
+ }
+
+ /// <inheritdoc/>
+ 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);
+ }
+
+ /// <inheritdoc/>
+ 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
+ {
+ field = GetField(type, index, converter);
+ return true;
+ }
+ catch
+ {
+ field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null;
+ return false;
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField(Type type, string name, ITypeConverter converter, out object? field)
+ {
+ CheckHasBeenRead();
+
+ var index = GetFieldIndex(name, isTryGet: true);
+ if (index == -1)
+ {
+ field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null;
+ return false;
+ }
+
+ return TryGetField(type, index, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ 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)
+ {
+ field = type.GetTypeInfo().IsValueType ? ObjectResolver.Current.Resolve(type) : null;
+ return false;
+ }
+
+ return TryGetField(type, fieldIndex, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T>(int index, out T? field)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter<T>();
+ return TryGetField(index, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T>(string name, out T? field)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter<T>();
+ return TryGetField(name, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T>(string name, int index, out T? field)
+ {
+ CheckHasBeenRead();
+
+ var converter = context.TypeConverterCache.GetConverter<T>();
+ return TryGetField(name, index, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T>(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<T>(index, converter);
+ return true;
+ }
+ catch
+ {
+ field = default;
+ return false;
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T>(string name, ITypeConverter converter, out T? field)
+ {
+ CheckHasBeenRead();
+
+ var index = GetFieldIndex(name, isTryGet: true);
+ if (index == -1)
+ {
+ field = default;
+ return false;
+ }
+
+ return TryGetField(index, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T>(string name, int index, ITypeConverter converter, out T? field)
+ {
+ CheckHasBeenRead();
+
+ var fieldIndex = GetFieldIndex(name, index, true);
+ if (fieldIndex == -1)
+ {
+ field = default;
+ return false;
+ }
+
+ return TryGetField(fieldIndex, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T, TConverter>(int index, out T? field) where TConverter : ITypeConverter
+ {
+ CheckHasBeenRead();
+
+ var converter = ObjectResolver.Current.Resolve<TConverter>();
+ return TryGetField(index, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T, TConverter>(string name, out T? field) where TConverter : ITypeConverter
+ {
+ CheckHasBeenRead();
+
+ var converter = ObjectResolver.Current.Resolve<TConverter>();
+ return TryGetField(name, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual bool TryGetField<T, TConverter>(string name, int index, out T? field) where TConverter : ITypeConverter
+ {
+ CheckHasBeenRead();
+
+ var converter = ObjectResolver.Current.Resolve<TConverter>();
+ return TryGetField(name, index, converter, out field);
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetRecord<T>()
+ {
+ CheckHasBeenRead();
+
+ if (headerRecord == null && hasHeaderRecord)
+ {
+ ReadHeader();
+ ValidateHeader<T>();
+
+ if (!Read())
+ {
+ return default;
+ }
+ }
+
+ T record;
+ try
+ {
+ record = recordManager.Value.Create<T>();
+ }
+ 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;
+ }
+ }
+
+ record = default;
+ }
+
+ return record;
+ }
+
+ /// <inheritdoc/>
+ public virtual T? GetRecord<T>(T anonymousTypeDefinition)
+ {
+ if (anonymousTypeDefinition == null)
+ {
+ throw new ArgumentNullException(nameof(anonymousTypeDefinition));
+ }
+
+ if (!anonymousTypeDefinition.GetType().IsAnonymous())
+ {
+ throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition));
+ }
+
+ return GetRecord<T>();
+ }
+
+ /// <inheritdoc/>
+ public virtual object? GetRecord(Type type)
+ {
+ CheckHasBeenRead();
+
+ if (headerRecord == null && hasHeaderRecord)
+ {
+ ReadHeader();
+ ValidateHeader(type);
+
+ if (!Read())
+ {
+ return null;
+ }
+ }
+
+ object record;
+ try
+ {
+ record = recordManager.Value.Create(type);
+ }
+ 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;
+ }
+ }
+
+ record = default;
+ }
+
+ return record;
+ }
+
+ /// <inheritdoc/>
+ public virtual IEnumerable<T> GetRecords<T>()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(nameof(CsvReader),
+ "GetRecords<T>() returns an IEnumerable<T> 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<T>();
+ }
+
+ while (Read())
+ {
+ T record;
+ try
+ {
+ record = recordManager.Value.Create<T>();
+ }
+ 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;
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual IEnumerable<T> GetRecords<T>(T anonymousTypeDefinition)
+ {
+ if (anonymousTypeDefinition == null)
+ {
+ throw new ArgumentNullException(nameof(anonymousTypeDefinition));
+ }
+
+ if (!anonymousTypeDefinition.GetType().IsAnonymous())
+ {
+ throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition));
+ }
+
+ return GetRecords<T>();
+ }
+
+ /// <inheritdoc/>
+ public virtual IEnumerable<object?> GetRecords(Type type)
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(nameof(CsvReader),
+ "GetRecords<object>() returns an IEnumerable<T> 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(type);
+ }
+
+ while (Read())
+ {
+ object? record;
+ try
+ {
+ record = recordManager.Value.Create(type);
+ }
+ 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;
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual IEnumerable<T> EnumerateRecords<T>(T record)
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(nameof(CsvReader),
+ "GetRecords<T>() returns an IEnumerable<T> 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<T>();
+ }
+
+ while (Read())
+ {
+ 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;
+ }
+ }
+
+ // If the callback doesn't throw, keep going.
+ continue;
+ }
+
+ yield return record;
+ }
+ }
+
+#if !NET45
+ /// <inheritdoc/>
+ public virtual async IAsyncEnumerable<T> GetRecordsAsync<T>([EnumeratorCancellation] CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(nameof(CsvReader),
+ "GetRecords<T>() returns an IEnumerable<T> 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<T>();
+ }
+
+ while (await ReadAsync().ConfigureAwait(false))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ T record;
+ try
+ {
+ record = recordManager.Value.Create<T>();
+ }
+ 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;
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual IAsyncEnumerable<T> GetRecordsAsync<T>(T anonymousTypeDefinition, CancellationToken cancellationToken = default)
+ {
+ if (anonymousTypeDefinition == null)
+ {
+ throw new ArgumentNullException(nameof(anonymousTypeDefinition));
+ }
+
+ if (!anonymousTypeDefinition.GetType().IsAnonymous())
+ {
+ throw new ArgumentException($"Argument is not an anonymous type.", nameof(anonymousTypeDefinition));
+ }
+
+ return GetRecordsAsync<T>(cancellationToken);
+ }
+
+ /// <inheritdoc/>
+ public virtual async IAsyncEnumerable<object?> GetRecordsAsync(Type type, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(nameof(CsvReader),
+ "GetRecords<object>() returns an IEnumerable<T> 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(type);
+ }
+
+ while (await ReadAsync().ConfigureAwait(false))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ object record;
+ try
+ {
+ record = recordManager.Value.Create(type);
+ }
+ 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;
+ }
+ }
+
+ /// <inheritdoc/>
+ public virtual async IAsyncEnumerable<T> EnumerateRecordsAsync<T>(T record, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(nameof(CsvReader),
+ "GetRecords<T>() returns an IEnumerable<T> 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<T>();
+ }
+
+ 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;
+ }
+ }
+
+ // If the callback doesn't throw, keep going.
+ continue;
+ }
+
+ yield return record;
+ }
+ }
+#endif
+
+ /// <summary>
+ /// Gets the index of the field with the given name.
+ /// </summary>
+ /// <param name="name">The name of the field.</param>
+ /// <param name="index">The index of the field.</param>
+ /// <param name="isTryGet">Indicates if a TryGet is executed.</param>
+ /// <returns>The index of the field.</returns>
+ public virtual int GetFieldIndex(string name, int index = 0, bool isTryGet = false)
+ {
+ return GetFieldIndex(new[] { name }, index, isTryGet);
+ }
+
+ /// <summary>
+ /// Gets the index of the field with the given name.
+ /// </summary>
+ /// <param name="names">The names of the field.</param>
+ /// <param name="index">The index of the field.</param>
+ /// <param name="isTryGet">Indicates if a TryGet is executed.</param>
+ /// <param name="isOptional">Indicates if the field is optional.</param>
+ /// <returns>The index of the field.</returns>
+ public virtual int GetFieldIndex(IEnumerable<string> names, int index = 0, bool isTryGet = false, bool isOptional = false)
+ {
+ if (names == null)
+ {
+ throw new ArgumentNullException(nameof(names));
+ }
+
+ if (!hasHeaderRecord)
+ {
+ throw new ReaderException(context, "There is no header record to determine the index by name.");
+ }
+
+ if (headerRecord == null)
+ {
+ throw new ReaderException(context, "The header has not been read. You must call ReadHeader() before any fields can be retrieved 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];
+ }
+
+ // 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))
+ {
+ name = fieldName;
+ break;
+ }
+
+ i++;
+ }
+
+ // 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;
+ }
+
+ namedIndexCache.Add(nameKey, (name, index));
+
+ return namedIndexes[name][index];
+ }
+
+ /// <summary>
+ /// Indicates if values can be read.
+ /// </summary>
+ /// <param name="memberMap">The member map.</param>
+ /// <returns>True if values can be read.</returns>
+ public virtual bool CanRead(MemberMap memberMap)
+ {
+ var cantRead =
+ // Ignored member;
+ memberMap.Data.Ignore;
+
+ 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;
+ }
+
+ /// <summary>
+ /// Indicates if values can be read.
+ /// </summary>
+ /// <param name="memberReferenceMap">The member reference map.</param>
+ /// <returns>True if values can be read.</returns>
+ public virtual bool CanRead(MemberReferenceMap memberReferenceMap)
+ {
+ var cantRead = false;
+
+ 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;
+ }
+
+ /// <inheritdoc/>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Disposes the object.
+ /// </summary>
+ /// <param name="disposing">Indicates if the object is being disposed.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ // Dispose managed state (managed objects)
+ if (disposing)
+ {
+ parser.Dispose();
+ }
+
+ // Free unmanaged resources (unmanaged objects) and override finalizer
+ // Set large fields to null
+ context = null;
+
+ disposed = true;
+ }
+
+ /// <summary>
+ /// Checks if the file has been read.
+ /// </summary>
+ /// <exception cref="ReaderException">Thrown when the file has not yet been read.</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected virtual void CheckHasBeenRead()
+ {
+ if (!hasBeenRead)
+ {
+ throw new ReaderException(context, "You must call read on the reader before accessing its data.");
+ }
+ }
+
+ /// <summary>
+ /// Parses the named indexes.
+ /// </summary>
+ /// <exception cref="ReaderException">Thrown when no header record was found.</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected virtual void ParseNamedIndexes()
+ {
+ if (headerRecord == null)
+ {
+ throw new ReaderException(context, "No header record was found.");
+ }
+
+ namedIndexes.Clear();
+ namedIndexCache.Clear();
+
+ for (var i = 0; i < headerRecord.Length; i++)
+ {
+ 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<int> { i };
+ }
+ }
+ }
+ }
+}