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

C# Bible - Jeff Ferguson, Brian Patterson, Jason Beres

.pdf
Скачиваний:
64
Добавлен:
24.05.2014
Размер:
4.21 Mб
Скачать

BinaryFile = new FileStream("test.dat", FileMode.Create, FileAccess.ReadWrite);

ByteArray = new byte [256];

}

public void WriteBytes()

{

int ArrayIndex;

for(ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++) ByteArray[ArrayIndex] = (byte)ArrayIndex;

BinaryFile.Write(ByteArray, 0, 256);

}

public bool ReadBytes()

{

int ArrayIndex;

BinaryFile.Seek(0, SeekOrigin.Begin);

BinaryFile.Read(ByteArray, 0, 256);

for(ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++)

{

if(ByteArray[ArrayIndex] != (byte)ArrayIndex) return false;

}

return true;

}

}

class MainClass

{

static public void Main()

{

FileTestClass FileTest = new FileTestClass(); bool ReadTest;

FileTest.WriteBytes();

ReadTest = FileTest.ReadBytes(); if(ReadTest == true)

Console.WriteLine("The readback test was successful."); else

Console.WriteLine("The readback test failed.");

}

}

Listing 25-1 implements two C# classes: FileTestClass, which contains the stream I/O code, and MainClass, which contains the application's Main() method. The Main() method creates an object of the FileTestClass class and asks the object to write and read data.

The FileTestClass class contains a private member representing a FileStream object. The class's constructor creates a new FileStream object using a constructor that accepts three arguments:

The pathname of the file stream to be operated on

A file operation mode specification

A file access mode specification

The file operation mode specification is represented by an enumeration named FileMode. The FileMode enumeration is found in the .NET System.IO namespace and supports the following enumeration members:

Append, which instructs the file stream class to open the named file if it exists. If the named file exists, the file stream class initializes itself to write data to the end of the existing file. If the named file does not exist, the class creates a new file with the specified name.

Create, which instructs the file stream to create the named file. If the file already exists, it is overwritten.

CreateNew, which, like Create, instructs the file stream to create the named file. The difference between CreateNew and Create is how existing files are handled. If the file already exists when CreateNew is specified as the file mode, the file stream class throws an exception of class IOException.

Open, which instructs the file stream to open the named file.

OpenOrCreate, which instructs the file stream to create the named file. If the named file already exists, the FileStream object opens the named file.

Truncate, which instructs the file stream to open the named file and then immediately truncate it so that its size is zero bytes.

The file access mode specification is represented by an enumeration named FileAccess. The FileAccess enumeration is also found in the .NET System.IO namespace and supports the following enumeration members:

Read, which specifies that the FileStream class should allow read access to the named file. Data can be read from the file, but not written to the file.

ReadWrite, which specifies that the FileStream class should allow both read and write access to the named file.

Write, which specifies that the FileStream class should allow write access to the named file. Data can be written to the file, but not read back.

The FileTestClass constructor shown in Listing 25-1 creates a new file stream that manages a file named test.dat. The file is opened in creation mode for read/write access.

The WriteBytes() method of the FileTestClass populates a 256-byte buffer, which is created by the class's constructor. It populates the buffer with 256 bytes with values from hex 00 to hex FF. The buffer is then written to the stream with the file stream method Write(). The Write() method accepts three arguments:

A reference to the byte buffer containing the data to be written

An integer specifying the array element of the first byte in the buffer to be written

An integer specifying the number of bytes to be written

The Write() method is synchronous, and the method does not return until the data has actually been written to the stream.

The ReadBytes() method of the FileTestClass reads the 256 bytes written by WriteBytes() and compares the bytes with the byte pattern implemented by WriteBytes().

The first operation that the ReadBytes() method performs involves moving the stream pointer back to the beginning of the file. Stream positioning is an important concept and deserves special mention. Streams support the concept of file positioning. A stream position refers to a position in the stream where the next I/O operation will take place. Usually, a stream position is set to the beginning of a stream when the stream is initialized. As data is read from or written to the stream, the stream position is advanced to the position just beyond the last operation.

Figure 25-2 illustrates this concept. It shows a stream with six bytes of data.

Figure 25-2: Stream I/O advances the stream position.

When the stream is initially opened, the stream position points to the first byte in the stream. This is illustrated in the top diagram in Figure 25-2. Suppose that the code that manages the stream reads three bytes from the file. The three bytes are read, and the stream position will point to the byte just beyond the last read position. Using this example, the stream position points to the fourth byte in the stream. This is illustrated in the bottom diagram in Figure 25-2.

The issue with the code in Listing 25-1 has to do with the fact that a file is created and 256 bytes are written to the file. After the bytes are written, they are read back. However, it is important to remember two stream positioning concepts:

The file position is updated after every read or write operation to point to a position just beyond the last operation.

Read and write operations start at the byte referenced by the stream position.

When the code in Listing 25-1 creates the new file stream, the file position is set to the beginning of the (empty) file. After the 256 bytes are written, the file position is updated to reference the position just after the 256 bytes. If the code were to read the 256 bytes just after the write operation, the read operation would fail because the stream position points to the end of the file after the write operation, and a read operation would attempt to read 256 bytes starting at that position, and there are no bytes available for reading at that position. The code needs to say, "before the read operation begins, adjust the file pointer back to the beginning of the stream so that the read operation is successful."

A method in the Stream class called Seek() accomplishes this task. The Seek() method enables your code to move, or seek, the stream position to any available in the stream. The Seek() method takes two parameters:

A long integer, specifying a positioning offset in bytes

A value from an enumeration called SeekOrigin, which specifies the stream position that should be used as the starting point for the seek operation

The SeekOrigin enumeration is declared in the System.IO namespace and supports the following values:

Begin, which indicates that the seek operation should be performed relative to the beginning of the stream

Current, which indicates that the seek operation should be performed relative to the current stream position

End, which indicates that the seek operation should be performed relative to the end of the stream

The Seek() method adjusts the stream position so that it points to the stream position referenced by the SeekOrigin enumeration, which is then offset by the specified number of bytes. The byte offset used in the Seek() method can be positive or negative. The following example uses a positive offset value:

File.Seek(4, SeekOrigin.Begin);

The preceding line adjusts the stream pointer to point to four bytes beyond the beginning of the stream. Positive offset values move the stream pointer toward the end of the stream. The following example uses a negative offset value:

File.Seek(-2, SeekOrigin.End);

This example adjusts the stream pointer to point to two bytes before the end of the stream. Negative offset values move the stream pointer toward the beginning of the stream.

The code in Listing 25-1 uses the following seek code before the 256 bytes are read:

BinaryFile.Seek(0, SeekOrigin.Begin);

This call adjusts the stream pointer back to the beginning of the stream. When the read operation begins, it starts reading from the beginning of the stream.

The ReadBytes() method uses the FileStream method called Read() to perform synchronous read I/O on the stream. The Read() method accepts three arguments:

A reference to the byte buffer to be used to contain the bytes read from the stream

An integer specifying the array element of the first byte in the buffer to contain data read from the stream

An integer specifying the number of bytes to be read

The Read() method is synchronous and does not return until the data has actually been read from the stream. When the read operation is complete, the code checks the byte pattern found in the array to ensure that it matches the byte pattern that was written.

Understanding asynchronous I/O

Listing 25-2 is a modification of Listing 25-1 that illustrates asynchronous I/O. Unlike synchronous I/O, in which calls to read and write operations do not return until the operation is complete, calls to asynchronous I/O operations return soon after they are called. The actual I/O operation is performed behind the scenes, on a separate thread created by the implementation of the asynchronous I/O methods in the .NET Framework, and your code is notified when the operation is complete through a delegate. The advantage to asynchronous I/O is that your main code need not be tied up waiting for an I/O operation to complete. Performing lengthy I/O operations in the background frees your application to perform other tasks, such as processing Windows messages in Windows Forms applications.

Listing 25-2: Synchronous Writing, Asynchronous Reading

using System; using System.IO;

using System.Threading;

class FileTestClass

 

{

BinaryFile;

private FileStream

private byte []

ByteArray;

private IAsyncResult

AsyncResultImplementation;

private AsyncCallback ReadBytesCompleteCallback;

public FileTestClass()

{

AsyncResultImplementation = null;

BinaryFile = new FileStream("test.dat", FileMode.Create, FileAccess.ReadWrite);

ByteArray = new byte [256];

ReadBytesCompleteCallback = new AsyncCallback(OnReadBytesComplete);

}

public void WriteBytes()

{

int ArrayIndex;

for(ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++) ByteArray[ArrayIndex] = (byte)ArrayIndex;

BinaryFile.Write(ByteArray, 0, 256);

}

public void ReadBytes()

{

BinaryFile.Seek(0, SeekOrigin.Begin);

AsyncResultImplementation = BinaryFile.BeginRead(ByteArray, 0, 256, ReadBytesCompleteCallback, null);

}

public void OnReadBytesComplete(IAsyncResult AsyncResult)

{

int ArrayIndex;

int BytesRead; int Failures;

BytesRead = BinaryFile.EndRead(AsyncResult); Console.WriteLine("Bytes read........: {0}", BytesRead); Failures = 0;

for(ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++)

{

if(ByteArray[ArrayIndex] != (byte)ArrayIndex)

{

Console.WriteLine("Read test failed for byte at offset

{0}.",

ArrayIndex);

Failures++;

}

}

Console.WriteLine("Read test failures: {0}", Failures);

}

public void WaitForReadOperationToFinish()

{

WaitHandle WaitOnReadIO;

WaitOnReadIO = AsyncResultImplementation.AsyncWaitHandle; WaitOnReadIO.WaitOne();

}

}

class MainClass

{

static public void Main()

{

FileTestClass FileTest = new FileTestClass();

FileTest.WriteBytes();

FileTest.ReadBytes();

FileTest.WaitForReadOperationToFinish();

}

}

Reading asynchronously

Listing 25-2 builds on the example shown in Listing 25-1 by performing the read operation asynchronously. The write operation is still performed synchronously. The stream is initialized in the same manner regardless of the way I/O is performed. Streams can be operated on in a synchronous manner for all I/O operations, in an asynchronous manner for all I/O operations, or in a combination of synchronous and asynchronous manners.

The write operations code in Listing 25-2 is handled synchronously, and its code is identical to the write operations code in Listing 25-1. The read operation, however, is quite different.

The read operations code in Listing 25-2 starts not with a call to the stream's synchronous Read() method but with a call to the stream's asynchronous BeginRead() method. This call accepts five parameters. The first three parameters match the parameters accepted by the synchronous Read() method, but the last two parameters are new:

A reference to the byte buffer to be used to contain the bytes read from the stream

An integer specifying the array element of the first byte in the buffer to contain data read from the stream

An integer specifying the number of bytes to be read

Call-specific data

The callback delegate must be an object of a class called AsyncCallback. The AsyncCallback class is declared in the .NET Framework System namespace and manages a method that returns nothing and accepts a reference to an interface called IAsyncResult. Listing 25-2 creates an instance of this delegate in the constructor of the FileTestClass class:

ReadBytesCompleteCallback = new AsyncCallback(OnReadBytesComplete);

The FileTestClass class in Listing 25-2 includes a new method called OnReadBytesComplete(), which is used as the delegate method. The Stream object invokes this delegate when the read operation is complete.

The IAsyncResult interface, which is used as the parameter to the AsyncCallback delegate, is defined in the .NET Framework System namespace. It supports four properties that can be used to obtain more information about the nature of the asynchronous operation:

AsyncState, which is a reference to the object that was provided as the last parameter of the BeginRead() method. The asynchronous I/O methods enable you to associate data with a specific operation in the last parameter to the I/O method. A copy of that data is available in the AsyncState property. Listing 25-2 has no need for data to be associated with the call, so it passes null as the last parameter to BeginRead(). As a result, the AsyncState property also has a value of null. You might want to use this data, for example, to distinguish one I/O call from another. It is legal for you to use the same delegate reference in multiple asynchronous I/O calls, for instance, and you might want to pass along data that distinguishes one call from another.

AsyncWaitHandle, which is a reference to an object of class WaitHandle. The WaitHandle class is declared in the .NET Framework System.Threading namespace. This object encapsulates a synchronization primitive and serves as a base class for specific primitives, such as mutexes and semaphores. Your code can wait on this handle to determine when the read operation actually completes. The code in Listing 25-2 does just that.

CompletedSynchronously, which is a Boolean that is set to True if the BeginRead() call completed synchronously, and False otherwise. Most stream implementations return False for this property when the interface references an asynchronous I/O operation.

IsCompleted, which is a Boolean that is set to True when the Stream object has completed the asynchronous operation. The property is set to False until then. Your code can destroy any stream-related resources after the IsCompleted property returns True.

An implementation of the IAsyncCallback interface is also returned from the call to BeginRead(). The code in Listing 25-2 caches the interface reference for later use.

The AsyncCallback method, which, in Listing 25-2, is the OnReadBytesComplete() method, is called by the Stream object when the asynchronous operation completes. The

implementation shown in Listing 25-2 begins with a call to EndRead(), which returns the number of bytes actually read from the operation. This number should match the number of bytes that were requested to be read by BeginRead().

Tip The call to EndRead() in Listing 25-2 is shown so that the number of bytes affected by the asynchronous operation can be found. If your code does not need this value, you do not need to call EndRead() at all.

The remainder of the implementation of the OnReadBytesComplete() method checks the byte pattern read in from the I/O operation and reports its findings to the console.

The Main() method in Listing 25-2 adds a new method call to the code from Listing 25-1, which is to a private method in the FileTestClass object called WaitForReadOperationToFinish(). Because the asynchronous read operation is the last operation in the code, the application could exit before the read operation can complete. Remember that the processing of the asynchronous I/O operation is performed on a separate thread. If the main thread exits before the I/O thread has a chance to finish, the code in OnReadBytesComplete() may not get a chance to finish. The WaitForReadOperationToFinish() method ensures that the operation completes before it returns to the caller.

The WaitForReadOperationToFinish() method uses the wait handle in the IAsyncCallback interface implementation to do its work. The method calls the WaitHandle method WaitOne() to wait until the wait handle is signaled. The call to WaitOne() does not return until the wait handle is signaled. The Stream object signals the wait handle only after the I/O operation completes. After the call to WaitOne() returns, you can be sure that the entire operation has completed.

Writing asynchronously

Asynchronous write I/O operations are similar to asynchronous read I/O operations. The only difference is that the BeginWrite() method is used instead of BeginRead(). Listing 25-3 improves on Listing 25-2 by implementing an asynchronous write operation.

Listing 25-3: Asynchronous Writing, Asynchronous Reading

using System; using System.IO;

using System.Threading;

class FileTestClass

 

{

BinaryFile;

private FileStream

private byte []

ByteArray;

private IAsyncResult

AsyncReadResultImplementation;

private IAsyncResult

AsyncWriteResultImplementation;

private AsyncCallback

ReadBytesCompleteCallback;

private AsyncCallback WriteBytesCompleteCallback;

public FileTestClass()

{

AsyncReadResultImplementation = null;

BinaryFile = new FileStream("test.dat", FileMode.Create,

FileAccess.ReadWrite);

ByteArray = new byte [256];

ReadBytesCompleteCallback = new AsyncCallback(OnReadBytesComplete); WriteBytesCompleteCallback = new

AsyncCallback(OnWriteBytesComplete);

}

public void WriteBytes()

{

int ArrayIndex;

for(ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++) ByteArray[ArrayIndex] = (byte)ArrayIndex;

AsyncWriteResultImplementation = BinaryFile.BeginWrite(ByteArray,

0,

256, WriteBytesCompleteCallback, null);

}

public void ReadBytes()

{

WaitForWriteOperationToFinish(); BinaryFile.Seek(0, SeekOrigin.Begin);

AsyncReadResultImplementation = BinaryFile.BeginRead(ByteArray, 0,

256,

ReadBytesCompleteCallback, null);

}

public void OnReadBytesComplete(IAsyncResult AsyncResult)

{

int ArrayIndex; int BytesRead; int Failures;

BytesRead = BinaryFile.EndRead(AsyncResult); Console.WriteLine("Bytes read........: {0}", BytesRead); Failures = 0;

for(ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++)

{

if(ByteArray[ArrayIndex] != (byte)ArrayIndex)

{

Console.WriteLine("Read test failed for byte at offset

{0}.",

ArrayIndex);

Failures++;

}

}

Console.WriteLine("Read test failures: {0}", Failures);

}

public void WaitForReadOperationToFinish()

{

WaitHandle WaitOnReadIO;

WaitOnReadIO = AsyncReadResultImplementation.AsyncWaitHandle; WaitOnReadIO.WaitOne();

}

public void OnWriteBytesComplete(IAsyncResult AsyncResult)

{

BinaryFile.EndWrite(AsyncResult);

}

private void WaitForWriteOperationToFinish()

{

WaitHandle WaitOnWriteIO;

WaitOnWriteIO = AsyncWriteResultImplementation.AsyncWaitHandle; WaitOnWriteIO.WaitOne();

}

}

class MainClass

{

static public void Main()

{

FileTestClass FileTest = new FileTestClass();

FileTest.WriteBytes();

FileTest.ReadBytes();

FileTest.WaitForReadOperationToFinish();

}

}

The EndWrite() method does not return a value, unlike the EndRead() method. The two methods are alike, however, in that they both block until the I/O operation is complete.

Understanding Writers and Readers

The .NET Framework ships with a variety of reader and writer classes that help you work with data more complicated than simple byte streams. Readers and writers encapsulate a stream and provide a translation layer that turns values into their byte stream equivalents (for writers) and vice versa (for readers).

Reader and writer classes in .NET are typically named to reflect the type of formatting that they perform. For example, the HtmlTextWriter class writes values destined for HTTP response information sent by ASP.NET, while the StringReader class reads values written using their string representation.

The writer and reader classes also handle varying encoding schemes, which is not possible using lower-level stream objects. The classes derived from the abstract TextWriter class, for example, enable your C# code to write text and have it encoded in the stream using ASCII, Unicode, UTF7, or UTF8 encoding algorithms.

Writing to streams with BinaryWriter

Listing 25-4 shows the BinaryWriter class in action. The job of the BinaryWriter class is to translate C# data types to a series of bytes that can be written to an underlying stream.

Listing 25-4: Working with the BinaryWriter Class

using System; using System.IO;

Соседние файлы в предмете Программирование