Chapter 8. Object In, Object Out

only for RuBoard

Chapter 8. Object In, Object Out

I/O in the .NET framework centers on the stream . Think of a stream as a flow of data. The process of using a stream can be likened to two people standing at opposite ends of a river . The person upstream puts a message in a bottle and drops it in. Downstream, the other person waits with a net to pull the bottle out and get the message. The only difference between a .NET stream and this allegorical river is that the stream flows both ways.

The stream is a useful abstraction that makes it possible to read and write data to and from a variety of sources generically. For the most part, the operations for reading bytes from a file, from memory, or from a network socket are identical.

only for RuBoard
only for RuBoard

8.1 Streams

.NET has several types of streams, each of which is derived from System.IO.Stream :

System.IO.FileStream

Provides a buffered stream used to read and write data from files.

System.IO.MemoryStream

Provides a stream that is buffered in memory instead of physical storage. This stream can alleviate the need for temporary buffers and files.

System.Net.Sockets.NetworkStream

Forward-only stream used to send and receive data through network sockets.

System.Security.Cryptography.CryptoStream

Provides a stream that associates data to cryptographic transformations.

Generally, streams support the following actions:

  • Reading using Read (synchronous) or BeginRead (asynchronous).

  • Writing using Write (synchronous) or BeginWrite (asynchronous).

  • Seeking to determine a position or change a location within the stream using Seek .

Not all streams support every action. NetworkStream , which is used to send and receive data through network sockets, does not support seeking, for example. However, it is easy to determine what capabilities a stream supports by calling CanRead , CanWrite , or CanSeek .

.NET also supports a buffered stream, which is encapsulated by the System.IO.BufferedStream class. A buffered stream contains a backing memory store that acts as a cache for read and write operations. It is most commonly used in conjunction with a NetworkStream , as data is usually read and written in chunks over a socket. The FileStream class already contains internal buffering, and memory streams are inherently buffered, so a buffered stream is not applicable with these types.

8.1.1 FileStream

Consider the file everything.txt , which contains the quotation, "When you understand one thing through and through, you understand everything."

It is fairly easy to obtain a stream that allows data to be read from the file. Example 8-1 demonstrates the process. The constructor of the FileStream class takes a path to a file and a combination of members of the FileMode or FileAccess enumerations designating the needed access.

Example 8-1. Reading from a file stream
 Imports System
Imports System.IO
Imports System.Text
   
Public Class StreamTest
   
    Public Sub New( )  Dim bytesRead As Integer   Dim buffer(256) As Byte   Dim fs As New FileStream("everything.txt", _   FileMode.Open, _   FileAccess.ReadWrite)   bytesRead = fs.Read(buffer, 0, buffer.Length)  While (bytesRead > 0)
            Dim i As Integer
            For i = 0 To bytesRead - 1
                Console.Write(buffer(i).ToString)
            Next i
            bytesRead = fs.Read(buffer, 0, buffer.Length)
        End While
   
    End Sub
   
End Class
   
Public Class Application
    Public Shared Sub Main( )
        Dim test As New StreamTest( )
        Console.ReadLine( )
    End Sub
End Class 

After the stream is acquired , the file's contents are read into an array of Byte . Just to be clear, a Byte is not the same thing as a Char . When the buffer is dumped to the console, the output is the following, very uninformative sequence:

 8710410111032121111117321171101001011141151169711010032111110101321161041051101033211
6104114111117103104329711010032116104114111117103104321211111173211711010010111411511
6971101003210111810111412111610410511010346 

A Byte is an 8-bit unsigned integer, while a Char is a 2-byte Unicode character. Fundamentally, streams just involve reading and writing bytes. No underlying notion of data type or representation of data is built into the stream. For that, other classes elsewhere in the framework can help.

8.1.1.1 System.Text.Encoding

The System.Text namespace contains a class called Encoding whose derivates contain facilities for conversion between bytes, characters , and strings in several different encoding schemes: ASCII, Unicode, UTF7, and UTF8. Each format is encapsulated by a class, but shared methods of the Encoding base class return instances of each one.

Assuming that the System.Text namespace is imported, the Encoding.ASCII method can convert the buffer into something more readily understandable by human eyes. The code from Example 8-1 then changes as follows :

 'From Example 8-1
'Dim i As Integer
'For i = 0 To bytesRead - 1
'    Console.Write(buffer(i).ToString)
'Next i
   
Dim s As String = Encoding.ASCII.GetString(buffer)
Console.WriteLine(s) 

This code returns something a little more meaningful:

 When you understand one thing through and through you understand everything. 
8.1.1.2 Writing to a stream

The stream in Example 8-1 was opened explicitly for reading and writing, so it is possible to write back to it. Normally, when opening a file for writing, the Seek method must be called to move to the end of the stream (unless the file contents will be overwritten). This method takes the offset of the stream to seek from and the destination, which is of type SeekOrigin :

 fs.Seek(0, SeekOrigin.End) 

We do not have to call Seek here. The pointer is already positioned at the end of the stream because the entire stream was just read into buffer .

Before a write operation to a stream can occur, whatever is to be written to the stream must first be converted to an array of bytes. This process of conversion is called serialization and will be discussed in depth later in the chapter. In the case of strings, the Encoding class again comes to the rescue. It provides a method called GetBytes that can handle the necessary details of the conversion. Here, the name of the author of the quotation in Example 8-1 is written back to file via the stream:

 bytesRead = fs.Read(buffer, 0, buffer.Length)
While (bytesRead > 0)
    Dim s As String = Encoding.ASCII.GetString(buffer)
    Console.WriteLine(s)
    bytesRead = fs.Read(buffer, 0, buffer.Length)
End While  Dim author( ) As Byte = Encoding.ASCII.GetBytes("Shunryu Suzuki")   fs.Write(author, 0, author.Length)   fs.Close( )  

There is an important concept to understand about streams. When the Write method is called, nothing is actually written to the file. How is that for a misleading method name? Instead, the data was written to the internal buffer of the FileStream . To get it to the file, one of two things must occur. If reading and writing is to continue, the buffer can be written to the file by calling Stream.Flush :

 fs.Flush( ) 

This method call clears the internal buffer and copies the contents into the file, but leaves the stream open for further reading and writing. If the stream is no longer needed, call Stream.Close :

 fs.Close( ) 

Close performs the same functions as Flush , but it also calls Dispose and releases the lock on the file. Remember that this behavior is the same, regardless of the type of stream. When working with streams, remember that like any limited resources (such as file handles and network connections), it is good practice to free the stream as soon as possible. Example 8-2 contains the complete listing for the exercise of reading and writing to a file.

Example 8-2. Reading and writing to a file stream
 Imports System
Imports System.IO
Imports System.Text
   
Public Class StreamTest
   
    Public Sub New( )
   
        Dim bytesRead As Integer
        Dim buffer(256) As Byte
        Dim fs As New FileStream("everything.txt", _
                                 FileMode.Open, _
                                 FileAccess.ReadWrite)
   
        'Read from stream and write to the console
        bytesRead = fs.Read(buffer, 0, buffer.Length)
        While (bytesRead > 0)
            Dim s As String = Encoding.ASCII.GetString(buffer)
            Console.WriteLine(s)
            bytesRead = fs.Read(buffer, 0, buffer.Length)
        End While
   
        'Append author's name to the end of the file
        Dim author( ) As Byte = Encoding.ASCII.GetBytes("Shunryu Suzuki")
        fs.Write(author, 0, author.Length)
        fs.Close( )
   
    End Sub
   
End Class
   
Public Class Application
    Public Shared Sub Main( )
        Dim test As New StreamTest( )
        Console.ReadLine( )
    End Sub
End Class 
only for RuBoard