3
\$\begingroup\$

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:

  1. 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.
  2. 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?
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jun 18, 2013 at 9:25
\$\endgroup\$
3
  • \$\begingroup\$ I considered posting there as well, but thought since the primary question was really "is there a better way to do this?" here would be more appropriate. \$\endgroup\$ Commented Jun 19, 2013 at 8:03
  • \$\begingroup\$ There is an example of implementation of Mika Kolari's suggestion here: stackoverflow.com/questions/26060441/… \$\endgroup\$ Commented Sep 29, 2014 at 11:52
  • \$\begingroup\$ An alternative approach is to use C# CSV Reader. More info: http://csvreader.readthedocs.io/en/latest/FixedWidthFileReader/ \$\endgroup\$ Commented Oct 31, 2016 at 20:38

1 Answer 1

4
\$\begingroup\$

I don't know if something does that already, but you could mimic StructLayout if you need to read multiple types of 'objects' or need reusability.

Basically just set starting point/offset and length for each property and read them.

public class Item
{
 // 3 chars starting from 0
 [Layout(0, 3)]
 public int Number { get; set; }
 // 15 chars starting from 3
 [Layout(3, 15)]
 public string Text { get; set; } 
 // 5 chars starting from 18
 [Layout(18, 5)]
 public float Number2 { get; set; } 
}
reader.Read<Item>()
answered Jun 26, 2013 at 7:31
\$\endgroup\$
6
  • \$\begingroup\$ +1 this is the way to go. In addition to explicit struct layout you could also look at fixed size buffers. \$\endgroup\$ Commented Jun 26, 2013 at 8:22
  • \$\begingroup\$ That is much more readable, good suggestion, thanks. \$\endgroup\$ Commented Jun 27, 2013 at 7:59
  • \$\begingroup\$ @FlintZA, just curious, feel like updating your post to you final version of the class using attributes? \$\endgroup\$ Commented Sep 19, 2014 at 20:26
  • \$\begingroup\$ @Terry unfortunately I haven't been able to justify refactoring with the attribute version. The original is working really well. It's on the todo list, but really, really low down below higher priority stuff :) \$\endgroup\$ Commented Sep 24, 2014 at 12:03
  • 1
    \$\begingroup\$ @FlintZA - I'm not sure what protocol here is, but given I just wrote up a slightly more generic version of the answer above (and posted by Giuseppe at SO) with Generics, LayoutAttribute, Compiled Expression Trees and Convert.ChangeType, I didn't post a new answer or code here. You can, however, read my blog post on this and see the sample code at terryaney.wordpress.com/2014/10/01/…. Should I have posted new answer/code here or? Just following hanselman.com/blog/YourWordsAreWasted.aspx :). \$\endgroup\$ Commented Oct 1, 2014 at 19:05

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.