Skip to content

Writing

kevin-montrose edited this page Apr 10, 2021 · 6 revisions

Writing

Introduction

Cesil splits writing into two interfaces IWriter<TRow> and IAsyncWriter<TRow>, for synchronous and asynchronous operations respectively. The same conceptual operations are supported by each interface.

Writer interfaces are obtained with the CreateWriter(...) and CreateAsyncWriter(...) methods on IBoundConfiguration<TRow> instances. Configurations are created with the Configuration static class's For<TRow>() and ForDynamic() methods.

Particulars of the format being read, and the manner to initialize and mutate the created TRow, are controlled by the Options (and the ITypeDescriber on it) provided to create the IBoundConfiguration<TRow>. By default, Options.Default or Options.DynamicDefault are used - both of which use the Default Type Describer.

Comments can only be written if they are supported by the used Options.

Synchronous Writing

These methods may block if writing to the underlying data stream blocks. Blocking is influenced by the Options used, but in general you should assume these methods block.

If you know the underlying data stream will never block when written to, this is the more efficient interface to use.

If your use case is entirely synchronous (ie. there's no other work your code could yield its thread to, you're calling ValueTask<T>.Result, etc.), then use the interface to avoid the inefficency of sync-over-async.

Write(TRow)

This method writes a single row.

After this method returns, mutating the provided row will not affect serialization.

Data may not be written to the underlying stream when this method returns, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

WriteAll(IEnumerable<TRow>)

This method fully enumerates the given IEnumerable<TRow>, writing each row in order. This method returns the number of rows written.

After this method returns, mutating the provided rows or IEnumerable<TRow> will not affect serialization.

Data may not be written to the underlying stream when this method returns, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

WriteComment(string)

This method writes a comment, if the Options used supports it.

If comments are not supported, this method will throw an exception.

If a comment contains the row ending sequence in Options the comment will be split into multiple comments lines.

Data may not be written to the underlying stream when this method returns, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

WriteComment(ReadOnlySpan<char>)

This method writes a comment, if the Options used supports it.

If comments are not supported, this method will throw an exception.

If a comment contains the row ending sequence in Options the comment will be split into multiple comments lines.

Data may not be written to the underlying stream when this method returns, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

Asynchronous Writing

These methods do not block, if at any point the underlying data stream is not able to complete immediately the methods will yield control back to the calling thread and return a ValueTask or ValueTask<TRow>.

Every method takes an optional CancellationToken, which is checked periodically to see if cancellation is requested. Cancellation leaves the writer in an exceptional state, it is not legal to resume using a writer post-cancellation.

If you know the underlying data stream will never block when written to, the synchronous interface is more efficient.

This is the preferred interface for most development scenarios, especially those (like web development) where blocking threads can lead to serious issues. However, if your use case is naturally synchronous or you know that all data can be written without blocking then the synchronous interface is more efficient.

WriteAsync(TRow, CancellationToken)

This method writes a single row.

Mutating the given row prior to the returned ValueTask completing may result in undefined behavior.

Data may not be written to the underlying stream when the returned ValueTask completes, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

WriteAllAsync(IEnumerable<TRow>, CancellationToken)

This method fully enumerates the given IEnumerable<TRow>, writing each row in order. This method returns the number of rows written.

Mutating the IEnumerable<TRow> or the TRow instances it enumerates prior to the returned ValueTask completing may result in undefined behavior.

This method differs from WriteAllAsync(IAsyncEnumerable<TRow>, CancellationToken) by assuming that enumerating rows will not block. If a call to MoveNext() blocks, this method may also block.

Data may not be written to the underlying stream when the returned ValueTask completes, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

WriteAllAsync(IAsyncEnumerable<TRow>, CancellationToken)

This method fully enumerates the given IAsyncEnumerable<TRow>, writing each row in order. This method returns the number of rows written.

Mutating the IAsyncEnumerable<TRow> or the TRow instances it enumerates prior to the returned ValueTask completing may result in undefined behavior.

This method differs from WriteAllAsync(IEnumerable<TRow>, CancellationToken) by supporting the case where an enumeration might itself require asynchronous work to complete. This makes it possible to avoid blocking in cases where I/O or significant computation is needed to implement an enumerable.

Data may not be written to the underlying stream when the returned ValueTask completes, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

WriteCommentAsync(string, CancellationToken)

This method writes a comment, if the Options used supports it.

If comments are not supported, this method will throw an exception.

If a comment contains the row ending sequence in Options the comment will be split into multiple comments lines.

Data may not be written to the underlying stream when the returned ValueTask completes, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

WriteCommentAsync(ReadOnlyMemory<char>, CancellationToken)

This method writes a comment, if the Options used supports it.

If comments are not supported, this method will throw an exception.

If a comment contains the row ending sequence in Options the comment will be split into multiple comments lines.

Data may not be written to the underlying stream when the returned ValueTask completes, as pending data may be buffered across calls. Write buffering can be influenced by the Options used.

Disposing

Writing takes conceptual ownership of the underlying data stream for symmetry with reading. This means that calling Dispose() (on IWriter<TRow>) or DisposeAsync() (on IAsyncWriter<TRow>) will invoke the equvialent method on the underlying data stream.

Flushing any buffered writes may be deferred until a writer is disposed - failing to dispose a writer may result in data not being written.

Typically, disposing will be handled with either using or await using statements rather than direct calls to the appropriate methods.

Thread Safety

No methods exposed by any writer are thread safe, invoking any of them simultaneous may result in undefined behavior. That said, it is legal to change the thread invoking methods provided there is no overlap in invocation.

As all async methods return a ValueTask or ValueTask<TRow>, it is illegal to await or invoke AsTask() on their returns multiple times.