diff --git a/src/CsvHelper/CsvHelper.csproj b/src/CsvHelper/CsvHelper.csproj index 804f98a56..4f9442b92 100644 --- a/src/CsvHelper/CsvHelper.csproj +++ b/src/CsvHelper/CsvHelper.csproj @@ -49,6 +49,13 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/CsvHelper/CsvParser.cs b/src/CsvHelper/CsvParser.cs index 83ad97b22..5b7e30cb5 100644 --- a/src/CsvHelper/CsvParser.cs +++ b/src/CsvHelper/CsvParser.cs @@ -13,7 +13,7 @@ namespace CsvHelper; /// /// Parses a CSV file. /// -public class CsvParser : IParser, IDisposable +public partial class CsvParser : IParser, IDisposable { private readonly IParserConfiguration configuration; private readonly FieldCache fieldCache = new FieldCache(); @@ -204,42 +204,9 @@ public CsvParser(TextReader reader, IParserConfiguration configuration, bool lea processedFields = new string[128]; } - /// - public bool Read() - { - isRecordProcessed = false; - rowStartPosition = bufferPosition; - fieldStartPosition = rowStartPosition; - fieldsPosition = 0; - quoteCount = 0; - row++; - rawRow++; - var c = '\0'; - var cPrev = c; - - while (true) - { - if (bufferPosition >= charsRead) - { - if (!FillBuffer()) - { - return ReadEndOfFile(); - } - - if (row == 1 && detectDelimiter) - { - DetectDelimiter(); - } - } - - if (ReadLine(ref c, ref cPrev) == ReadLineResult.Complete) - { - return true; - } - } - } /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public async Task ReadAsync() { isRecordProcessed = false; diff --git a/src/CsvHelper/CsvReader.cs b/src/CsvHelper/CsvReader.cs index 3fa74c2a0..ef12d777e 100644 --- a/src/CsvHelper/CsvReader.cs +++ b/src/CsvHelper/CsvReader.cs @@ -14,7 +14,7 @@ namespace CsvHelper; /// /// Reads data that was parsed from . /// -public class CsvReader : IReader +public partial class CsvReader : IReader { private readonly Lazy recordManager; private readonly bool detectColumnCountChanges; @@ -237,41 +237,9 @@ protected virtual void ValidateHeader(ClassMap map, List invalidH } } - /// - 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 (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; - } - } - - prevColumnCount = parser.Count; - } - - return hasMoreRecords; - } /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public virtual async Task ReadAsync() { bool hasMoreRecords; diff --git a/src/CsvHelper/CsvWriter.cs b/src/CsvHelper/CsvWriter.cs index d74da342d..a8249bac7 100644 --- a/src/CsvHelper/CsvWriter.cs +++ b/src/CsvHelper/CsvWriter.cs @@ -20,7 +20,7 @@ namespace CsvHelper; /// /// Used to write CSV files. /// -public class CsvWriter : IWriter +public partial class CsvWriter : IWriter { private readonly TextWriter writer; private readonly CsvContext context; @@ -346,119 +346,9 @@ public virtual void WriteRecord(T? record) } } - /// - public virtual void WriteRecords(IEnumerable records) - { - // Changes in this method require changes in method WriteRecords(IEnumerable records) also. - - var enumerator = records.GetEnumerator(); - - try - { - if (!enumerator.MoveNext()) - { - return; - } - - if (WriteHeaderFromRecord(enumerator.Current)) - { - NextRecord(); - } - - Action? write = null; - RecordTypeInfo writeType = default; - - do - { - 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(); - } - 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(); - } - } - } - - /// - 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."); - - try - { - if (WriteHeaderFromType()) - { - NextRecord(); - } - - if (!enumerator.MoveNext()) - { - return; - } - - if (WriteHeaderFromRecord(enumerator.Current)) - { - NextRecord(); - } - - Action? write = null; - RecordTypeInfo writeType = default; - - do - { - var record = enumerator.Current; - - if (write == null || (record != null && writeType.RecordType != typeof(T))) - { - writeType = GetTypeInfoForRecord(record); - write = recordManager.Value.GetWriteDelegate(writeType); - } - - 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(); - } - } - } /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default) { // These methods should all be the same; @@ -489,7 +379,14 @@ public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationTok var record = enumerator.Current; - if (write == null || (record != null && writeType.RecordType != record.GetType())) + if (record == null) + { + // Since every record could be a different type, just write a blank line. + await NextRecordAsync().ConfigureAwait(false); + continue; + } + + if (write == null || writeType.RecordType != record.GetType()) { writeType = GetTypeInfoForRecord(record); write = recordManager.Value.GetWriteDelegate(writeType); @@ -514,6 +411,7 @@ public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationTok } /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default) { // These methods should all be the same; @@ -574,6 +472,8 @@ public virtual async Task WriteRecordsAsync(IEnumerable records, Cancellat } /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] + [Zomp.SyncMethodGenerator.SkipSyncVersion] public virtual async Task WriteRecordsAsync(IAsyncEnumerable records, CancellationToken cancellationToken = default) { // These methods should all be the same; @@ -631,16 +531,7 @@ public virtual async Task WriteRecordsAsync(IAsyncEnumerable records, Canc } /// - public virtual void NextRecord() - { - WriteToBuffer(newLine); - FlushBuffer(); - - index = 0; - row++; - } - - /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public virtual async Task NextRecordAsync() { WriteToBuffer(newLine); @@ -651,13 +542,7 @@ public virtual async Task NextRecordAsync() } /// - public virtual void Flush() - { - FlushBuffer(); - writer.Flush(); - } - - /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public virtual async Task FlushAsync() { await FlushBufferAsync().ConfigureAwait(false); diff --git a/tests/CsvHelper.Tests/Writing/WriteNullTests.cs b/tests/CsvHelper.Tests/Writing/WriteNullTests.cs index d9400e049..0934345ab 100644 --- a/tests/CsvHelper.Tests/Writing/WriteNullTests.cs +++ b/tests/CsvHelper.Tests/Writing/WriteNullTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Threading.Tasks; using Xunit; namespace CsvHelper.Tests.Writing @@ -80,6 +81,58 @@ public void WriteRecord_RecordIsNull_WritesEmptyRecord() } } + [Fact] + public async Task WriteRecordsEnumerableGenericAsync_RecordIsNull_WritesEmptyRecord() + { + var records = new List + { + new Foo { Id = 1, Name = "one"}, + null, + new Foo { Id = 2, Name = "two" }, + }; + var config = new CsvConfiguration(CultureInfo.InvariantCulture); + using (var writer = new StringWriter()) + using (var csv = new CsvWriter(writer, config)) + { + await csv.WriteRecordsAsync(records); + await csv.FlushAsync(); + + var expected = new TestStringBuilder(config.NewLine); + expected.AppendLine("Id,Name"); + expected.AppendLine("1,one"); + expected.AppendLine(","); + expected.AppendLine("2,two"); + + Assert.Equal(expected, writer.ToString()); + } + } + + [Fact] + public async Task WriteRecordsEnumerableAsync_RecordIsNull_WritesEmptyRecord() + { + IEnumerable records = new List + { + new Foo { Id = 1, Name = "one"}, + null, + new Foo { Id = 2, Name = "two" }, + }; + var config = new CsvConfiguration(CultureInfo.InvariantCulture); + using (var writer = new StringWriter()) + using (var csv = new CsvWriter(writer, config)) + { + await csv.WriteRecordsAsync(records); + await csv.FlushAsync(); + + var expected = new TestStringBuilder(config.NewLine); + expected.AppendLine("Id,Name"); + expected.AppendLine("1,one"); + expected.AppendLine(""); + expected.AppendLine("2,two"); + + Assert.Equal(expected, writer.ToString()); + } + } + private class Foo { public int Id { get; set; }