Introduction
Most common applications require reading and writing of files. There are
numerous classes in the .NET BCL that provide various methods for
creating and manipulating files and directories. In this article I'll cover the
basic operations that you might have to perform with files. Since the
functionality offered by most of the classes discussed here is too extensive to
be completely covered, I only demonstrate some really common uses of these
classes. The readers can then explore the rest of the functionality on their
own. The first version of this article used C# examples, but in
this refined and expanded version I have used Managed C++. The
article comprises of various techniques and is not in the form of a top-to-down
tutorial. I welcome requests for more tips or techniques that readers would like
to see included in the article.
Getting file information
For this we can use the System.IO.FileInfo
class which has
several instance methods for performing various file operations. As a short
example we'll retrieve some information on Windows Notepad. I've used a native
API call to retrieve the Windows directory as I couldn't figure out the .NET way of doing it. I'd be obliged if someone could tell me how
to do it using .NET. Show is a user defined function
that I use for a padded output.
void FileInfoDemo()
{
TCHAR WinPath[MAX_PATH+1];
GetWindowsDirectory(WinPath,MAX_PATH+1);
String* NotepadPath = WinPath;
NotepadPath = String::Concat(NotepadPath,S"\\notepad.exe");
FileInfo* finfo = new FileInfo(NotepadPath);
Show("Name",finfo->Name);
Show("Directory",finfo->Directory);
Show("Extension",finfo->Extension);
Show("Length",__box(finfo->Length));
Show("FullName",finfo->FullName);
Show("CreationTime ",finfo->CreationTime.ToString());
}
Basically, all we do is to specify the path to the file in the constructor.
Now we can use some very useful Properties to query information
regarding our file. The FileInfo
class has methods that return
various stream readers and writers, but there are better methods to read and
write to files as I'll be covering, later in this article.
Enumerate sub-directories and files
We use the DirectoryInfo
class to enumerate sub-directories and
files in a particular folder. It has two methods among many others called
GetFiles
and GetDirectories
, the former returning a
FileInfo
array and the latter returning a
DirectoryInfo
array.
void DirectoryInfoDemo1()
{
DirectoryInfo* dinfo = new DirectoryInfo("C:\\");
DirectoryInfo* subdirs[] = dinfo->GetDirectories();
Console::WriteLine("Sub-Directories for C:\\");
Console::WriteLine("-----------------------");
for(int i=0; i<subdirs->Length; i++)
Show((i+1).ToString(),subdirs->Item[i]);
}
void DirectoryInfoDemo2()
{
DirectoryInfo* dinfo = new DirectoryInfo("C:\\");
FileInfo* filesindir[] = dinfo->GetFiles();
Console::WriteLine("Files under C:\\");
Console::WriteLine("---------------");
for(int i=0; i<filesindir->Length; i++)
Show((i+1).ToString(),filesindir->Item[i]);
}
Binary files
We can read and write binary files using the BinaryReader
and
BinaryWriter
classes. Just for fun, let's create a simple 16 bit
COM format executable. We can write it using just 4 lines of 16 bit assembler as
shown below. It simply calls Interrupt 21h Function
02h which outputs the character held in DL. I have also given the
machine code equivalents of these assembler statements. So all we do is create
an array holding these 8 bytes and then we use the BinaryWriter
class to write to a file. Now we can actually run this file and it will print an
'A' on screen.
MOV AH,02h
MOV DL,41h
INT 21h
INT 20h
void BinaryWriterDemo()
{
Console::WriteLine("Creating C:\\nish.com");
Console::WriteLine("This will output an 'A' to the console");
FileStream* fs = new FileStream("C:\\nish.com",
FileMode::Create,FileAccess::Write);
BinaryWriter* bw = new BinaryWriter(fs);
unsigned char SomeBinaryData __gc[] =
{0xb4,0x02,0xb2,0x41,0xcd,0x21,0xcd,0x20};
bw->Write(SomeBinaryData);
bw->Flush();
bw->Close();
}
Now I'll show you how to use the BinaryReader
class to read the
binary file we created above and display the bytes in the file.
void BinaryReaderDemo()
{
Console::WriteLine("Reading C:\\nish.com");
FileStream* fs = new FileStream("C:\\nish.com",
FileMode::Open,FileAccess::Read);
BinaryReader* br = new BinaryReader(fs);
unsigned char c;
while(br->PeekChar() != -1)
{
c = br->ReadByte();
Console::Write("{0:X2} ",__box(c));
}
br->Close();
Console::WriteLine();
}
One small issue with the BinaryReader.ReadByte
Method is that it
throws an EndOfStreamException
instead of indicating it using a
special return value. To avoid handling the exception and doing it the old-C
style way, which may or may not be a good thing, I have used
PeekChar
which will return the next byte but will not move the file
pointer.
Text Files
There are more than one way of reading and writing text files, but here I'll
show you how to use the StreamWriter
and StreamReader
classes. They remind me very much of the MFC
CStdioFile
class. The StreamReader
class has a
ReadLine
method and the StreamWrite
class has a
WriteLine
method both of which are exactly similar in behaviour
with the CStdioFile::ReadString
and
CStdioFile::WriteString
methods. Both the classes have constructors
that take a file path as argument and thus we don't need to create the
FileStream
object first as we had to do with the
BinaryReader
and BinaryWriter
classes.
void StreamWriterDemo()
{
Console::WriteLine("Creating C:\\TempFile.txt");
StreamWriter *sw = new StreamWriter ("C:\\TempFile.txt");
sw->WriteLine("This is the first line");
sw->WriteLine("This is the second line");
sw->WriteLine("This is the third/last line");
sw->Close();
}
void StreamReaderDemo()
{
Console::WriteLine("Reading C:\\TempFile.txt");
Console::WriteLine("-----------------------");
StreamReader* sr = new StreamReader ("C:\\TempFile.txt");
String* s;
while(s = sr->ReadLine())
Console::WriteLine(s);
sr->Close();
}
Reading from a string
They think of everything, don't they. StringReader
is a class
derived from TextReader
that allows us to read from a string
directly. There is also a corresponding StringWriter
class. I am
not very definite about it's purpose, but I guess someone would find some use
for it. Maybe it might prove useful for parsing simple strings.
void StringReaderDemo(String* s)
{
Console::WriteLine("Reading the string [{0}]",s);
StringReader* sr = new StringReader(s);
int c;
while((c = sr->Read()) != -1)
Console::Write("{0} ",__box(Convert::ToChar(c)));
Console::WriteLine();
sr->Close();
}