// 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 { /// /// 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 columnCount; private int currentIndex = -1; private bool hasBeenRead; private string[]? headerRecord; /// public virtual int ColumnCount => columnCount; /// 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."); } headerRecord = parser.Record; ParseNamedIndexes(); return headerRecord != null; } /// /// Validates the header to be of the given type. /// /// The expected type of the header public virtual void ValidateHeader() { ValidateHeader(typeof(T)); } /// /// 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."); } 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(); ValidateHeader(map, invalidHeaders); var args = new HeaderValidatedArgs(invalidHeaders.ToArray(), context); headerValidated?.Invoke(args); } /// /// 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; } 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); } } /// 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; } /// public virtual async Task 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; } /// public virtual string? this[int index] { get { CheckHasBeenRead(); return GetField(index); } } /// public virtual string? this[string name] { get { CheckHasBeenRead(); return GetField(name); } } /// public virtual string? this[string name, int index] { get { CheckHasBeenRead(); return GetField(name, index); } } /// 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; } /// public virtual string? GetField(string name) { CheckHasBeenRead(); var index = GetFieldIndex(name); if (index < 0) { return null; } return GetField(index); } /// public virtual string? GetField(string name, int index) { CheckHasBeenRead(); var fieldIndex = GetFieldIndex(name, index); if (fieldIndex < 0) { return null; } return GetField(fieldIndex); } /// public virtual object? GetField(Type type, int index) { CheckHasBeenRead(); var converter = context.TypeConverterCache.GetConverter(type); return GetField(type, index, converter); } /// public virtual object? GetField(Type type, string name) { CheckHasBeenRead(); var converter = context.TypeConverterCache.GetConverter(type); return GetField(type, name, 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(); 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); } /// public virtual object? GetField(Type type, string name, ITypeConverter converter) { CheckHasBeenRead(); var index = GetFieldIndex(name); return GetField(type, index, converter); } /// public virtual object? GetField(Type type, string name, int index, ITypeConverter converter) { CheckHasBeenRead(); var fieldIndex = GetFieldIndex(name, index); return GetField(type, fieldIndex, converter); } /// public virtual T? GetField(int index) { CheckHasBeenRead(); var converter = context.TypeConverterCache.GetConverter(); return GetField(index, converter); } /// public virtual T? GetField(string name) { CheckHasBeenRead(); var converter = context.TypeConverterCache.GetConverter(); return GetField(name, converter); } /// public virtual T? GetField(string name, int index) { CheckHasBeenRead(); var converter = context.TypeConverterCache.GetConverter(); return GetField(name, index, converter); } /// public virtual T? GetField(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); } /// 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 { 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 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); } /// 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); } /// 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); } /// 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(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); } /// 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; } 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) { ReadHeader(); ValidateHeader(); if (!Read()) { return default; } } T record; try { record = recordManager.Value.Create(); } 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; } /// public virtual T? GetRecord(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(); } /// 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; } /// public virtual IEnumerable GetRecords() { 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()) { T record; try { record = recordManager.Value.Create(); } 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; } } /// public virtual IEnumerable GetRecords(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(); } /// 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. 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; } } /// public virtual IEnumerable EnumerateRecords(T record) { 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); 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 /// public virtual async IAsyncEnumerable GetRecordsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default(CancellationToken)) { 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(); } while (await ReadAsync().ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); T record; try { record = recordManager.Value.Create(); } 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; } } /// public virtual IAsyncEnumerable GetRecordsAsync(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(cancellationToken); } /// 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?" ); } // 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; } } /// 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(); } 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 /// /// 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); } /// /// 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)); } 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]; } /// /// 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; 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; } /// /// 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; 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; } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Disposes the object. /// /// Indicates if the object is being disposed. 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; } /// /// 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."); } } /// /// Parses the named indexes. /// /// Thrown when no header record was found. [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 { i }; } } } } }