Class FixedWidthLoader<TRecord>

Namespace
Wolfgang.Etl.FixedWidth
Assembly
Wolfgang.Etl.FixedWidth.dll

Writes records of type TRecord to a fixed-width text stream as an asynchronous operation.

public class FixedWidthLoader<TRecord> : LoaderBase<TRecord, FixedWidthReport>, ILoadWithProgressAndCancellationAsync<TRecord, FixedWidthReport>, ILoadWithProgressAsync<TRecord, FixedWidthReport>, ILoadWithCancellationAsync<TRecord>, ILoadAsync<TRecord>, IReportsItemErrors, IAsyncDisposable, IDisposable where TRecord : notnull

Type Parameters

TRecord
Inheritance
LoaderBase<TRecord, FixedWidthReport>
FixedWidthLoader<TRecord>
Implements
ILoadWithProgressAndCancellationAsync<TRecord, FixedWidthReport>
ILoadWithProgressAsync<TRecord, FixedWidthReport>
ILoadWithCancellationAsync<TRecord>
ILoadAsync<TRecord>
IReportsItemErrors
Inherited Members
LoaderBase<TRecord, FixedWidthReport>.CreateProgressReport()
LoaderBase<TRecord, FixedWidthReport>.IncrementCurrentItemCount()
LoaderBase<TRecord, FixedWidthReport>.IncrementCurrentSkippedItemCount()
LoaderBase<TRecord, FixedWidthReport>.OnItemError(ItemErrorContext)
LoaderBase<TRecord, FixedWidthReport>.HandleItemError(ItemErrorContext)
LoaderBase<TRecord, FixedWidthReport>.DisposeAsync()
LoaderBase<TRecord, FixedWidthReport>.Dispose()
LoaderBase<TRecord, FixedWidthReport>.StartedAt
LoaderBase<TRecord, FixedWidthReport>.Elapsed
LoaderBase<TRecord, FixedWidthReport>.ReportingInterval
LoaderBase<TRecord, FixedWidthReport>.CurrentItemCount
LoaderBase<TRecord, FixedWidthReport>.CurrentSkippedItemCount
LoaderBase<TRecord, FixedWidthReport>.CurrentErrorItemCount
LoaderBase<TRecord, FixedWidthReport>.MaximumItemCount
LoaderBase<TRecord, FixedWidthReport>.SkipItemCount
LoaderBase<TRecord, FixedWidthReport>.WorkerResilience
LoaderBase<TRecord, FixedWidthReport>.ErrorPolicy

Remarks

Two construction modes are supported, each with different ownership semantics:

  • TextWriter constructor — the caller owns the TextWriter lifetime. The loader does not dispose it, and calling Dispose() is optional (no-op). The caller is responsible for flushing the writer.
  • Stream constructor — the loader creates an internal StreamWriter with a 64 KB buffer for improved throughput. The caller retains ownership of the Stream (it is not closed). The internal writer is flushed automatically at the end of LoadWorkerAsync, and Dispose() must be called to release it.
// Stream-based (preferred for files — 64 KB buffer reduces syscall overhead):
await using var stream = File.OpenWrite("output.txt");
using var loader = new FixedWidthLoader<MyRecord>(stream);

// TextWriter-based (caller owns the writer):
var sw = new StringWriter();
var loader = new FixedWidthLoader<MyRecord>(sw);
var result = sw.ToString();

// Write a formatted table to the console
var loader = new FixedWidthLoader<MyRecord>(Console.Out, new FixedWidthLoaderOptions
{
    WriteHeader    = true,
    FieldSeparator = '-',
    FieldDelimiter = " | ",
});

Constructors

FixedWidthLoader(Stream, ILogger<FixedWidthLoader<TRecord>>, Encoding)

Initializes a new FixedWidthLoader<TRecord> from a Stream decoded with the supplied Encoding, with diagnostic logging.

[Obsolete("Use the constructor that takes FixedWidthLoaderStreamOptions. This overload will be removed in a future release.")]
public FixedWidthLoader(Stream stream, ILogger<FixedWidthLoader<TRecord>> logger, Encoding encoding)

Parameters

stream Stream

The stream to use.

logger ILogger<FixedWidthLoader<TRecord>>

The logger to use for diagnostic output.

encoding Encoding

The encoding to decode with, or null for the documented default.

Exceptions

ArgumentNullException

stream is null.

FixedWidthLoader(Stream, Encoding)

Initializes a new FixedWidthLoader<TRecord> from a Stream decoded with the supplied Encoding.

[Obsolete("Use the constructor that takes FixedWidthLoaderStreamOptions. This overload will be removed in a future release.")]
public FixedWidthLoader(Stream stream, Encoding encoding)

Parameters

stream Stream

The stream to use.

encoding Encoding

The encoding to decode with, or null for the documented default.

Exceptions

ArgumentNullException

stream is null.

FixedWidthLoader(Stream, FixedWidthLoaderStreamOptions?, ILogger<FixedWidthLoader<TRecord>>?)

Initializes a new FixedWidthLoader<TRecord> over the specified Stream, with the logger as the trailing optional parameter.

public FixedWidthLoader(Stream stream, FixedWidthLoaderStreamOptions? options = null, ILogger<FixedWidthLoader<TRecord>>? logger = null)

Parameters

stream Stream

The Stream to use.

options FixedWidthLoaderStreamOptions

Options that control behaviour, including the Encoding to use. When null, the documented defaults apply.

logger ILogger<FixedWidthLoader<TRecord>>

An optional logger for diagnostic output. When null — or omitted — Instance is used and logging is disabled.

Exceptions

ArgumentNullException

stream is null.

FixedWidthLoader(TextWriter, ILogger<FixedWidthLoader<TRecord>>?)

Initializes a new FixedWidthLoader<TRecord> that writes to the specified TextWriter with diagnostic logging.

public FixedWidthLoader(TextWriter writer, ILogger<FixedWidthLoader<TRecord>>? logger = null)

Parameters

writer TextWriter

The TextWriter to write fixed-width records to.

logger ILogger<FixedWidthLoader<TRecord>>

The logger instance for diagnostic output.

Exceptions

ArgumentNullException

writer or logger is null.

FixedWidthLoader(TextWriter, FixedWidthLoaderOptions?, ILogger<FixedWidthLoader<TRecord>>?)

Initializes a new instance that writes to writer with the given configuration.

[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Shipped shape: the Stream overload keeps its optional options/logger for source compatibility until the constructor set is settled in the 2026-12-15 removal wave (#373 / #343).")]
public FixedWidthLoader(TextWriter writer, FixedWidthLoaderOptions? options = null, ILogger<FixedWidthLoader<TRecord>>? logger = null)

Parameters

writer TextWriter

The writer receiving the fixed-width lines. The caller owns it.

options FixedWidthLoaderOptions

The formatting configuration. null (the default) keeps every default. The writer already owns its encoding, so this is the base record without an Encoding.

logger ILogger<FixedWidthLoader<TRecord>>

An optional logger.

Exceptions

ArgumentNullException

writer is null.

Properties

CurrentLineNumber

The 1-based physical line number of the line most recently written to the output. Updated after each line is written. Includes the header line and separator line if written. Matches the line number shown in a text editor.

public long CurrentLineNumber { get; }

Property Value

long

Remarks

Thread-safe: reads are performed with Interlocked so this property may be sampled from a progress-reporting timer thread without a data race.

FieldDelimiter

An optional string written between fields on every line including headers, separators, and data rows. Set to null (default) for pure fixed-width output with no delimiter. Use a value like " | " for human-readable report output.

public string? FieldDelimiter { get; set; }

Property Value

string

Examples

// Supplied through FixedWidthLoaderOptions, passed to the constructor:
FieldDelimiter = " | ",   // human-readable table: "John       | Smith      |  42 "
FieldDelimiter = null,    // pure fixed-width (default): "John      Smith        42 "

Remarks

When set, the delimiter is inserted between every adjacent pair of fields — it is not appended after the last field. The FieldDelimiter on the corresponding extractor must be set to the same value so that field boundaries are correctly identified during extraction.

FieldSeparator

When non-null, a separator line is written after the header, consisting of the specified character repeated to each field's width. Set to null (default) to write no separator. Has no effect if WriteHeader is false. Mirrors FieldSeparator.

public char? FieldSeparator { get; set; }

Property Value

char?

Examples

// Supplied through FixedWidthLoaderOptions, passed to the constructor:
FieldSeparator = '-',  // writes "----------"
FieldSeparator = '=',  // writes "=========="
FieldSeparator = null, // no separator (default)

HeaderConverter

The function used to convert a header label to its string representation. Defaults to StrictHeader.

public Func<string, FieldContext, string> HeaderConverter { get; set; }

Property Value

Func<string, FieldContext, string>

Examples

// Render all headers in upper-case:
new FixedWidthLoaderOptions
{
    HeaderConverter = (label, ctx) =>
        FixedWidthConverter.StrictHeader(label.ToUpperInvariant(), ctx),
}

// Silently truncate headers that are too long:
new FixedWidthLoaderOptions { HeaderConverter = FixedWidthConverter.TruncateHeader }

Remarks

Only called when WriteHeader is true. Space-padding to FieldLength is applied by the framework after this converter returns — the converter must only ensure the returned string is not longer than the field width.

IsDryRun

Gets or sets a value indicating whether the run is a dry run that exercises the pipeline without writing any output.

public bool IsDryRun { get; set; }

Property Value

bool

Remarks

When true, the loader enumerates the source and evaluates Wolfgang.Etl.Abstractions.LoaderBase<TDestination, TProgress>.SkipItemCount / Wolfgang.Etl.Abstractions.LoaderBase<TDestination, TProgress>.MaximumItemCount, increments progress counters, fires the progress-timer callback, and logs as usual — but writes nothing to the output fixed-width stream. Field-width validation still runs, so a dry run surfaces FieldOverflowException the same way a real run would. Defaults to false.

Schema

An optional layout that overrides the [FixedWidthField] / [FixedWidthSkip] attributes on TRecord (#23). Build one with FixedWidthSchemaBuilder<T> to map a type you cannot decorate, or to define the layout in code. When null (the default) the attribute-based layout is used. The schema's RecordType must be TRecord.

public FixedWidthSchema? Schema { get; set; }

Property Value

FixedWidthSchema

ValueConverter

The function used to convert a field value to its string representation before padding and writing. Defaults to Strict.

public Func<object, FieldContext, string> ValueConverter { get; set; }

Property Value

Func<object, FieldContext, string>

Examples

// Write booleans as "Y"/"N" instead of "True"/"False":
new FixedWidthLoaderOptions
{
    ValueConverter = (value, ctx) =>
        ctx.PropertyType == typeof(bool)
            ? ((bool)value ? "Y" : "N")
            : FixedWidthConverter.Strict(value, ctx),
}

// Silently truncate all values instead of throwing on overflow:
new FixedWidthLoaderOptions { ValueConverter = FixedWidthConverter.Truncate }

Remarks

The converter receives the raw boxed property value and a FieldContext describing the field. It must return a string no longer than FieldLength; otherwise the loader's field-width safety-net will throw a FieldOverflowException.

WriteHeader

When true, a header line is written before any records. Defaults to false.

public bool WriteHeader { get; set; }

Property Value

bool

Examples

// Supplied through FixedWidthLoaderOptions, passed to the constructor:
WriteHeader = true,
// Produces a header line like: "FirstName LastName  Age  "

Remarks

The header label for each field is taken from Header if set, or the property name otherwise. Labels are passed through HeaderConverter before being written.

Methods

CreateProgressReport()

Creates a progress report snapshot for the current loader state.

protected override FixedWidthReport CreateProgressReport()

Returns

FixedWidthReport

A FixedWidthReport snapshot containing Wolfgang.Etl.Abstractions.LoaderBase<TDestination, TProgress>.CurrentItemCount, Wolfgang.Etl.Abstractions.LoaderBase<TDestination, TProgress>.CurrentSkippedItemCount, and CurrentLineNumber at the moment of the call.

CreateProgressTimer(IProgress<FixedWidthReport>)

Creates the Wolfgang.Etl.Abstractions.IProgressTimer used to drive progress callbacks. Override this method in a derived class to inject a custom timer (for example, a custom implementation that allows manual control in unit tests).

protected override IProgressTimer CreateProgressTimer(IProgress<FixedWidthReport> progress)

Parameters

progress IProgress<FixedWidthReport>

The progress sink that will receive callbacks.

Returns

IProgressTimer

A started Wolfgang.Etl.Abstractions.IProgressTimer instance.

Dispose(bool)

Releases the internal StreamWriter when this instance was constructed from a Stream, then defers to the base class. Has no effect on a caller-owned TextWriter.

protected override void Dispose(bool disposing)

Parameters

disposing bool

true when called from IDisposable; false when called from a finalizer.

LoadWorkerAsync(IAsyncEnumerable<TRecord>, CancellationToken)

This method is the core implementation of the loading logic and should be overridden by derived classes.

protected override Task LoadWorkerAsync(IAsyncEnumerable<TRecord> items, CancellationToken token)

Parameters

items IAsyncEnumerable<TRecord>

The items to be loaded to the destination.

token CancellationToken

A CancellationToken to observe while waiting for the task to complete.

Returns

Task

A task representing the asynchronous operation.

Remarks

Items may be an empty sequence if no data is available or if the loading fails.

Exceptions

ArgumentNullException

Argument items is null