File and Stream IO

File and Stream I/O

Classes in the System.IO namespace enable managed applications to perform file I/O and other forms of input and output. The fundamental building block for managed I/O is the stream, which is an abstract representation of byte-oriented data. Streams are represented by the System.IO.Stream class. Because Stream is abstract, System.IO as well as other namespaces include concrete classes derived from Stream that represent physical data sources. For example, System.IO.FileStream permits files to be accessed as streams; System.IO.MemoryStream does the same for blocks of memory. The System.Net.Sockets namespace includes a Stream derivative named NetworkStream that abstracts sockets as streams, and the System.Security.Cryptography namespace defines a CryptoStream class used to read and write encrypted streams.

Stream classes have methods that you can call to perform input and output, but the .NET Framework offers an additional level of abstraction in the form of readers and writers. The BinaryReader and BinaryWriter classes provide an easy-to-use interface for performing binary reads and writes on stream objects. StreamReader and StreamWriter, which derive from the abstract TextReader and TextWriter classes, support the reading and writing of text.

One of the most common forms of I/O that managed and unmanaged applications alike are called upon to perform is file I/O. The general procedure for reading and writing files in a managed application is as follows:

  1. Open the file using a FileStream object.

  2. For binary reads and writes, wrap instances of BinaryReader and BinaryWriter around the FileStream object and call BinaryReader and BinaryWriter methods such as Read and Write to perform input and output.

  3. For reads and writes involving text, wrap a StreamReader and StreamWriter around the FileStream object and use StreamReader and StreamWriter methods such as ReadLine and WriteLine to perform input and output.

  4. Close the FileStream object.

That this example deals specifically with file I/O is not to imply that readers and writers are only for files. They re not. Later in this chapter, you ll see a sample program that uses a StreamReader object to read text fetched from a Web page. The fact that readers and writers work with any kind of Stream object makes them powerful tools for performing I/O on any stream-oriented media.

System.IO also contains classes for manipulating files and directories. The File class provides static methods for opening, creating, copying, moving, and renaming files, as well as for reading and writing file attributes. FileInfo boasts the same capabilities, but FileInfo exposes its features through instance methods rather than static methods. The Directory and DirectoryInfo classes provide a programmatic interface to directories, enabling them to be created, deleted, enumerated, and more via simple method calls. Chapter 4 s ControlDemo application demonstrates how to use File and Directory methods to enumerate the files in a directory and obtain information about those files.

Text File I/O

The reading and writing of text files from managed applications is aided and abetted by the FileStream, StreamReader, and StreamWriter classes. Suppose you wanted to write a simple app that dumps text files to the console window the functional equivalent of the old DOS TYPE command. Here s how to go about it:

StreamReader reader = new StreamReader (filename); for (string line = reader.ReadLine (); line != null;line = reader.ReadLine ()) Console.WriteLine (line); reader.Close ();

The first line creates a StreamReader object that wraps a FileStream created from filename. The for loop uses StreamReader.ReadLine to iterate through the lines in the file and Console.WriteLine to output them to the console window. The final statement closes the file by closing the StreamReader.

That s the general approach, but in real life you have to anticipate the possibility that things might not go strictly according to plan. For example, what if the file name passed to StreamReader s constructor is invalid? Or what if the framework throws an exception before the final statement is executed, causing the file to be left open? Figure 3-1 contains the source code for a managed version of the TYPE command (called LIST to distinguish it from the real TYPE command) that responds gracefully to errors using C# exception handling. The catch block traps exceptions thrown when StreamReader s constructor encounters an invalid file name or when I/O errors occur as the file is being read. The finally block ensures that the file is closed even if an exception is thrown.

List.cs

using System; using System.IO; class MyApp { static void Main (string[] args) { // Make sure a file name was entered on the command line if (args.Length == 0) { Console.WriteLine ("Error: Missing file name"); return; } // Open the file and display its contents StreamReader reader = null; try { reader = new StreamReader (args[0]); for (string line = reader.ReadLine (); line != null; line = reader.ReadLine ()) Console.WriteLine (line); } catch (IOException e) { Console.WriteLine (e.Message); } finally { if (reader != null) reader.Close (); } } }
Figure 3-1

A managed application that mimics the TYPE command.

Because the FCL is such a comprehensive class library, passing a file name to StreamReader s constructor isn t the only way to open a text file for reading. Here are some others:

// Use File.Open to create a FileStream, and then wrap a // StreamReader around it FileStream stream = File.Open (filename, FileMode.Open, FileAccess.Read); StreamReader reader = new StreamReader (stream);

// Create a FileStream directly, and then wrap a // StreamReader around it FileStream stream = new FileStream (filename, FileMode.Open, FileAccess.Read); StreamReader reader = new StreamReader (stream);

// Use File.OpenText to create a FileStream and a // StreamReader in one step StreamReader reader = File.OpenText (filename);

There are other ways, too, but you get the picture. None of these methods for wrapping a StreamReader around a file is intrinsically better than the others, but they do demonstrate the numerous ways in which ordinary, everyday tasks can be accomplished using the .NET Framework class library.

StreamReaders read from text files; StreamWriters write to them. Suppose you wanted to write catch handlers that log exceptions to a text file. Here s a LogException method that takes a file name and an Exception object as input and uses StreamWriter to append the error message in the Exception object to the file:

void LogException (string filename, Exception ex) { StreamWriter writer = null; try { writer = new StreamWriter (filename, true); writer.WriteLine (ex.Message); } finally { if (writer != null) writer.Close (); } }

Passing true in the second parameter to StreamWriter s constructor tells the StreamWriter to append data if the file exists and to create a new file if it doesn t.

Binary File I/O

BinaryReader and BinaryWriter are to binary files as StreamReader and StreamWriter are to text files. Their key methods are Read and Write, which do exactly what you would expect them to. To demonstrate, the sample program in Figure 3-2 uses BinaryReader and BinaryWriter to encrypt and unencrypt files by XORing their contents with passwords entered on the command line. Encrypting a file is as simple as running Scramble.exe from the command line and including a file name and password, in that order, as in:

scramble readme.txt imbatman

To unencrypt the file, execute the same command again:

scramble readme.txt imbatman

XOR-encryption is hardly industrial-strength encryption, but it s sufficient to hide file contents from casual intruders. And it s simple enough to not distract from the main point of the application, which is to get a firsthand look at BinaryReader and BinaryWriter.

Scramble.cs contains two lines of code that merit further explanation:

ASCIIEncoding enc = new ASCIIEncoding (); byte[] keybytes = enc.GetBytes (key);

These statements convert the second command-line parameter a string representing the encryption key into an array of bytes. Strings in the .NET Framework are instances of System.String. ASCIIEncoding.GetBytes is a convenient way to convert a System.String into a byte array. Scramble XORs the bytes in the file with the bytes in the converted string. Had the program used UnicodeEncoding.GetBytes instead, encryption would be less effective because calling UnicodeEncoding.GetBytes on strings containing characters from Western alphabets produces a buffer in which every other byte is a 0. XORing a byte with 0 does absolutely nothing, and XOR encryption is weak enough as is without worsening matters by using keys that contain lots of zeros. ASCIIEncoding is a member of the System.Text namespace, which explains the using System.Text directive at the top of the file.

Scramble.cs

using System; using System.IO; using System.Text; class MyApp { const int bufsize = 1024; static void Main (string[] args) { // Make sure a file name and encryption key were entered if (args.Length < 2) { Console.WriteLine ("Syntax: SCRAMBLE filename key"); return; } string filename = args[0]; string key = args[1]; FileStream stream = null; try { // Open the file for reading and writing
Figure 3-2

A simple file encryption utility.

 stream = File.Open (filename, FileMode.Open, FileAccess.ReadWrite); // Wrap a reader and writer around the FileStream BinaryReader reader = new BinaryReader (stream); BinaryWriter writer = new BinaryWriter (stream); // Convert the key into a byte array ASCIIEncoding enc = new ASCIIEncoding (); byte[] keybytes = enc.GetBytes (key); // Allocate an I/O buffer and a key buffer byte[] buffer = new byte[bufsize]; byte[] keybuf = new byte[bufsize + keybytes.Length - 1]; // Replicate the byte array in the key buffer to create // an encryption key whose size equals or exceeds the // size of the I/O buffer int count = (1024 + keybytes.Length - 1) / keybytes.Length; for (int i=0; i<count; i++) Array.Copy (keybytes, 0, keybuf, i * keybytes.Length, keybytes.Length); // Read the file in bufsize blocks, XOR-encrypt each block, // and write the encrypted block back to the file long lBytesRemaining = stream.Length; while (lBytesRemaining > 0) { long lPosition = stream.Position; int nBytesRequested = (int) System.Math.Min (bufsize, lBytesRemaining); int nBytesRead = reader.Read (buffer, 0, nBytesRequested); for (int i=0; i<nBytesRead; i++) buffer[i] ^= keybuf[i]; stream.Seek (lPosition, SeekOrigin.Begin); writer.Write (buffer, 0, nBytesRead); lBytesRemaining -= nBytesRead; } } catch (Exception e) { Console.WriteLine (e.Message);

 } finally { if (stream != null) stream.Close (); } } }



Programming Microsoft  .NET
Applied MicrosoftNET Framework Programming in Microsoft Visual BasicNET
ISBN: B000MUD834
EAN: N/A
Year: 2002
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net