Namespaces and the Base Classes

File & Folder Operations 2

Reading Text Files

Reading files is quite simple, since Microsoft have provided a large number of classes that represent streams, which may be used to transfer data. Transferring data here can include such things as reading and writing to files, or downloading from the Internet, or simply moving data from one location to another using a stream.

The available classes are all derived from the class System.IO.Stream, which can represent any stream, and the various classes represent different specializations, for example streams that are specifically geared to reading or writing to files. In general, for the examples in this chapter that involve using streams, there are potentially a number of different ways to write the code, using any of several of the available stream objects. However, in this chapter we'll just present one way that you could perform each of the processes of reading and writing to text and binary files.

Reading text files is quite simple - for this we're going to use the StreamReader class. The StreamReader represents a stream specifically geared to reading text. We'll demonstrate the process with a sample that reads in and displays the contents of the ReadMe.txt file generated by the developer environment's AppWizard for our earlier EnumFiles sample. The code looks like this.

  File fIn = new File
             ("C:\\dotNET Projects\\Namespaces\\EnumFiles\\ReadMe.txt");
  StreamReader strm = fIn.OpenText();

  // continue reading until end of file
  string sLine;
  do
  {
     sLine = strm.ReadLine();
     AddItem(sLine);
  }
  while (sLine != null);
  strm.Close();

We obtain a StreamReader instance using the OpenText() method of the File class. The StreamReader class contains several methods that either read or peek at differing amounts of data.

Peeking means looking ahead at the data, but without actually moving through the data. The best way to understand this is by imagining a pointer that indicates which bit of the file you are due to read next. If you read data, then the pointer will be moved to point at the byte following the last byte read, so the next read (or peek) will bring in the next block of data. If you peek at data, the pointer is not changed, so the next read (or peek) will retrieve the same data again.

The most useful method however for our purposes is ReadLine(), which reads as far as the next carriage return, returning the result in a string. If we have reached the end of the file, ReadLine() does not throw an exception, but simply returns a null reference - so we use this to test for the end of the file. Note that a null reference is not the same as an empty string. If we'd instead applied the condition

  while (sLine != "");

to the do loop, the loop would have finished the moment we came to a blank line in the file, not at the end of the file. (StreamReader.ReadLine() returns the string without the trailing carriage return and line feed).

Running this sample produces this output, showing it's correctly read the ReadMe.Txt file:

Writing Text Files

Writing text files follows similar principles to reading from them - in this case we use the StreamWriter class.

What makes writing text files even easier than reading them is that the method we use to write a line of text output followed by a carriage return-line feed, StreamWriter.WriteLine(), has a number of overloads so we don't necessarily need to pass it just text. It will accept a string, an object, a Boolean or several of the numeric types. This can be seen from this code sample, which writes out some text to the blank file we created earlier, MyNewFile.txt.

  StreamWriter strm = new StreamWriter
     ("C:\\dotNET Book\\MyNewFile.txt", false);

  strm.WriteLine("This is some text");
  strm.WriteLine("Next lines are numbers");
  strm.WriteLine(3);
  strm.WriteLine(4.55);
  strm.WriteLine("And the next line is a boolean");
  strm.WriteLine(true);
  strm.Close();    

The results of this can be seen in Notepad:

There are a number of overrides to the constructor of the StreamWriter. The constructor we have picked in our code sample is quite flexible, taking two parameters: the full name of the file, and a Boolean that indicates whether data should be appended to the file. If this is false then the contents of the file will be overwritten by the StreamWriter. In either case, the file will be opened if it already exists or created if it doesn't. This behavior can be customized by using other more complex constructors to the StreamWriter.

Reading Binary Files

Once we get to binary files we need to abandon the text-specific StreamReader and StreamWriter classes in place of a more general-purpose class. There are actually two classes that will do the job, System.IO.Stream and System.IO.FileStream. FileStream is designed specifically for reading and writing to files, while Stream is able to transmit data between files or other objects. The two classes work in very similar ways, and for the sake of demonstrating both of them, we'll use Stream for reading data and then use FileStream in the following sample which writes data to a file.

This sample demonstrates how to use the Stream class to read data. It opens a file and reads it, a byte at a time, each time displaying the numeric value of the byte read.

  File fl = new File("C:\\dotNET Book\\TestReader.txt");
  Stream strm = fl.OpenRead();
  int iNext;
  do
  {
     iNext = strm.ReadByte();
     if (iNext != -1)
        AddItem(iNext.ToString());
  }
  while (iNext != -1);
  strm.Close();

We obtain an instance of the Stream class by first instantiating a File object attached to the required file and calling its OpenRead() method. We then read through the file by calling the Stream.ReadByte() method. This method reads the next byte returning its value as an int. If we have reached the end of the file, then -1 is returned, but no exception is thrown - and it is this condition we use to test for the end of the file. Note that the Stream class also has a Read() method which can read a specified number of bytes in one go - I've chosen to use ReadByte() here as it leads to simpler code.

The file I've tested this code on looks like this - the first few letters of the alphabet followed by three carriage return-line feeds. Although the code will work on any file, I've demonstrated it on a text file because that makes it easier to see visually that the results are correct.

Running the code on this file produces the following:

Writing Binary Files

Although we can use either the Stream or FileStream classes to perform this task, we'll use an instance of FileStream for this sample. This code writes out a short text file that contains the letters FGHIJK followed by a carriage return-line feed combination. Note that again although this code is capable of writing any data, we're using textual data in the sample so that we can easily use Notepad to check that the sample has worked.

The code looks like this

  byte [] bytes = {70,71,72,73,74,75,13,10};
  FileStream strm = new FileStream
     ("C:\\dotNET Book\\TestWriter.txt", 
     FileMode.OpenOrCreate, FileAccess.Write);
  foreach (byte bNext in bytes)
  {
     strm.WriteByte(bNext);
  }
  strm.Close();

We first define an array of bytes that contains the data to be written to the file - in this case the ASCII codes of the characters. True binary data would have simply meant changing some of the values in this array to represent non-printable characters.

Next we instantiate a FileStream object. The constructor we use takes three parameters: the full pathname of the file, the mode we are using to open it and the access required. The mode and access merit more consideration -- they are enumerated values respectively taken from two further classes in the System.IO namespace: FileMode and FileAccess. The possible values these can take are all self-explanatory. In the case of the mode the possible values are Append, Create, CreateNew, Open, OpenOrCreate and Truncate. For the access they are Read, ReadWrite and Write.

Finally we use the WriteByte method of the FileStream object to write out each byte before closing the file. Again there is a FileStream.Write method, which can write out a number of bytes at a time and which you may prefer to use. We've stuck with WriteByte because it makes it clearer what is going on as we loop through the array.

And finally the test whether this code has worked: After running it, opening the new file with Notepad gives this:

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Debuggers don't remove bugs. They only show them in slow motion.”