Class FixedWidthLoader<TRecord, TProgress>

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, TProgress> : LoaderBase<TRecord, TProgress>, ILoadWithProgressAndCancellationAsync<TRecord, TProgress>, ILoadWithProgressAsync<TRecord, TProgress>, ILoadWithCancellationAsync<TRecord>, ILoadAsync<TRecord>, IDisposable where TRecord : notnull where TProgress : notnull

Type Parameters

TRecord
TProgress
Inheritance
LoaderBase<TRecord, TProgress>
FixedWidthLoader<TRecord, TProgress>
Implements
ILoadWithProgressAndCancellationAsync<TRecord, TProgress>
ILoadWithProgressAsync<TRecord, TProgress>
ILoadWithCancellationAsync<TRecord>
ILoadAsync<TRecord>
Inherited Members
LoaderBase<TRecord, TProgress>.CreateProgressReport()
LoaderBase<TRecord, TProgress>.IncrementCurrentItemCount()
LoaderBase<TRecord, TProgress>.IncrementCurrentSkippedItemCount()
LoaderBase<TRecord, TProgress>.ReportingInterval
LoaderBase<TRecord, TProgress>.CurrentItemCount
LoaderBase<TRecord, TProgress>.CurrentSkippedItemCount
LoaderBase<TRecord, TProgress>.MaximumItemCount
LoaderBase<TRecord, TProgress>.SkipItemCount

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, Report>(stream);

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

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

Constructors

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

Initializes a new FixedWidthLoader<TRecord, TProgress> that writes to the specified Stream using an internal StreamWriter with a 64 KB buffer for improved throughput on large files.

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

Parameters

stream Stream

The Stream to write fixed-width records to. The stream must be writable. The caller retains ownership — the loader does not dispose the stream.

logger ILogger<FixedWidthLoader<TRecord, TProgress>>

An optional ILogger<TCategoryName> for diagnostic output. Pass null (the default) to disable logging.

Exceptions

ArgumentNullException

stream is null.

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

Initializes a new FixedWidthLoader<TRecord, TProgress> that writes to the specified TextWriter.

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

Parameters

writer TextWriter

The TextWriter to write fixed-width records to. This can be a StreamWriter wrapping a file or network stream, a StringWriter for in-memory content, Out for formatted console table output, or any other TextWriter implementation. The caller is responsible for the writer's lifetime — the loader does not dispose it.

logger ILogger<FixedWidthLoader<TRecord, TProgress>>

An optional ILogger<TCategoryName> for diagnostic output. Pass null (the default) to disable logging.

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

loader.FieldDelimiter = " | ";   // human-readable table: "John       | Smith      |  42 "
loader.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

loader.FieldSeparator = '-';  // writes "----------"
loader.FieldSeparator = '=';  // writes "=========="
loader.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:
loader.HeaderConverter = (label, ctx) =>
    FixedWidthConverter.StrictHeader(label.ToUpperInvariant(), ctx);

// Silently truncate headers that are too long:
loader.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.

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":
loader.ValueConverter = (value, ctx) =>
    ctx.PropertyType == typeof(bool)
        ? ((bool)value ? "Y" : "N")
        : FixedWidthConverter.Strict(value, ctx);

// Silently truncate all values instead of throwing on overflow:
loader.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 safety-net inside FormatSegment 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

loader.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. Override in a derived class to return a custom TProgress instance. The default implementation returns a FixedWidthReport when TProgress is FixedWidthReport or the base Wolfgang.Etl.Abstractions.Report, and throws NotSupportedException otherwise.

protected override TProgress CreateProgressReport()

Returns

TProgress

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

Examples

// To use a custom progress type, subclass and override:
public class MyLoader : FixedWidthLoader<MyRecord, MyProgress>
{
    public MyLoader(TextWriter writer) : base(writer) { }

    protected override MyProgress CreateProgressReport() =>
        new MyProgress(CurrentItemCount, CurrentLineNumber);
}

Exceptions

NotSupportedException

Thrown when TProgress is not FixedWidthReport or Wolfgang.Etl.Abstractions.Report and CreateProgressReport() has not been overridden.

CreateProgressTimer(IProgress<TProgress>)

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<TProgress> progress)

Parameters

progress IProgress<TProgress>

The progress sink that will receive callbacks.

Returns

IProgressTimer

A started Wolfgang.Etl.Abstractions.IProgressTimer instance.

Dispose()

Disposes the internal StreamWriter when this instance was constructed from a Stream. Has no effect when constructed from a caller-owned TextWriter.

public void Dispose()

Dispose(bool)

Releases managed resources when disposing is true. Override in a derived class to add cleanup logic.

protected virtual void Dispose(bool disposing)

Parameters

disposing bool

true when called from Dispose(); 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