I'm working on a communications layer for a system that reads data from a TCPIP client that is formatted as fixed-width ASCII (yeah, old school). I was quite surprised that there seemed to be no built in way to do this, and ended up using the following simple StreamReader subclass:
/// <summary>
/// A Stream reader that reads values as fixed width fields from a stream
/// </summary>
class FixedWidthFieldStreamReader : StreamReader
{
#region Private/Protected fields
private char[] buffer; // Local buffer used to copy data before conversion
#endregion
#region Methods
/// <summary>
/// Instantiates a new FixedWidthFieldStreamReader for a stream
/// </summary>
/// <param name="stream">Stream to read from</param>
/// <param name="initialBufferSize">Initial size of the buffer used to copy data before formatting</param>
/// <param name="encoding">Encoding to use when reading from the stream</param>
public FixedWidthFieldStreamReader(Stream stream, int initialBufferSize, Encoding encoding)
: base(stream, encoding)
{
buffer = new char[initialBufferSize];
}
/// <summary>
/// Checks if the buffer exists and is large enough,
/// and allocates or grows it if necessary.
/// </summary>
/// <param name="length">The required buffer length</param>
private void EnsureBufferLength(int length)
{
if (null == buffer ||
buffer.Length < length)
{
buffer = new char[length];
}
}
/// <summary>
/// Reads a number of bytes into the buffer
/// </summary>
/// <param name="length">The number of bytes to read</param>
/// <returns>True if the required number of bytes was read, false otherwise</returns>
private bool ReadToBuffer(int length)
{
EnsureBufferLength(length);
// Read from the stream
int read = Read(buffer, 0, length);
return read == length;
}
/// <summary>
/// Reads a specified number of bytes from the stream and
/// converts and returns the read value.
/// </summary>
/// <typeparam name="T">Type of the object to read and return</typeparam>
/// <param name="length">Number of bytes in the field to read from the stream.</param>
/// <returns>The read object if successful, or the default value for the type otherwise.</returns>
public T Read<T>(int length) where T : IConvertible
{
if (ReadToBuffer(length))
{
return (T)Convert.ChangeType(new string(buffer, 0, length), typeof(T));
}
return default(T);
}
/// <summary>
/// Skips a specified number of bytes in the stream
/// </summary>
/// <param name="length">The number of bytes to skip</param>
public void Skip(int length)
{
// Ideally we should be able to just seek on the current stream,
// but that seems to seek to an incorrect location?
//this.BaseStream.Seek(length, SeekOrigin.Current);
ReadToBuffer(length);
}
#endregion
}
This would be used something like this:
using (MemoryStream stream = new MemoryStream(buffer))
{
stream.Seek((int)FieldOffsets.DATA, SeekOrigin.Begin);
using (FixedWidthFieldStreamReader reader = new FixedWidthFieldStreamReader(stream, 15, Encoding.ASCII))
{
intVal = reader.Read<int>(3);
stringVal = reader.Read<string>(15);
floatVal= reader.Read<float>(5);
}
}
I have two questions based on this:
- Am I just missing some completely obvious existing utility to do this? It really does seem like a common problem that would have been solved by the framework team ages ago.
- Aside from the obvious optimization of having some type specific versions of Read that don't do the conversion, are there any suggestions to improve this approach?