Using Readers and Writers

A totally alternative approach for building large strings or writing to and reading from streams is to use the new reader and writer classes introduced in Delphi 2009, again mapped to their .NET counterparts.

In Delphi there have traditionally been a couple of similar classes (TReader and TWriter) but they are specifically aimed at streaming properties in and out of DFM files. The new classes, instead, are better suited for more general approaches and are focused on reading and writing textual data.

There are four new reading and writing classes, defined in the Classes unit:

  • TStringReader and TStringWriter work on a string in memory (directly or using a TStringBuilder)
  • TStreamReader and TStreamWriter work on a generic stream (a file stream, a memory stream, and more)

These four classes inherit from the TTextReader and TTextWriter abstract base classes, which provide the interface to a list of operations. Each of the readers implements a few basic reading techniques:

function Read: Integer; overload; function Read(const Buffer: TCharArray; Index, Count: Integer): Integer; overload;

function ReadBlock(const Buffer: TCharArray;

Index, Count: Integer): Integer; function ReadLine: string; function ReadToEnd: string;

Each of the writers has two sets of overloaded operations without (Write) and with ( WriteLine) an end-of-line separator. Here is the first set:

procedure Wri procedure Wri procedure Wri procedure Wri procedure Wri procedure Wri procedure Wri procedure Wri procedure Wri procedure Wri procedure Wri te(Value: Boolean); overload; te(value: Char); overload; te(const Value: TCharArray); overload; te(value: Double); overload; te(value: Integer); overload; te(value: Int64); overload; te(value: TObject); overload; te(value: Single); overload; te(const value: string); overload; te(value: Cardinal); overload; te(value: UInt64); overload; procedure Write(const Format: string;

Args: array of const); overload; procedure Write(value: TCharArray; Index, Count: Integer); overload;

In the current implementations the write operations transform their content to a string, before writing it (this is different from Delphi's original TReader and TWriter, which can also work with binary data).

For writing to a stream, the TStreamWriter class uses a stream or creates one using the filename and the encoding passed as parameters. So we can write, as I did in the ReaderWriter demo: var sw: TStreamWriter; begi n sw := TStreamWriter.Create( 'test.txt',

False, TEncoding.UTF8); try sw.WriteLine ('Hello, world'); sw.WriteLine ('Have a nice day'); sw.WriteLine (Left); fi nal ly sw.Free;


For reading the TStreamReader, you can work again on a stream or a file (in which case it can detect the encoding from the BOM): var sr: TStreamReader; begi n sr := TStreamReader.Create( 'test.txt', True); try while not sr.EndOfStream do Memo1.Lines.Add (sr.ReadLine); fi nal ly sr.Free; end;

Notice how you can check for the EndOfStream status. Compared to the classic Delphi code used for writing and reading a string to and from a string, passing as untyped parameter the first character of the string (str[1], below), the readability is much improved. This is a snippet of the classic code as a comparison: var fstr: TFileStream; str: string; begi n fstr := TFileStream.Create (test.txt', fmCreate); try str := 'Hello, world'; fstr.Write(str[1], Length (str)); fi nal ly; end;

For writing to an in-memory string you can use a specific stream class or use the TStringWriter class, which uses either a TStringBuilder object passed to its constructor or creates an internal one. At the end you can ask for the complete string. The TStringReader works on a string passed as parameter to its only constructor, but it has no easy way to detect the end of the string. The only ready-to-use solution I've found (without extending the class) has been to see if the Peek call returns any value. The following event handler (again from the ReaderWriter application) fills a string in memory with a writer and than reads it back:

procedure TFormReaderWriter.btnWriteAndReadClick(

Sender: TObject); var sw: TStringWriter; sr: TStringReader; theString: string; begi n sw := TStringWriter.Create; try sw.WriteLine ('Hello, world'); sw.WriteLine ('Have a nice day'); sw.WriteLine (Left);

theString := sw.ToString; fi nal ly sw.Free; end;

sr := TStringReader.Create(theString); try while sr.Peek <> -l do

Memol.Lines.Add (sr.ReadLine); fi nal ly sr.Free; end; end;

Compared to a direct use of streams (or strings), these classes are particularly handy to use, and provide good performance. When you have to create a very large string with data (say an external XML file) using a stream with a proper writer can provide you with top notch performance.

Another interesting element, is that having a standard interface available, you can write algorithms or classes that work with the two abstract classes, TTextReader and TTextWriter, and can be used for working on strings or in memory streams. As an example of this approach I've written an extremely simplified XML writer class, that doesn't inherit from TTextWriter but rather encapsulates it. This is the class definition, available in the Reader-Writer project: type

TTrivialXmlWriter = class private fWriter: TTextWriter; fNodes: TStack<string>; publ ic constructor Create (aWriter: TTextWriter); destructor Destroy; override;

procedure WriteStartElement (const sName: string); procedure WriteEndElement;

procedure WriteString (const sValue: string); end;

Internally the class uses a stack of strings to keep track of the XML elements that have been opened and not closed yet, providing a semi-automatic close in the WriteEndElement method:

procedure TTrivialXmlWriter.WriteStartElement(

const sName: string); begi n fWriter.Write( '<' + sName + '>'); fNodes.Push (sname); end;

procedure TTrivialXmlWriter.WriteEndElement; begi n fWriter.Write( '</' + fNodes.Pop + '>'); end;

This is a example of how the class can be used, by populating a TStringWriter (the actual code of the demo is slightly longer):

procedure TFormReaderWriter.btnXmlCorrectClick(

Sender: TObject); var sw: TStringWriter; txw: TTrivialXmlWriter; theString: string; begi n sw := TStringWriter.Create; try txw := TTrivialXmlWriter.Create (sw); try txw.WriteStartElement( 'book'); txw.WriteStartElement( 'title');

txw.WriteString( 'Delphi 2009 Handbook'); txw.WriteEndElement; txw.WriteEndElement; fi nal ly txw.Free; end;

theString := sw.ToString; fi nal ly sw.Free; end;

Memo1.Lines.Text := theString; end;

To make the class slightly more interesting, the destructor takes care of closing all XML nodes that were left open (although it could as well raise an error, in case such a situation occurs).

Was this article helpful?

+1 0
Project Management Made Easy

Project Management Made Easy

What you need to know about… Project Management Made Easy! Project management consists of more than just a large building project and can encompass small projects as well. No matter what the size of your project, you need to have some sort of project management. How you manage your project has everything to do with its outcome.

Get My Free Ebook

Post a comment