Streams
Streams don't get much love in the .NET world. You don't read much about them in blogs, on Web sites, or in books. Despite the lack of respect, streams are used in a number of different I/O operations, including working with files, networking, data compression, and cryptography. This is your peek into the secret lives of streams.
I have some entrepreneurial friends who, upon searching .NET documentation, couldn't find the specific classes used to manipulate files. That's because they were looking more for something that sounds file-centric instead of streams. Make no mistake thoughstreams are where it's at! |
A stream is just a sequence of bytes. The various stream classes all derive from System.IO.Stream. It's an abstract class, so by itself it helps no one. The class does however have a number of members that provide the base functionality to the classes that inherit it. Read and write methods complement properties that tell you something about the class's abilities and your place in the stream. Table 17.1 shows the Stream properties, while Table 17.2 shows the Stream methods. Again, these behave differently in different derived classes.
Property | Description |
---|---|
CanRead | Boolean value that says whether you can read from the stream. A closed stream, for example, can't be read. |
CanSeek | Boolean value that returns false if the stream is closed but also indicates whether a stream enables you to seek a particular position. You can't seek a position in a NetworkStream, for example. |
CanTimeout | Indicates if the stream can timeout. Used by NetworkStream. |
CanWrite | Boolean value that indicates if you can write to the stream. Again, a closed stream can't be written to. |
Length | A long value indicating the length of the stream. Not supported where CanSeek is false. |
Position | A long value indicating your position in the stream. This is also not supported if CanSeek is false. |
ReadTimeout | Used to set a read timeout for NetworkStream. |
WriteTimeout | Used to set a write timeout for NetworkStream. |
Property | Description |
---|---|
BeginRead() | Asynchronously begins reading the stream. Calls an event handler when the reading is complete. We won't get into this or the other asynchronous methods here for simplicity's sake. |
BeginWrite() | Asynchronously begins writing to the stream. |
Close() | Closes the stream. |
EndRead() | Waits for an asynchronous read to complete. |
EndWrite() | Ends asynchronous writing to the stream. |
Flush() | Clears the stream buffers and causes buffered data to be written to the stream. |
Read() | Reads bytes from the stream. |
ReadByte() | Reads a single byte from the stream. |
Seek() | Goes to a position in the stream. |
SetLength() | Sets the length of the stream, in bytes. Can only be used if CanSeek and CanWrite are true. Calling this method will truncate part of the stream if it's made smaller. |
Write() | Writes bytes to the stream. |
WriteByte() | Writes a single byte to the stream. |
The FileStream class is probably the most commonly used of the stream classes because it operates on (believe it or not) files. A FileStream object can be created with any of ten constructors (plus several that are obsolete starting in .NET v2.0), enabling you to specify the file name you want and how you'll work with it. In its most basic use, let's look at creating a file, writing to it, reading from it, and appending it.
Keep in mind that when manipulating files, your application must have the right permissions on a folder to create and alter its contents.
The most basic constructor takes a file name and a FileMode parameter, the latter of which is an enumeration indicating how you'll open it, as shown in Table 17.3.
Member name | Action |
---|---|
Append | Opens the file to add data to the end of it. |
Create | Starts a new file or overwrites an existing one. |
CreateNew | Also starts a new file, but throws an exception if it already exists. |
Open | Opens an existing file. |
OpenOrCreate | Opens a file or creates a new one if it doesn't exist. |
truncate | Opens a file and truncates all of its data. |
Another constructor takes a third parameter indicating the read and write mode of the file. This parameter is of the FileAccess enumeration, and not surprisingly, it only has three self-explanatory values: Read, ReadWrite, and Write.
After we have a FileStream, we can use the methods mentioned previously to move around it and manipulate it. Listing 17.1 shows the FileStream in action. The comments in the code describe what happens in every step.
Listing 17.1. Manipulating a file from our page in the code-behind file
C#
[View full width]
using System;
using System.IO;
using System.Text;
using System.Web;
public partial class FileFun_aspx
{
public void Page_Load(object sender, EventArgs e)
{
// open a new FileStream
FileStream fs = new FileStream(@"C:\Documents and Settings\Jeff
\Desktop\test.txt",FileMode.Create);
// create a byte array to write to the stream
byte[] bytes = Encoding.ASCII.GetBytes("This is my string!");
fs.Write(bytes, 0, bytes.Length);
// move to the 11 index position
fs.Seek(11, SeekOrigin.Begin);
// write new text there
bytes = Encoding.ASCII.GetBytes("special code!");
fs.Write(bytes, 0, bytes.Length);
// go to the same position, but only write enough bytes
s to overwrite "special"
fs.Seek(11, SeekOrigin.Begin);
// perform the overwrite
bytes = Encoding.ASCII.GetBytes("waycool");
fs.Write(bytes, 0, bytes.Length);
// now read back the stream into our byte array
fs.Seek(0, SeekOrigin.Begin);
byte[] readBytes = new byte[fs.Length];
fs.Read(readBytes, 0, Convert.ToInt32(fs.Length));
// convert the byte array into a string and output to Trace
// Output: "This is my waycool code!"
Trace.Warn(Encoding.ASCII.GetString(readBytes));
fs.Close();
// open a new FileStream in Append mode
fs = new FileStream(@"C:\Documents and Settings\Jeff
\Desktop\test.txt", FileMode.Append);
bytes = Encoding.ASCII.GetBytes(" This was appended.");
fs.Write(bytes, 0, bytes.Length);
fs.Close();
// open a new FileStream, read-only
fs = new FileStream(@"C:\Documents and Settings\Jeff\Desktop
\test.txt", FileMode.Open,FileAccess.Read);
readBytes = new byte[fs.Length];
fs.Read(readBytes, 0, Convert.ToInt32(fs.Length));
// convert the byte array into a string and output to Trace
// Output: "This is my waycool code! This was appended."
Trace.Warn(Encoding.ASCII.GetString(readBytes));
fs.Close();
}
}
VB.NET
[View full width]
Imports System
Imports System.IO
Imports System.Text
Imports System.Web
Public Partial Class FileFun_aspx
Public Sub Page_Load(sender As Object, e As EventArgs)
' open a new FileStream
Dim fs As New FileStream("C:\Documents and Settings\Jeff
\Desktop\test.txt", FileMode.Create)
' create a byte array to write to the stream
Dim bytes As Byte() = Encoding.ASCII.GetBytes("This is my string!")
fs.Write(bytes, 0, bytes.Length)
' move to the 11 index position
fs.Seek(11, SeekOrigin.Begin)
' write new text there
bytes = Encoding.ASCII.GetBytes("special code!")
fs.Write(bytes, 0, bytes.Length)
' go to the same position, but only write enough bytes to
' overwrite "special"
fs.Seek(11, SeekOrigin.Begin)
' perform the overwrite
bytes = Encoding.ASCII.GetBytes("waycool")
fs.Write(bytes, 0, bytes.Length)
' now read back the stream into our byte array
fs.Seek(0, SeekOrigin.Begin)
Dim readBytes(fs.Length) As Byte
fs.Read(readBytes, 0, Convert.ToInt32(fs.Length))
' convert the byte array into a string and output to Trace
' Output: "This is my waycool code!"
Trace.Warn(Encoding.ASCII.GetString(readBytes))
fs.Close()
' open a new FileStream in Append mode
fs = New FileStream("C:\Documents and Settings\JeffDesktop\test.txt", FileMode.Append)
bytes = Encoding.ASCII.GetBytes(" This was appended.")
fs.Write(bytes, 0, bytes.Length)
fs.Close()
' open a new FileStream, read-only
fs = New FileStream("C:\Documents and Settings\Jeff
\Desktop\test.txt", FileMode.Open,FileAccess.Read)
readBytes = New Byte(fs.Length) {}
fs.Read(readBytes, 0, Convert.ToInt32(fs.Length))
' convert the byte array into a string and output to Trace
' Output: "This is my waycool code! This was appended."
Trace.Warn(Encoding.ASCII.GetString(readBytes))
fs.Close()
End Sub
End Class
As the code demonstrates, manipulating a stream can involve a bit of conversion when it comes to writing and reading text. You can just as easily read and write bytes that mean virtually anything. We use static properties of System.Text.Encoding to do these conversions. These properties (we use ASCII, but there are others) are actually of the Encoding type themselves.
Another important thing to note here is that we call the Close() method of our FileStream objects. This releases the file from our use so that other processes can use it. If you don't explicitly release a resource like this, it will eventually be released, but in the meantime it might be locked up by our process, causing errors in other processes or even our own application.
Reading and writing text to files is probably one of the best-documented concepts in the .NET documentation. The definition for the FileStream class alone has nearly two dozen links to various file operations. |
This raw manipulation of the FileStream is straightforward, but there are two helper classes that can get the job done just as easily. These classes are the StreamWriter and StreamReader classes. What makes them particularly useful is that you can use them with any of the Stream derived classes to write and read characters to a stream. Listing 17.2 shows how to read and write using these classes.
Listing 17.2. Using the StreamWriter and StreamReader classes
C#
[View full width]
// open a new FileStream
FileStream fs = new FileStream(@"C:\Documents and Settings\Jeff\Desktop\test.txt",FileMode.Create);
// create a StreamWriter, based on our FileStream
StreamWriter sw = new StreamWriter(fs);
// write to the stream
sw.Write("This is my string!");
sw.Close();
fs.Close();
fs = new FileStream(@"C:\Documents and Settings\Jeff\Desktop\test.txt", FileMode.Open,FileAccess.Read);
// create a StreamReader, based on our FileStream
StreamReader sr = new StreamReader(fs);
// Output: "This is my string!"
Trace.Warn(sr.ReadLine());
sr.Close();
fs.Close();
VB.NET
[View full width]
' open a new FileStream
Dim fs As New FileStream("C:\Documents and Settings\Jeff\Desktop\test.txt", FileMode.Create)
' create a StreamWriter, based on our FileStream
Dim sw As New StreamWriter(fs)
' write to the stream
sw.Write("This is my string!")
sw.Close()
fs.Close()
fs = New FileStream("C:\Documents and SettingsJeff\Desktop\test.txt", FileMode.Open,FileAccess.Read)
' create a StreamReader, based on our FileStream
Dim sr As New StreamReader(fs)
' Output: "This is my string!"
Trace.Warn(sr.ReadLine())
sr.Close()
fs.Close()