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
TRecordTProgress
- 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>.ReportingIntervalLoaderBase<TRecord, TProgress>.CurrentItemCountLoaderBase<TRecord, TProgress>.CurrentSkippedItemCountLoaderBase<TRecord, TProgress>.MaximumItemCountLoaderBase<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
streamStreamThe Stream to write fixed-width records to. The stream must be writable. The caller retains ownership — the loader does not dispose the stream.
loggerILogger<FixedWidthLoader<TRecord, TProgress>>An optional ILogger<TCategoryName> for diagnostic output. Pass null (the default) to disable logging.
Exceptions
- ArgumentNullException
streamis 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
writerTextWriterThe 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.
loggerILogger<FixedWidthLoader<TRecord, TProgress>>An optional ILogger<TCategoryName> for diagnostic output. Pass null (the default) to disable logging.
Exceptions
- ArgumentNullException
writeris 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
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
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
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
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
public bool WriteHeader { get; set; }
Property Value
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
TProgresssnapshot 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
TProgressis 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
progressIProgress<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
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
itemsIAsyncEnumerable<TRecord>The items to be loaded to the destination.
tokenCancellationTokenA 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