This article explores the foundations of file handling and serialization in
VB.NET
Introduction.NET
programs perform I/O through streams. A stream is an abstraction which either
produces or consumes information. Alternatively, we can say that streams
represent a sequence of bytes going to or coming from a storage medium (such
as a file) or a physical or virtual device (network port or inter-process
communication pipe). All streams behave in the same manner, even if the
actual devices they are linked to may differ, which means we are going to use
the same methods to write data to a standard output device as a disk file.
Byte Streams and Character
Streams
At the lowest level, all I/O
systems operate on bytes. In .NET, the char data type is of 2 Bytes to
support the UNICODE character set. Using the ASCII character set it is easy
to convert between char and byte by just ignoring the higher order byte. So
we can not directly use byte streams to perform character based I/O. To
overcome this problem .NET defines several classes that convert a byte stream
to character stream, where handling of translation of byte-to-char and
char-to-byte is automatic.
Predefined Steams
We have three predefined
streams in .NET which can be redirected to any compatible I/O device.
Console.Out -> Represents the output stream associated with
standard Output device.
Console.Err -> Represents the output stream associated with
standard Error device.
Console.In -> Represents the input stream associated with
standard Input device.
We have to import the
required classes into our application for performing I/O operations.
Imports System.IO ->
Namespace containing all I/O classes.
Stream
Class -> This class represents a byte stream and is a base class for
all other stream classes. As it is an abstract class we can instantiate
it but it gives the information about a set of standard stream
operations.
Close() ->
Closes the stream.
Flush() ->
Flushes the stream to push any data present in the stream to the
destination.
ReadByte() -> Reads one byte of data from the input source
and returns the integer representing that byte. If not successful it
returns a value –1.
ReadBytes(Byte(),offset,noofbytes) -> Tries to read noofbytes of the input source
and stores them in the byte array. It returns an integer stating how
many bytes actually read.
Seek(offset,SeekOrigin) -> Moves the stream pointer to specific location
of the stream.
WriteByte(Byte) -> Tries to write one byte to the output.
WriteBytes(Byte(),position,noofbytes) -> Tries to write noofbytes from the byte array
starting from position to the output.
Exceptions
IOException: This exception is generated when an I/O operation
fails.
NotSupportedException: This exception is generated when we attempt to
perform an invalid operation not supported by the stream.
Before performing any
operations through streams we can test for its support using CanRead(), CanSeek(), CanWrite()
methods. We can also use the other properties as Length and Position for
knowing the length and pointer position respectively.
Concept
of Flush
When the file output is performed,
data is often not immediately written to the actual physical device. Instead,
the output is buffered by the O/S until a sizable chunk is obtained which is
written all at once. If we can cause data to be written to the physical
device whether the buffer is full or not we call the Flush()
method, and this improves efficiency. Similarly, when we finished up writing
to an output destination we should close the stream by using Close()
which ensures data preset in buffer written to the physical device.
ByteStream Classes
FileStream: This represents byte stream for File I/O.
MemoryStream: Byte Stream that uses memory for storage.
BufferedStream: Wraps a byte stream and adds buffering.
CharacterStreams Classes
To create a character stream
we use the .NET character stream wrappers which wrap a byte stream within
themselves. We can perform character based I/O using these character streams.
The abstract classes in this category are TextReader and TextWriter.
Methods
Supported by TextReader
ReadLine() -> Returns a string containing one line of data
read from the input source.
Peek()
-> Tries to see and know whether next character is available in the
input stream or not. It returns –1 in case the next character is not
available.
Read()
-> Reads a character and returns –1 for non availability.
Read(Char(),position,noofchars)
ReadBlock(Char(),position,noofchars)
ReadToEnd() ->Returns a string containing data from current
position to the end of input.
Methods
supported by TextWriter
Write()
-> This method has 16 overloads to support the writing of all data
types.
WriteLine(String) -> Write the string as one line in the output.
Close()
-> Closes the Writer.
Flush()
-> Causes any data remaining in the output buffer to be written to
the physical medium.
We will use classes like StreamReader, StreamWriter, StringReader and StringWriter.
BinaryStream Classes
These classes provide the
functionality of reading and writing binary data directly.
FileStream: To create a byte stream linked to a file
we use the file stream class. We can perform both read and write operations
using this class.
'Constructor:
FileStream(String
Filename,FileMode,FileAccess)
FileMode
Append:
opens the file if it exists or creates a new file. Adds data to the end
of the file.
Create:
Will create a new file for writing, if it exists it will be overwritten.
CreateNew:
Will create a new file, throws exception if the file already exists.
OpenOrCreate: Opens the file if exists or create the file.
Open:
Opens the file reading or writing if exists or throws FileNotFoundException
Truncate:
Opens the existing file and reduces its length to zero.
FileAccess
Read:
Provides Read Access to the File.
Write:
Provides Write Access to the File.
ReadWrite:
Provides Read and write access to the file.
The BinaryReader and BinaryWriter
classes are suitable for working with binary streams; one such stream might
be associated with a file containing data in a native format. In this
context, "native format" means the actual bits are used to store
the value in memory. The data is read and written using its internal binary
format, not its human readable text format. We must create a Stream
object explicitly and pass it to the constructor method of either the BinaryReader or
the BinaryWriter
class. These classes provide a wrapper around the byte stream which manages
the reading and writing of binary data.
Working with the BinaryWriter
object is especially simple because its Write method is overloaded to accept all the primitive .NET types,
including signed and unsigned integers; Single, Double, and String values;
and so on.
The BinaryReader
class exposes many Readxxxx
methods, one for each possible native data type, and a PeekChar method
that returns -1 at the end of the stream.
Outputting strings with a BinaryWriter
requires some additional care, however. Passing a string to the Write
method outputs a length-prefixed string to the stream. If you want to write
only the actual characters (as often happens when you're working with
fixed-length strings), you must pass the Write method an array of chars. The Write
method is overloaded to take additional arguments that specify which portion
of the array should be written. Reading back strings requires different
techniques as well, depending on how the string was written. You use the ReadString
method for length-prefixed strings and the ReadChars method for fixed-length
strings.
In addition to the I/O classes
VB.NET supports some runtime functions which help us to perform the I/O
operations on a file. Here we use a File Number which is an integer value
representing the file in memory and this number is used to perform operations
on that file.
It exposes functions like:
FileOpen(FileNumber,String
filename,OpenMode) -> Opens the named file
in the OpenMode
specified and it is identified with FileNumber. The mode can be OpenMode.Output, OpenMode.Append, OpenMode.Input, OpenMode.Binary.
PrintLine(FileNumber,String) -> Writes the string as a line to the file
represented by FN.
FileClose(FileNumber) -> Closes the File.
LineInput(FileNumber) -> returns a string by reading one line from the
file.
EOF(FileNumber) -> States True/False informing whether end of file
is reached.
FilePut(FileNumber,Object) -> Writes data to the file including objects and
structures.
FileGet(FileNumber,object) -> Tries to read native data from the file.
InputString(FileNmber,noofchars) -> Returns a string by reading characters from
the file
Writing Data
FileOpen(10, "myfile.txt",
OpenMode.Output)
PrintLine(10,
TextBox1.Text)
FileClose(10)
MsgBox("Data
Saved")
Reading Data
FileOpen(78, "myfile.txt",
OpenMode.Input)
WhileNot EOF(78)
TextBox1.Text
+= LineInput(78) & vbCrLf
EndWhile
FileClose(78)
Using Structures
Structure student
Dim roll AsInteger
Dim nm AsString
PublicSubNew(ByVal r As Integer, ByVal n AsString)
roll = r
nm = n
EndSub
PublicSub display()
MsgBox("The values are " & roll & Space(5) & nm)
EndSub
EndStructure
Writing Structures
Dim s1 AsNew student(10, "Ashok")
FileOpen(10, "struct.txt",
OpenMode.Binary)
FilePut(10, s1)
FileClose(10)
MsgBox("Structure
saved")
Reading Structures
Dim s2 AsNew student
FileOpen(100, "struct.txt",
OpenMode.Binary)
FileGet(100,
s2)
s2.display()
FileClose(100)
Object Persistence or
Serialization
Serialization is the term for
the act of saving (or serializing) an object onto a storage Medium — a file, a
database field, a buffer in memory — and later deserializing it from the
storage medium to re-create an object instance that can be considered
identical to the original one. Serialization is a key feature in the .NET
Framework and is transparently used by the runtime for tasks other than
simply saving an object to a file. For example, marshaling an object by value
to another application. You should make an object serializable if you plan to
send it to another application or save it on disk or in a database field by
setting the <Serializable()>
attribute in the class definition.
Serialization and persistence
are often used as synonyms, so you can also say that an object is persisted
and depersisted. The MSDN documentation makes a distinction, however, and uses
persistence to mean that the data is stored in a durable medium, such as a
file or a database field, while serialization can be applied to objects
stored in nondurable media, such as memory buffers.
The .NET Framework knows how
to serialize all basic data types, including numbers, strings, and arrays of
numbers and strings, so you can save and reload these types to and from a
file stream (or any other type of stream) with minimal effort. All we need to
serialize and deserialize a basic object is the use of a proper formatter
object. Formally speaking, a formatter is an object that implements the IFormatter
interface (defined in the System.Runtime.Serialization namespace).
The BinaryFormatter
object, defined in System.Runtime.Serialization.Formatters. Binary namespace, provides an efficient way to
persist an object in a compact binary format. In practice, the actual
bits in memory are persisted, so the serialization and deserialization
processes are very fast.
The SoapFormatter
object, defined in System.Runtime.Serialization.Formatters. SOAP namespace, persists data in human-readable XML
format, following the Simple Object Access Protocol (SOAP)
specifications. The serialization and deserialization processes are
somewhat slower than with the BinaryFormatter object. On the other hand, data can be sent easily
to another application through HTTP and displayed in a human-readable
format.
Binary Serialization
The key methods that all
formatter objects support are Serialize and Deserialize, whose purpose is rather
evident. The Serialize
method takes a Stream
object as its first argument and the object to be serialized as its second
argument:
Dim fs As FileStream = New FileStream("ser.dat",
FileMode.Create)
' Create a
binary formatter for this stream.
Dim bf AsNew
BinaryFormatter()
' Serialize the
array to the file stream, and flush the stream.
bf.Serialize(fs,
arr)
fs.Close()
Reading back the file data
and deserializing it into an object requires the Deserialize
function, which takes the input Stream as its only argument and returns an Object
value, which must be cast to a properly typed variable:
'Open a file stream
for input.
Dim fs As FileStream = New FileStream("ser.dat",
FileMode.Open)
' Create a
binary formatter for this stream.
Dim bf AsNew
BinaryFormatter()
' Deserialize
the contents of the file stream into an Integer array.
' Deserialize returns
an object that must be coerced.
Dim arr() AsInteger = CType(bf.Deserialize(fs),
Integer())
' Display the
result.
ForEach n AsIntegerIn arr
Console.Write(n.ToString & " ")
Next
SOAP Serialization
You can change the
serialization format to SOAP by simply using another formatter object, the SoapFormatter.
This namespace isn't available in the default Visual Basic console project,
so you have to click Add Reference on the Project menu in Visual Studio to
add the System.Runtime.Serialization.Formatters.Soap.dll library to
the list of libraries that appears in the Object Browser. Note that the SoapFormatter's
constructor is passed a StreamingContext object that specifies where
the serialization data is stored:
' Serialize an
object to a file in SOAP format.
Sub SaveSoapData(ByVal o AsObject)
' Open a file
stream for output.
Dim fs As FileStream = New FileStream("soap.xml",
FileMode.Create)
' Create a SOAP
formatter for this file stream.
Dim sf AsNew
SoapFormatter(Nothing, New StreamingContext(
StreamingContextStates.File))
' Serialize the
array to the file stream, and close the stream.
sf.Serialize(fs,
o)
fs.Close()
EndSub
' Deserialize
an object from a file in SOAP format.
Function LoadSoapData()
AsObject
' Open a file
stream for input.
Dim fs As FileStream = New FileStream("soap.xml",
FileMode.Open)
' Create a SOAP
formatter for this file stream.
Dim sf AsNew
SoapFormatter(Nothing,
New
StreamingContext(StreamingContextStates.File))
' Deserialize the
contents of the stream into an object and close the stream.
LoadSoapData =
sf.Deserialize(fs)
fs.Close()
EndFunction
To make a user-defined class Serializable we
have to use <Serializable()>
attribute for a class as:
<Serializable()>
Class SerClass
Public x,y asInteger
PublicSubNew()
x=10
y=20
EndSub
EndClass
To Serialize
Dim fs As FileStream = New FileStream("obj.dat",
FileMode.Create)
Dim bf AsNew
BinaryFormatter()
' Serialize the
class to the file stream, and flush the stream.
bf.Serialize(fs,
New SerClass())
fs.Close()
To Deserialize
Dim fs As FileStream = New FileStream("obj.dat",
FileMode.Open)
Dim bf AsNew
BinaryFormatter()
' Deserialize
the contents of the file stream into an Integer array.
' Deserialize returns
an object that must be coerced.
Dim o As SerClass = CType(bf.Deserialize(fs),
SerClass)