Exercise index of this lecture   Alphabetic index   Course home   

Exercises and solutions
Input and Output Classes


10.1   A variant of the file copy program  

The purpose of this exercise is to train the use of the Read method in class Stream, and subclasses of class Stream.

Write a variant of the file copy program. Your program should copy the entire file into a byte array. Instead of the method ReadByte you should use the Read method, which reads a number of bytes into a byte array. (Please take a careful look at the documentation of Read in class FileStream before you proceed). After this, write out the byte array to standard output such that you can assure yourself that the file is read correctly.

Are you able to read the entire file with a single call to Read? Or do you prefer to read chunks of a certain (maximum) size?

Solution

Here is a solution:

using System;
using System.IO;

public class CopyApp {

  public static void Main(string[] args) {
    FileCopy(args[0]);
  }
 
  public static void FileCopy(string fromFile){
      try
      {
          using (Stream fromStream = new FileStream(fromFile, FileMode.Open))
          {
              long strmLgt = fromStream.Length;
              byte[] strmContent = new byte[strmLgt];          // A byte array large enough to 
                                                               // hold the fromFile.

              int totalBytesRead = 0,
                  bytesRead,
                  n = 0;

              const int chuckSize = 100; 

              do{
                bytesRead = fromStream.Read(strmContent, totalBytesRead, Math.Min(chuckSize, (int)strmLgt - totalBytesRead));   
                totalBytesRead += bytesRead;
                n++; 
              } while (totalBytesRead < strmLgt && bytesRead > 0);

              foreach(byte bt in strmContent) Console.Write((char)bt);

              Console.WriteLine();
              Console.WriteLine("Number of calls to Read: {0}", n);
          }
      }
      catch (FileNotFoundException e)
      {
          Console.WriteLine("File {0} not found: ", e.FileName);
          throw;
      }
      catch (Exception)
      {
          Console.WriteLine("Other file copy exception");
          throw;
      }
 }

}

In the solution above we allocate a byte array, which is large enough to hold the entire fromFile. In the do-while loop we repeatedly read chunks of chunkSize (100) bytes. After outputting the bytes in strmContent to standard output we report the number of calls to Read.


10.2   Finding the encoding of a given text file  

Make a UTF-8 text file with some words in Danish. Be sure to use plenty of special Danish characters. You may consider to write a simple C# program to create the file. You may also create the text file in another way.

In this exercise you should avoid writing a byte order mark (BOM) in your UTF-8 text file. (A BOM in the UTF-8 text file may short circuit the decoding we are asking for later in the exercise). One way to avoid the BOM is to denote the UTF-8 encoding with new UTF8Encoding(), or equivalently new UTF8Encoding(false). You may want to consult the constructors in class UFT8Encoding for more information.

Now write a C# program which systematically - in a loop - reads the text file six times with the following objects of type Encoding: ISO-8859-1, UTF-7, UTF-8, UTF-16 (Unicode), UTF32, and 7 bits ASCII.

More concretely, I suggest you make a list of six encoding objects. For each encoding, open a TextReader and read the entire file (with ReadToEnd, for instance) with the current encoding. Echo the characters, which you read, to standard output.

You should be able to recognize the correct, matching encoding (UTF-8) when you see it.

Solution

The following program generates a UTF-8 encoded text file:

using System;
using System.IO;
using System.Text;

public class TextWriterProg{

  public static void Main(){
    string str =      "A  u    i  ",
           strEquiv = "A \u00E6 u \u00E5 \u00E6 \u00F8 i \u00E6 \u00E5";
  
    TextWriter tw = new StreamWriter(                         // UTF-8
                       new FileStream("guess-me.txt", FileMode.Create),
                       new UTF8Encoding());
  
    tw.WriteLine(strEquiv);
    tw.Close();
  }

}

The program writes the Danish string to the file guess-me.txt

Here follows the program that reads guess-me.txt with six different encodings:

using System;
using System.IO;
using System.Text;

public class TextReaderProg{

  public static void Main(string[] args){
    Encoding[] encodings = new Encoding[] 
      {Encoding.GetEncoding("iso-8859-1"),
       new UTF8Encoding(),
       new UTF7Encoding(),
       new UnicodeEncoding(),
       new UTF32Encoding(),
       new ASCIIEncoding() };

    foreach(Encoding e in encodings){
      using(TextReader sr = new StreamReader(args[0], e)){
        Console.WriteLine("Encoding {0}:", e);
        Console.WriteLine(sr.ReadToEnd());
        Console.WriteLine();
      }
    }
  }

}

Notice that the program expects you to pass the name of the text file, guess-me.txt, as a program parameter.

The output is something like:

Encoding System.Text.Latin1Encoding:
A æ u å æ ø i æ å


Encoding System.Text.UTF8Encoding:
A  u    i  


Encoding System.Text.UTF7Encoding:
A æ u å æ ø i æ å


Encoding System.Text.UnicodeEncoding:
?ꛃ??쌠?ꛃ쌠??ꛃ쌠?

Encoding System.Text.UTF32Encoding:
??????

Encoding System.Text.ASCIIEncoding:
A ?? u ?? ?? ?? i ?? ??


We confirm from this that the original file is UTF8 encoded.


10.3   Die tossing - writing to text file  

Write a program that tosses a Die 1000 times, and writes the outcome of the tosses to a textfile. Use a TextWriter to accomplish the task.

Write another program that reads the text file. Report the number of ones, twos, threes, fours, fives, and sixes.

Solution

The output part of the program is here:

using System;
using System.IO;

class C{

  public static void Main(){

    Die d = new Die();
    using(TextWriter tw = new StreamWriter(new FileStream("die.txt", FileMode.Create))){
      for(int i = 1; i <= 1000; i++){
        d.Toss();
        tw.WriteLine(d.NumberOfEyes());
      }
   }

  }

}

Take at look at the file written by your program. Here is an outline of the output written by my version of the program:

3
1
6
5
1
3
5
5
3
6
1
...

The reading part of the program is here:

using System;
using System.IO;

class C{

  public static void Main(){

    int[] count = new int[7];  // use 1..6.
    int numberOfEyes;

    for(int i = 1; i <= 6; i++) count[i] = 0;

    using(TextReader tr = new StreamReader(new FileStream("die.txt", FileMode.Open))){
      for(int i = 1; i <= 1000; i++){
        numberOfEyes = Int32.Parse(tr.ReadLine());
        count[numberOfEyes]++;
      }
   }

   for(int i = 1; i <= 6; i++)
     Console.WriteLine("Number of {0}: {1}", i, count[i]);

  }

}

Based on the input read by the program, it generates the following on standard output - the screen:

Number of 1: 170
Number of 2: 140
Number of 3: 189
Number of 4: 184
Number of 5: 160
Number of 6: 157


10.4   Die tossing - writing to a binary file  

This exercise is a variant of the die tossing and file writing exercise based on text files.

Modify the program to use a BinaryWriter and a BinaryReader.

Take notice of the different sizes of the text file from the previous exercise and the binary file from this exercise. Explain your observations.

Solution

There are only a few differences between this solution and the corresponding text file solution.

The output part of the program is here:

using System;
using System.IO;

class C{

  public static void Main(){

    Die d = new Die();
    using(BinaryWriter tw = new BinaryWriter(new FileStream("die.bin", FileMode.Create))){
      for(int i = 1; i <= 1000; i++){
        d.Toss();
        tw.Write(d.NumberOfEyes());
      }
   }

  }

}

The size of the binary file is 4000 bytes, namely four bytes per integer. (This is actually waste of space. Can you do it better?) The size of the text file generated by the program from the previous exercise is 3000 bytes, namely one byte representing either '1', '2', '3', '4', '5', or '6' and two bytes for newline and carriage return (Windows end-of-line convention). Notice that we - in general - expect smaller binary files than the similar text files!

The reading part of the program is here:

using System;
using System.IO;

class C{

  public static void Main(){

    int[] count = new int[7];  // use 1..6.
    int numberOfEyes;

    for(int i = 1; i <= 6; i++) count[i] = 0;

    using(BinaryReader tr = new BinaryReader(new FileStream("die.bin", FileMode.Open))){
      for(int i = 1; i <= 1000; i++){
        numberOfEyes = tr.ReadInt32();
        count[numberOfEyes]++;
      }
   }

   for(int i = 1; i <= 6; i++)
     Console.WriteLine("Number of {0}: {1}", i, count[i]);

  }

}


10.5   Serializing one of your own classes  

The purpose of this exercise is to let you have your first experiences with serialization of some of your own object, which are instances of your own classes.

Select one or more of your own classes, typically from the project on which you are working.

Mark the classes as Serializable, in the same way as we have done in class Person and class Date on the accompanying slide page.

Consider if some of the instance variables should be marked as NonSerialized and if an OnDeserialized method should be provided.

Like in the client program of class Person and class Date, make a sample client class of your own classes, and call the Serialize and the Deserialize methods.


10.6   Serializing with an XML formatter  

In the programs shown on the accompanying slide we have used a binary formatter for serialization of Person and Date object.

Modify the client program to use a so-called Soap formatter in the namespace System.Runtime.Serialization.Formatters.Soap. SOAP is an XML language intended for exchange of XML documents. SOAP is related to the discipline of web services in the area of Internet technology.

After the serialization you should take a look at the file person.dat, which is written and read by the client program.

Solution

Here follows the program that serializes and deserializes the person and date objects to an XML file:

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

class Client{

  public static void Main(){
    Person p = new Person("Peter", new Date(1936, 5, 11));
    p.Died(new Date(2007,5,10));
    Console.WriteLine("{0}", p);

    using (FileStream strm = 
               new FileStream("person.dat", FileMode.Create)){
      IFormatter fmt = new SoapFormatter();
      fmt.Serialize(strm, p);
    }

    // -----------------------------------------------------------
    p = null;
    Console.WriteLine("Reseting person");
    // -----------------------------------------------------------
    
    using (FileStream strm = 
               new FileStream("person.dat", FileMode.Open)){
      IFormatter fmt = new SoapFormatter();
      p = fmt.Deserialize(strm) as Person;
    }

    Console.WriteLine("{0}", p);
  }

}

The XML file generated by the serialization is here:

<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Person id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/assem/person%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<name id="ref-4">Peter</name>
<age>71</age>
<dateOfBirth href="#ref-5"/>
<dateOfDeath href="#ref-6"/>
</a1:Person>
<a2:Date id="ref-5" xmlns:a2="http://schemas.microsoft.com/clr/assem/date%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<year>1936</year>
<month>5</month>
<day>11</day>
<nameOfDay>Monday</nameOfDay>
</a2:Date>
<a2:Date id="ref-6" xmlns:a2="http://schemas.microsoft.com/clr/assem/date%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<year>2007</year>
<month>5</month>
<day>10</day>
<nameOfDay>Thursday</nameOfDay>
</a2:Date>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>


Generated: Monday February 7, 2011, 12:19:56