Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

672 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

FileInfo f3 = new FileInfo(@"C:\Test3.dat"); using(FileStream readOnlyStream = f3.OpenRead())

{

// Use the FileStream object...

}

// Now get a FileStream object with write-only permissions.

FileInfo f4 = new FileInfo(@"C:\Test4.dat"); using(FileStream writeOnlyStream = f4.OpenWrite())

{

// Use the FileStream object...

}

}

The FileInfo.OpenText() Method

Another open-centric member of the FileInfo type is OpenText(). Unlike Create(), Open(),

OpenRead(), and OpenWrite(), the OpenText() method returns an instance of the StreamReader type, rather than a FileStream type. Assuming you have a file named boot.ini on your C drive, the following would be one manner to gain access to its contents:

static void Main(string[] args)

{

// Get a StreamReader object.

FileInfo f5 = new FileInfo(@"C:\boot.ini"); using(StreamReader sreader = f5.OpenText())

{

// Use the StreamReader object...

}

}

As you will see shortly, the StreamReader type provides a way to read character data from the underlying file.

The FileInfo.CreateText() and FileInfo.AppendText() Methods

The final two methods of interest at this point are CreateText() and AppendText(), both of which return a StreamWriter reference, as shown here:

static void Main(string[] args)

{

FileInfo f6 = new FileInfo(@"C:\Test5.txt"); using(StreamWriter swriter = f6.CreateText())

{

// Use the StreamWriter object...

}

FileInfo f7 = new FileInfo(@"C:\FinalTest.txt"); using(StreamWriter swriterAppend = f7.AppendText())

{

// Use the StreamWriter object...

}

}

As you would guess, the StreamWriter type provides a way to write character data to the underlying file.

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

673

Working with the File Type

The File type provides functionality almost identical to that of the FileInfo type, using a number of static members. Like FileInfo, File supplies AppendText(), Create(), CreateText(), Open(), OpenRead(), OpenWrite(), and OpenText() methods. In fact, in many cases, the File and FileInfo types may be used interchangeably. To illustrate, each of the previous FileStream examples can be simplified by using the File type instead:

static void Main(string[] args)

{

//Obtain FileStream object via File.Create(). using(FileStream fs = File.Create(@"C:\Test.dat"))

{

}

//Obtain FileStream object via File.Open(). using(FileStream fs2 = File.Open(@"C:\Test2.dat",

FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))

{

}

//Get a FileStream object with read-only permissions. using(FileStream readOnlyStream = File.OpenRead(@"Test3.dat"))

{

}

//Get a FileStream object with write-only permissions. using(FileStream writeOnlyStream = File.OpenWrite(@"Test4.dat"))

{

}

//Get a StreamReader object.

using(StreamReader sreader = File.OpenText(@"C:\boot.ini"))

{

}

// Get some StreamWriters.

using(StreamWriter swriter = File.CreateText(@"C:\Test3.txt"))

{

}

using(StreamWriter swriterAppend = File.AppendText(@"C:\FinalTest.txt"))

{

}

}

Additional File-centric Members

The File type also supports a few unique members shown in Table 20-6, which can greatly simplify the processes of reading and writing textual data.

674 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

Table 20-6. Methods of the File Type

Method

Meaning in Life

ReadAllBytes()

Opens the specified file, returns the binary data as an array of bytes, and then

 

closes the file

ReadAllLines()

Opens a specified file, returns the character data as an array of strings, and

 

then closes the file

ReadAllText()

Opens a specified file, returns the character data as a System.String, and then

 

closes the file

WriteAllBytes()

Opens the specified file, writes out the byte array, and then closes the file

WriteAllLines()

Opens a specified file, writes out an array of strings, and then closes the file

WriteAllText()

Opens a specified file, writes the character data, and then closes the file

 

 

Using these new methods of the File type, you are able to read and write batches of data in just a few lines of code. Even better, each of these new members automatically closes down the underlying file handle. For example, the following console program (named SimpleFileIO) will persist the string data into a new file on the C drive (and read it into memory) with minimal fuss:

using System; using System.IO;

class Program

{

static void Main(string[] args)

{

Console.WriteLine("***** Simple IO with the File Type *****\n");

string[] myTasks = {

"Fix bathroom sink", "Call Dave", "Call Mom and Dad", "Play Xbox 360"};

//Write out all data to file on C drive.

File.WriteAllLines(@"C:\tasks.txt", myTasks);

//Read it all back and print out.

foreach (string task in File.ReadAllLines(@"C:\tasks.txt"))

{

Console.WriteLine("TODO: {0}", task);

}

Console.ReadLine();

}

}

Clearly, when you wish to quickly obtain a file handle, the File type will save you some keystrokes. However, one benefit of first creating a FileInfo object is that you are able to investigate the file using the members of the abstract FileSystemInfo base class.

Source Code The SimpleFileIO project is located under the Chapter 20 subdirectory.

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

675

The Abstract Stream Class

At this point, you have seen numerous ways to obtain FileStream, StreamReader, and StreamWriter objects, but you have yet to read data from, or write data to, a file using these types. To understand how to do so, you’ll need to become familiar with the concept of a stream. In the world of I/O manipulation, a stream represents a chunk of data flowing between a source and a destination. Streams provide a common way to interact with a sequence of bytes, regardless of what kind of device (file, network connection, printer, etc.) is storing or displaying the bytes in question.

The abstract System.IO.Stream class defines a number of members that provide support for synchronous and asynchronous interactions with the storage medium (e.g., an underlying file or memory location). Figure 20-6 shows various descendents of the Stream type, seen through the eyes of the Visual Studio 2008 Object Browser.

Figure 20-6. Stream-derived types

Note Be aware that the concept of a stream is not limited to files IO. To be sure, the .NET libraries provide stream access to networks, memory locations, and other stream-centric abstractions.

Again, Stream descendents represent data as a raw stream of bytes; therefore, working directly with raw streams can be quite cryptic. Some Stream-derived types support seeking, which refers to the process of obtaining and adjusting the current position in the stream. To begin understanding the functionality provided by the Stream class, take note of the core members described in

Table 20-7.

676 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

Table 20-7. Abstract Stream Members

Member

Meaning in Life

CanRead, CanWrite

Determine whether the current stream supports reading, seeking, and/or

 

CanSeek writing.

Close()

Closes the current stream and releases any resources (such as sockets and

 

file handles) associated with the current stream. Internally, this method

 

is aliased to the Dispose() method; therefore “closing a stream” is

 

functionally equivalent to “disposing a stream.”

Flush()

Updates the underlying data source or repository with the current state of

 

the buffer and then clears the buffer. If a stream does not implement a

 

buffer, this method does nothing.

Length

Returns the length of the stream, in bytes.

Position

Determines the position in the current stream.

Read(), ReadByte()

Read a sequence of bytes (or a single byte) from the current stream and

 

advance the current position in the stream by the number of bytes read.

Seek()

Sets the position in the current stream.

SetLength()

Sets the length of the current stream.

Write(), WriteByte()

Write a sequence of bytes (or a single byte) to the current stream and

 

advance the current position in this stream by the number of bytes

 

written.

 

 

Working with FileStreams

The FileStream class provides an implementation for the abstract Stream members in a manner appropriate for file-based streaming. It is a fairly primitive stream; it can read or write only a single byte or an array of bytes. In reality, you will not often need to directly interact with the members of the FileStream type. Rather, you will most likely make use of various stream wrappers, which make it easier to work with textual data or .NET types. Nevertheless, for illustrative purposes, let’s experiment with the synchronous read/write capabilities of the FileStream type.

Assume you have a new Console Application named FileStreamApp. Your goal is to write a simple text message to a new file named myMessage.dat. However, given that FileStream can operate only on raw bytes, you will be required to encode the System.String type into a corresponding byte array. Luckily, the System.Text namespace defines a type named Encoding, which provides members that encode and decode strings to (or from) an array of bytes (check out the .NET Framework 3.5 SDK documentation for full details of the Encoding type).

Once encoded, the byte array is persisted to file using the FileStream.Write() method. To read the bytes back into memory, you must reset the internal position of the stream (via the Position property) and call the ReadByte() method. Finally, you display the raw byte array and the decoded string to the console. Here is the complete Main() method:

// Don't forget to import the System.Text and System.IO namespaces. static void Main(string[] args)

{

Console.WriteLine("***** Fun with FileStreams *****\n");

// Obtain a FileStream object.

using(FileStream fStream = File.Open(@"C:\myMessage.dat", FileMode.Create))

{

// Encode a string as an array of bytes.

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

677

string msg = "Hello!";

byte[] msgAsByteArray = Encoding.Default.GetBytes(msg);

// Write byte[] to file.

fStream.Write(msgAsByteArray, 0, msgAsByteArray.Length);

//Reset internal position of stream. fStream.Position = 0;

//Read the types from file and display to console.

Console.Write("Your message as an array of bytes: "); byte[] bytesFromFile = new byte[msgAsByteArray.Length]; for (int i = 0; i < msgAsByteArray.Length; i++)

{

bytesFromFile[i] = (byte)fStream.ReadByte(); Console.Write(bytesFromFile[i]);

}

//Display decoded messages.

Console.Write("\nDecoded Message: "); Console.WriteLine(Encoding.Default.GetString(bytesFromFile));

}

Console.ReadLine();

}

While this example does indeed populate the file with data, it punctuates the major downfall of working directly with the FileStream type: it demands to operate on raw bytes. Other Stream- derived types operate in a similar manner. For example, if you wish to write a sequence of bytes to a region of memory, you can allocate a MemoryStream. Likewise, if you wish to push an array of bytes through a network connection, you can make use of the NetworkStream type.

As mentioned, the System.IO namespace thankfully provides a number of “reader” and “writer” types that encapsulate the details of working with Stream-derived types.

Source Code The FileStreamApp project is included under the Chapter 20 subdirectory.

Working with StreamWriters and StreamReaders

The StreamWriter and StreamReader classes are useful whenever you need to read or write charac- ter-based data (e.g., strings). Both of these types work by default with Unicode characters; however, you can change this by supplying a properly configured System.Text.Encoding object reference. To keep things simple, let’s assume that the default Unicode encoding fits the bill.

StreamReader derives from an abstract type named TextReader, as does the related StringReader type (discussed later in this chapter). The TextReader base class provides a very limited set of functionality to each of these descendents, specifically the ability to read and peek into a character stream.

The StreamWriter type (as well as StringWriter, also examined later in this chapter) derives from an abstract base class named TextWriter. This class defines members that allow derived types to write textual data to a given character stream.

To aid in your understanding of the core writing capabilities of the StreamWriter and StringWriter classes, Table 20-8 describes the core members of the abstract TextWriter base class.

Note

678 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

Table 20-8. Core Members of TextWriter

Member

Meaning in Life

Close()

This method closes the writer and frees any associated resources. In the process,

 

the buffer is automatically flushed (again, this member is functionally equivalent

 

to calling the Dispose() method).

Flush()

This method clears all buffers for the current writer and causes any buffered data

 

to be written to the underlying device, but does not close the writer.

NewLine

This property indicates the newline constant for the derived writer class. The

 

default line terminator for the Windows OS is a carriage return followed by a line

 

feed (\r\n).

Write()

This overloaded method writes data to the text stream without a newline constant.

WriteLine()

This overloaded method writes data to the text stream with a newline constant.

 

 

 

 

The last two members of the TextWriter class probably look familiar to you. If you recall, the System.Console type has Write() and WriteLine() members that push textual data to the standard output device. In fact, the Console.In property wraps a TextWriter, and the Console.Out property wraps a

TextReader.

The derived StreamWriter class provides an appropriate implementation for the Write(), Close(), and Flush() methods, and it defines the additional AutoFlush property. This property, when set to true, forces StreamWriter to flush all data every time you perform a write operation. Be aware that you can gain better performance by setting AutoFlush to false, provided you always call Close() when you are done writing with a StreamWriter.

Writing to a Text File

To see the StreamWriter type in action, create a new Console Application named StreamWriterReaderApp. The following Main() method creates a new file named reminders.txt using the File.CreateText() method. Using the obtained StreamWriter object, you add some textual data to the new file, as shown here:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");

// Get a StreamWriter and write string data. using(StreamWriter writer = File.CreateText("reminders.txt"))

{

writer.WriteLine("Don't forget Mother's Day this year..."); writer.WriteLine("Don't forget Father's Day this year..."); writer.WriteLine("Don't forget these numbers:");

for(int i = 0; i < 10; i++) writer.Write(i + " ");

// Insert a new line. writer.Write(writer.NewLine);

}

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

679

Console.WriteLine("Created file and wrote some thoughts..."); Console.ReadLine();

}

Once you run this program, you can examine the contents of this new file (see Figure 20-7). You will find this file under the bin\Debug folder of your current application, given that you have not specified an absolute path at the time you called CreateText().

Figure 20-7. The contents of your *.txt file

Reading from a Text File

Now you need to understand how to programmatically read data from a file using the corresponding StreamReader type. As you recall, this class derives from the abstract TextReader, which offers the functionality described in Table 20-9.

Table 20-9. TextReader Core Members

Member

Meaning in Life

Peek()

Returns the next available character without actually changing the position of the

 

reader. A value of -1 indicates you are at the end of the stream.

Read()

Reads data from an input stream.

ReadBlock()

Reads a maximum of count characters from the current stream and writes the

 

data to a buffer, beginning at index.

ReadLine()

Reads a line of characters from the current stream and returns the data as a string

 

(a null string indicates EOF).

ReadToEnd()

Reads all characters from the current position to the end of the stream and returns

 

them as a single string.

 

 

If you now extend the current MyStreamWriterReader class to use a StreamReader, you can read in the textual data from the reminders.txt file as shown here:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");

...

// Now read data from file.

Console.WriteLine("Here are your thoughts:\n"); using(StreamReader sr = File.OpenText("reminders.txt"))

{

string input = null;

while ((input = sr.ReadLine()) != null)

{

680 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

Console.WriteLine (input);

}

}

Console.ReadLine();

}

Once you run the program, you will see the character data within reminders.txt displayed to the console.

Directly Creating StreamWriter/StreamReader Types

One of the slightly confusing aspects of working with the types within System.IO is that you can often achieve an identical result using numerous approaches. For example, you have already seen that you can obtain a StreamWriter via the File or FileInfo type using the CreateText() method. In reality, there is yet another way in which you can work with StreamWriters and StreamReaders: create them directly. For example, the current application could be retrofitted as follows:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");

//Get a StreamWriter and write string data. using(StreamWriter writer = new StreamWriter("reminders.txt"))

{

...

}

//Now read data from file.

using(StreamReader sr = new StreamReader("reminders.txt"))

{

...

}

}

Although it can be a bit confusing to see so many seemingly identical approaches to file I/O, keep in mind that the end result is greater flexibility. In any case, now that you have seen how to move character data to and from a given file using the StreamWriter and StreamReader types, you will next examine the role of the StringWriter and StringReader classes.

Source Code The StreamWriterReaderApp project is included under the Chapter 20 subdirectory.

Working with StringWriters and StringReaders

Using the StringWriter and StringReader types, you can treat textual information as a stream of in-memory characters. This can prove helpful when you wish to append character-based information to an underlying buffer. To illustrate, the following Console Application (named StringReaderWriterApp) writes a block of string data to a StringWriter object rather than a

file on the local hard drive:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with StringWriter / StringReader *****\n");

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

681

// Create a StringWriter and emit character data to memory. using(StringWriter strWriter = new StringWriter())

{

strWriter.WriteLine("Don't forget Mother's Day this year...");

//Get a copy of the contents (stored in a string) and pump

//to console.

Console.WriteLine("Contents of StringWriter:\n{0}", strWriter);

}

Console.ReadLine();

}

Because StringWriter and StreamWriter both derive from the same base class (TextWriter), the writing logic is more or less identical. However, given that nature of StringWriter, be aware that this class allows you to extract a System.Text.StringBuilder object via the GetStringBuilder() method:

using (StringWriter strWriter = new StringWriter())

{

strWriter.WriteLine("Don't forget Mother's Day this year..."); Console.WriteLine("Contents of StringWriter:\n{0}", strWriter);

// Get the internal StringBuilder.

StringBuilder sb = strWriter.GetStringBuilder(); sb.Insert(0, "Hey!! ");

Console.WriteLine("-> {0}", sb.ToString()); sb.Remove(0, "Hey!! ".Length); Console.WriteLine("-> {0}", sb.ToString());

}

When you wish to read from a stream of character data, make use of the corresponding StringReader type, which (as you would expect) functions identically to the related StreamReader class. In fact, the StringReader class does nothing more than override the inherited members to read from a block of character data, rather than a file, as shown here:

using (StringWriter strWriter = new StringWriter())

{

strWriter.WriteLine("Don't forget Mother's Day this year..."); Console.WriteLine("Contents of StringWriter:\n{0}", strWriter);

// Read data from the StringWriter.

using (StringReader strReader = new StringReader(strWriter.ToString()))

{

string input = null;

while ((input = strReader.ReadLine()) != null)

{

Console.WriteLine(input);

}

}

}

Source Code The StringReaderWriterApp is included under the Chapter 20 subdirectory.