Tuesday, September 11, 2012

Writing A Chunk of Data to File in C# and VB6

In Visual Basic 6 / VBA it is possible to quickly write a lot a data to file in just a couple of steps. By creating a user defined type that contains the data, and then using an array of user defined types, all that data can be loaded from and saved to disk in a very easy manner. Below is some pseudo code melded from a couple of my VB6 projects to illustrate the point for loading all data in one shot:
VB6 Code:
' declarations
' 16 byte data storage
Private Type TimeSales5
    ntSymNum As Byte
    ntDay As Byte
    ntMonth As Byte
    ntYear As Integer
    ntHour As Byte
    ntMinute As Byte
    ntSecond As Byte
    ntBid As Long
    ntAsk As Long
End Type
VB6 Code:
' Main Load Routine
Sub LoadFromFile(ByVal sName as string)
    dim uTick() as TimeSales5
    iFreeTSFile = FreeFile
    Open "C:CompleteData2\" & sName & ".ts3" For Binary As #iFreeTSFile
    cLen = LOF(iFreeTSFile)

    lRecords = cLen / 16 - 1
    If lRecords >= 0 Then
        ' size the uTick type to receive all the file's records
        ReDim uTick(lRecords) 
        Get #iFreeTSFile, , uTick ' load all the data
        ' code to assign the fields from uTick to native 
        ' types (double, long etc)
    End If
End Sub
Simple VB6 code to save all data in a user defined type one line of code based on the snippet above might be:
Put #iFreeTSFile, , uTick

Is there an equivalent to VB6 binary file writing of user defined types in C#? I searched quite a bit for a simple yet elegant solution to reading and writing custom file formats in C#.  The answer, it seems is using C#'s Stream object for file access and BinaryFormatter to serialize a custom data class to and from disk.

Now that the table has been set, it's time for a paradigm shift from the VB6 mentality of writing binary files to the C# way of writing data to file via Serialization. There are other ways to write data to file in C# but using C# Serialization and DeSerialization for file writing and reading  seems most similar to writing a VB6 user defined type to file in one shot or reading from file in one shot.

Code from SaveFormat class
C# Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyNamespace {
    [Serializable()]
    class SaveFormat {
        private static int MAXROWS = 1000;
        private static int MAXTABS = 50;
        public DateTime[] dateTime = new DateTime[MAXROWS];
        public double[, ,] EquityCurve = new double[4, MAXTABS, MAXROWS];
        public int[] EquityCurveIndex = new int[MAXROWS];
    }
}
Note the [Serializable()] attribute which tells C# that the SaveFormat class may be written to and read from a file. The class can contain any data format you need to store and retrieve, including as in the above example, multidimensional arrays.

Code from SaveFile class
C# Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
namespace MyNamespace {
    class SaveFile {
        public static void SaveMultiA(SaveFormat Data, string name) {
            try {
                using (Stream stream = File.Open (name  ,  FileMode.Create)) {
                    BinaryFormatter bin = new BinaryFormatter();
                    bin.Serialize(stream,  Data);
                }
            } catch (IOException ex) {
                MessageBox.Show("Error Saving SaveFormat Storage Class : " 
                 + ex.ToString());
            }
        }
    }
}
The SaveFile class essentially just creates a new C# Stream object with the specified file name and the FileMode.Create option. This stream object is then plugged into a new BinaryFormatter object using the Serialize method. The entire SaveFormat class (and this could be any format) is then serialized to disk via BinaryFormatter.

And finally this is the LoadFile class
C# Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
namespace MyNamespace {
    class LoadFile {
        public static void LoadMultiA(ref SaveFormat Data, string name) {
            try {
                if (!File.Exists(name)) return;
                using (Stream stream = File.Open (name  ,  FileMode.Open)) {
                    BinaryFormatter bin = new BinaryFormatter();
                    Data = (SaveFormat)bin.Deserialize(stream);
                }
            } catch (IOException ex) {
                MessageBox.Show("Error Loading SaveFormat Storage Class : " 
                 + ex.ToString());
            }
        }
    }
}
The LoadFile class just does this in reverse. Again a new Stream object is created with a valid file name, using the FileMode.Open option. The stream object is then plugged in again to the BinaryFormatter object using the Deserialize method. the Data is cast as (SaveFormat) to ensure the proper translation of the bits.

Allow me to pause for a moment to admire the beauty of the C# code I just posted. While this may seem like a lot of code, sans the using statements, this is just a few lines of actual code in C# and it gives me that good feeling I get from the pithy VB6 code being able to read and write in one swift, decisive operation. Beautiful!

2 comments:

Anonymous said...

I was able to find good info from your articles.

Anonymous said...

Good post. I'm dealing with some of these issues as
well..