Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1
.pdf288 |
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
Figure 8-2 shows the results of the FileInfo.exe program.
Figure 8-2. Results of FileInfo.exe
Opening Files
There is no shortage of ways that you can open a file using the .NET Framework class library. There are 14 methods combined in the File and FileInfo class (see Table 8-4). Many of these methods have numerous parameter combinations. Both File and FileInfo use the same 7 method names, and each of the methods with the same name do the same thing. Though the methods have the same name, the parameters passed differ, or at least the first parameter differs.
There always seems to be one exception. The File::Create() has an overloaded method that has a buffer size parameter that the FileInfo class’s Create() method lacks.
Table 8-4. Opening a File Using the File and FileInfo Classes
Method |
Description |
Open() |
Creates a FileStream to a file providing a plethora of read/write and share |
|
privilege options |
Create() |
Creates a FileStream providing full read and write privileges to a file |
OpenRead() |
Creates a read-only FileStream to an existing file |
OpenWrite() |
Creates a write-only unshared FileStream to a file |
AppendText() |
Creates a StreamWriter that appends text to the end of an existing file |
CreateText() |
Creates a StreamWriter that writes a new text file |
OpenText() |
Creates a StreamReader that reads from an existing file |
|
|
You will see FileStream, StreamWriter, and StreamReader later in this chapter.
Of these 14 (7×2) methods, only 2 actually take any parameters (other than the name of the file you wish to open for the static methods). Basically, the .NET Framework class library provides 2 equivalent file open methods and 12 shortcuts.
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
289 |
The Open Methods
There are only two root open methods in the .NET Framework class library: File::Open() and FileInfo::Open(). These methods are virtually the same, except the File::Open() method has one additional parameter: the path to the file you want to open. The FileInfo::Open() method gets this information from its constructor.
The Open() method is made up of three overloaded methods. Each overload provides progressively more information about how you want the file opened. The first overload takes as a parameter the file mode with which you wish to open the file (see Table 8-5). Because the other two parameters are not specified, the file will open by default with read/write access and as unshared.
FileInfo |
^fileinfo = gcnew FileInfo("file.dat"); |
|
FileStream |
^fs = |
fileinfo.Open(FileMode::Truncate); |
// or |
|
|
FileStream |
^fs = |
File::Open("file.dat", FileMode::CreateNew); |
Table 8-5. FileMode Enumeration Values |
FileMode |
Description |
Append |
Opens a file if it exists and sets the next write point to the end of the file. If the file |
|
does not exist, it creates a new one. You can only use FileMode::Append with a |
|
file access of write-only, as any attempt to read throws an ArgumentException. |
Create |
Creates a new file. If the file already exists, it will be overwritten. |
CreateNew |
Creates a new file. If the file already exists, an IOException is thrown. |
Open |
Opens an existing file. If the file does not exist, a FileNotFoundException is thrown. |
OpenOrCreate |
Opens an existing file. If the file does not exist, it creates a new file. |
Truncate |
Opens an existing file and truncates it to a length of 0 bytes. If the file does not |
|
exist, a FileNotFoundException is thrown. |
|
|
The second overload takes the additional parameter of the file access you require the file to have (see Table 8-6). The file will also be opened by default as unshared.
FileInfo ^fileinfo = gcnew FileInfo("file.dat");
FileStream ^fs = fileinfo->Open(FileMode::Truncate, FileAccess::ReadWrite); // or
FileStream ^fs = File::Open("file.dat", FileMode::Append, FileAccess::Write);
Table 8-6. FileAccess Enumeration Values
FileAccess |
Description |
Read |
Allows data only to be read from the file |
ReadWrite |
Allows data to be read from and written to the file |
Write |
Allows data only to be written to the file |
|
|
290 |
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
The final overload has one more parameter. It specifies how the file is shared with others trying to access it concurrently (see Table 8-7).
FileInfo |
^fileinfo = gcnew FileInfo("file.dat"); |
FileStream |
^fs = fileinfo->Open(FileMode::Truncate, FileAccess::ReadWrite, |
|
FileShare::Read); |
// or
FileStream ^fs = File::Open("file.dat", FileMode::Append, FileAccess::Write,
|
FileShare::None); |
Table 8-7. FileShare Enumeration Values |
|
|
|
FileShare |
Description |
|
|
None |
Specifies exclusive access to the current file. Subsequent openings of the file by a |
|
process, including the current one, will fail until the file closes. |
Read |
Specifies that subsequent openings of the file by a process, including the current |
|
one, will succeed only if it is for a FileMode of Read. |
ReadWrite |
Specifies that subsequent openings of the file by a process, including the current |
|
one, will succeed for either reading or writing. |
Write |
Specifies that subsequent openings of the file by a process, including the current |
|
one, will succeed only if it is for a FileMode of Write. |
|
|
All those parameters make the file open process very configurable, but also a little tedious. This is especially true if you just want to open the file in a very generic and standard way. The .NET Framework class library provides you with a way to simplify file opening if the way you want to open a file happens to fall in one of six standard open configurations.
FileInfo |
^fileinfo |
= gcnew FileInfo("file.dat"); |
FileStream |
^CreateFile |
= fileinfo.Create(); |
FileStream |
^OpenReadFile |
= fileinfo.OpenRead(); |
FileStream |
^OpenWriteFile |
= fileinfo.OpenWrite(); |
StreamWriter |
^AppendTextFile |
= fileinfo.AppendText(); |
StreamWriter |
^CreateTextFile |
= fileinfo.CreateText(); |
StreamReader |
^OpenTextFile |
= fileinfo.OpenText(); |
// or |
|
|
FileStream |
^CreateFile |
= File::Create("file.dat"); |
FileStream |
^OpenReadFile |
= File::OpenRead("file.dat"); |
FileStream |
^OpenWriteFile |
= File::OpenWrite("file.dat"); |
StreamWriter |
^AppendTextFile |
= File::AppendText("file.dat"); |
StreamWriter |
^CreateTextFile |
= File::CreateText("file.dat"); |
StreamReader |
^OpenTextFile |
= File::OpenText("file.dat"); |
Notice that none of the preceding file opening methods takes any parameters, except the file path in the case of the static method of the File class. Personally, I think the names of the methods make them pretty self-explanatory.
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
291 |
I/O Manipulation
Okay, you now have a file open and it is time to actually do something with it. Oops, did I say “file”? Files are only one thing that you can do I/O manipulation with. You can also do I/O manipulation in and out of memory using the MemoryStream and BufferedStream classes and in and out of network sockets using NetworkStream. You will look at the MemoryStream class a little later to see how it differs from a FileStream.
There are several different means to accomplish I/O manipulation. You will examine the three most common: using Streams, using TextReaders and TextWriters, and using BinaryReaders and BinaryWriters. Figure 8-3 shows the class hierarchy for manipulating files.
Figure 8-3. The class hierarchy for I/O manipulation
Using Streams
In the computer world, streams are a method of transferring a sequential stream of data to and from one source to another in either a synchronous or asynchronous manner. The .NET Framework class library sends this data as a stream of bytes. A stream can also transfer these blocks of data starting from any location in one source to any location in another source.
What does this mean to you? Basically, you can read data, write data, and adjust the current location where you access the data. Not much to it, is there?
All stream-based I/O in the .NET Framework class library derives from the abstract base class Stream. The Stream class contains several virtual methods, which the inheriting class must define (see Table 8-8). Basically, these virtual methods define core Stream functionality and thus ensure that the inheriting class satisfies the definition of a stream as stated previously.
292 C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N
Table 8-8. The Virtual Methods and Properties of the Stream Class
Member |
Description |
CanRead |
A Boolean value specifying whether reading is supported. |
CanSeek |
A Boolean value specifying whether seeking is supported. |
CanWrite |
A Boolean value specifying whether writing is supported. |
Close() |
A method that closes the file and releases resources associated with the stream. |
Flush() |
This method moves the data from the source buffer to its destination source |
|
and then clears the buffer. If the stream does not support a buffer, this method |
|
does nothing. |
Length |
The length of the stream in bytes. |
Position |
If seeking is supported, then this property can be used to get or set the position |
|
in the stream. |
Read() |
Reads a specified number of bytes from the stream and then advances the position |
|
after the last read byte. |
ReadByte() |
Reads a single byte from the stream and then advances the position after the byte. |
Seek() |
If seeking is supported, then this method can be used to set the position in |
|
the stream. |
SetLength() |
Sets the length of the stream in bytes. |
Write() |
Writes a specified number of bytes to the stream and then advances the position |
|
after the last written byte. |
WriteByte() |
Writes one byte to the stream and then advances the position after the byte. |
|
|
You will see some of these properties and methods implemented in the following stream implementations.
FileStreams
One of the most common implementations of a Stream is the FileStream class. This class provides implementations for the abstract Stream class so that it can perform file-based streaming. Or, in other words, it allows you to read from and write to a file.
You have already seen several ways to open a FileStream. It is also possible to open a FileStream directly without using File or FileInfo. To do this, you use one of the FileStream’s many constructors. The most common parameters passed to the constructor are identical to those passed to the static
File::Open() method.
FileStream ^fs = gcnew FileStream("file.dat", FileMode::CreateNew);
FileStream ^fs = gcnew FileStream("file.dat", FileMode::Append,
FileAccess::Write);
FileStream ^fs = gcnew FileStream("file.dat", FileMode::Create,
FileAccess::Write, FileShare::None);
Once you finally have the FileStream open, you can start to read and/or write Bytes of data from or to it. As you saw from the virtual methods defined by the Stream class in Table 8-8, there are two ways of reading and writing to a stream. You can do it either by individual unsigned chars or by arrays of unsigned chars.
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
293 |
array<unsigned char>^ data = { 'A', 'p', 'p', 'l', 'e' }; fso->Write(data, 0, 4);
fso->WriteByte(data[4]);
array<unsigned char>^ ca = gcnew array<unsigned char>(5); ca[0] = fsi->ReadByte();
fsi->Read(ca, 1, 4);
Simply placing the location in the Position property sets the location of the next place to read from or write to the file.
fsi->Position = 0;
You can also set the location of the next read or write by the Seek() method. This method allows you to use offsets from the beginning of the file (same as the Position property), the current location, or the end of the file.
fsi->Seek(0, SeekOrigin::Begin);
If you desire further access but want the data available in the file (for another operation or just for safety), flush the file buffer.
fso->Flush();
You should always close your files after you are done with them.
fso->Close();
Listing 8-3 shows the FileStream class in action and demonstrates many of the functionalities described previously.
Listing 8-3. Working with a FileStream
using namespace System; using namespace System::IO;
void main()
{
FileStream ^fso = gcnew FileStream("file.dat", FileMode::Create, FileAccess::Write, FileShare::None);
array<unsigned char>^ data = gcnew array<unsigned char> { 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 't', 'e', 's', 't', '!', '\r', '\n', 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'o', 'n', 'l', 'y', ' ', 'a', ' ', 't', 'e', 's', 't', '.','\r', '\n' };
for (int i = 0; i < data->Length-5; i += 5)
{
fso->Write(data, i, 5);
}
for (int i = data->Length -4; i < data->Length; i++)
{
fso->WriteByte(data[i]);
}
294 |
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
fso->Close();
FileInfo ^fi = gcnew FileInfo("file.dat");
FileStream ^fsi = fi->OpenRead();
int b;
while ((b = fsi->ReadByte()) != -1)
{
Console::Write((Char)b);
}
fsi->Position = 0;
array<unsigned char>^ ca = gcnew array<unsigned char>(17); fsi->Read(ca, 0, 17);
for (int i = 0; i < ca->Length; i++)
{
Console::Write((Char)ca[i]);
}
Console::WriteLine();
fsi->Close();
fi->Delete(); // If you want to get rid of it
}
Figure 8-4 shows the file output generated by the FileStream.exe program.
Figure 8-4. File output of FileStream.exe
MemoryStreams
Programming with a MemoryStream is not much different from working with a FileStream. Obviously, what’s happening behind the scenes, on the other hand, is completely different. You’re no longer dealing with files; instead, you’re dealing with computer memory.
There are only a few differences from a coding perspective when you deal with a MemoryStream. Obviously, the constructor is different.
MemoryStream ^fs = gcnew MemoryStream();
A MemoryStream has an additional property and a couple of unique methods (see Table 8-9).
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
295 |
Table 8-9. Additional MemoryStream Property and Methods
Member |
Description |
Capacity |
This property gets or sets the number of bytes allocated to the stream. |
GetBuffer() |
This method returns an unsigned array of bytes that the stream created. |
WriteTo() |
This method writes the contents of the MemoryStream to another stream, which |
|
comes in handy if you want to write the stream out to a FileStream. |
|
|
Listing 8-4 shows the MemoryStream class in action and demonstrates many of the functionalities described previously.
Listing 8-4. Working with a MemoryStream
using namespace System; using namespace System::IO;
void main()
{
array<unsigned char>^ data = gcnew array<unsigned char> { 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 't', 'e', 's', 't', '!', '\r', '\n', 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'o', 'n', 'l', 'y', ' ', 'a', ' ', 't', 'e', 's', 't', '.','\r', '\n' };
MemoryStream ^ms = gcnew MemoryStream(); ms->Capacity = 40;
for (int i = 0; i < data->Length-5; i += 5)
{
ms->Write(data, i, 5);
}
for (int i = data->Length -4; i < data->Length; i++)
{
ms->WriteByte(data[i]);
}
array<unsigned char>^ ca = ms->GetBuffer(); for each (unsigned char c in ca)
{
Console::Write((Char)c);
}
Console::WriteLine();
FileStream ^fs = File::OpenWrite("file.dat");
ms->WriteTo(fs);
fs->Close(); ms->Close();
}
296 |
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
Figure 8-5 shows a display of the buffer contained within the MemoryStream. Figure 8-6 shows the results displayed to the console. Figure 8-7 shows the resulting file output generated by the MemoryStream.exe program. Notice that Figures 8-5 through 8-7 all have the same results, as expected.
Figure 8-5. Display of the buffer of the MemoryStream created by MemoryStream.exe
C H A P T E R 8 ■ I N P U T , O U T P U T , A N D S E R I A L I Z A T I O N |
297 |
Figure 8-6. Console results of MemoryStream.exe
Figure 8-7. File output of MemoryStream.exe
Using StreamReaders and StreamWriters
A drawback when using a FileStream is that it isn’t very String- or character-friendly. Because what you often want to store are Strings and characters, it only makes sense that methods be made to optimize and simplify the process of writing these to a stream. This is where the StreamReader and StreamWriter classes become helpful.
Just like the Stream class, the abstract StreamReader and StreamWriter classes define all the functionality that needs to be implemented to support String and character reading and writing (see Tables 8-10 and 8-11).
Table 8-10. Common StreamReader Members
Method |
Description |
Close() |
Closes the file and releases any resources |
Peek() |
Reads the next character without advancing the stream pointer |
Read() |
Reads data from the input stream |
ReadBlock() |
Reads a specified number of characters from the stream to a specified starting |
|
location in an input buffer |
ReadLine() |
Reads a line of data from the input stream and returns it as a String |
ReadToEnd() |
Reads the rest of the data from the current file location to the end and returns it |
|
as a single String |
|
|