The .NET Framework class library provides four namespaces to support object serialization: System.Runtime.Serialization, System.Runtime.Serialization.Formatters, System.Runtime.Serialization.Formatters.Binary, and System.Runtime.Serialization.Formatters.Soap.

To serialize an object, you need to either mark the object class with the [Serializable] attribute or implement the ISerializable interface. The following is a class that is marked with the [Serializable] attribute:


[Serializable]
public class MySerializableClass
{
  ...
}

If a class to be serialized contains references to other objects, the classes of those other objects must also be marked [Serializable] or implement the ISerializable interface.

The ISerializable Interface

Unless you mark the class with the [Serializable] attribute, this interface must be implemented by all classes with serialized instances. Use the ISerializable interface if you want your class to control its own serialization and de-serialization.

The ISerializable interface only has one method, GetDataObject, to which you pass a SerializationInfo object and a StreamingContext object. The GetDataObject method will then populate the SerializationInfo object with the data necessary for the serialization of the target object.

The following code illustrates a class named MySerializableClass that implements the System.Runtime.Serialization.ISerializable interface. A SerializationInfo object and a StreamingContext object are available from the GetObjectData method.


  using System.Runtime.Serialization;

  public class MySerializableClass : ISerializable
  {
    SerializationInfo info;
    StreamingContext context;

    public void GetObjectData(SerializationInfo info, 
	                          StreamingContext context) 
    {
      this.info = info;
      this.context = context;

    // implementation code goes here
    }
  }

Classes that implement this interface include System.Data.DataSet, System.Drawing.Font, System.Collections.Hashtable, System.Drawing.Icon, and System.Drawing.Image.

The IFormatter Interface

IFormatter is a member of the System.Runtime.Serialization namespace. This interface is implemented by both the BinaryFormatter class and the SoapFormatter class. This interface has only two methods, Serialize and de-serialize. The Serialize method is used to serialize an object or an object containing other objects. The signature of this method is:


void Serialize( Stream serializationStream As Stream, 
     Object graph As Object );

The de-serialize method is used to de-serialize an object or an object containing other objects. This method has the following signature:


Object de-serialize( Stream serializationStream ) ;

Because the return value of the de-serialize method is an object of type Object, you need to downcast the object into the appropriate type. Two formats are available to store your persisted objects: binary and XML. Normally, you upcast either a BinaryFormatter object or a SoapFormatter object as an IFormatter object, depending on the format you use for the serialization.

The BinaryFormatter Class

The BinaryFormatter class is the only member of the System.Runtime.Serialization.Formatters.Binary namespace. You use this class to serialize and de-serialize an object or a graph of objects in a binary file. For performance, this is the format you need to choose when serializing your object(s). The resulting file is also more compact than the XML format.

The SoapFormatter Class

This is the only member of the System.Runtime.Serialization.Formatters.Soap namespace. You use this class to serialize and de-serialize an object or a graph of objects in XML format.

A Vector-Based Drawing Application

To illustrate the object serialization technique, the following is a simple vector-based drawing application. The user can draw three types of shapes: rectangles, lines, and ellipses, represented by the Rect, Line, and Ellipse classes, respectively. The three classes implement the IShape interface. The IShape and the three classes implementing it are given in Example 1. Note that all classes are marked with the [Serializable] attribute.

Example 1: Classes for objects that can be serialized.


using System;
using System.Drawing;
using System.Runtime.Serialization;

namespace MyCSharp
{
  public interface IShape 
  {
    void Draw(Graphics g);
  }

  [Serializable]
  public class Line : IShape
  {
    Point startPoint, endPoint;
    public Line(Point startPoint, Point endPoint) 
    {
      this.startPoint = startPoint;
      this.endPoint = endPoint;
    }
    public void Draw(Graphics g) 
    {
      g.DrawLine(Pens.Black, startPoint, endPoint);
    }
  }

  [Serializable]
  public class Rect : IShape
  {
    Rectangle rect;
    public Rect(Rectangle rect) 
    {
      this.rect = rect;
    }
    public void Draw(Graphics g)
    {
      g.DrawRectangle(Pens.Black, rect);
    }
  }

  [Serializable]
  public class Ellipse : IShape
  {
    Rectangle rect;
    public Ellipse(Rectangle rect)
    {
      this.rect = rect;
    }
    public void Draw(Graphics g)
    {
      g.DrawEllipse(Pens.Black, rect);
    }
  }

}

The Line class' constructor accepts two Point objects. The Rect and Ellipse classes' constructors accept a System.Drawing.Rectangle object. All classes implement the Draw method from the IShape interface. Now, let's shift our attention to the form for the application. The graphical user interface for the form of this application is shown in Figure 1.

Figure 1.
Figure 1. Drawing application that illustrates the object serialization technique.

As shown in Figure 1, there is a toolbar with three toolbar buttons, each of which is used to draw one of the three shapes. To select a shape to draw, click one of these buttons. The client area of the form contains nothing; however, the form wires the MouseDown and the MouseUp events to the this_MouseDown and this_MouseUp event handlers, respectively.

To draw a shape, the user clicks on the client area, drags, and releases the mouse button. The shapes drawn are added to an ArrayList called shapes. To draw the shapes, we override the form's OnPaint method. The OnPaint method iterates through the elements of the shapes ArrayList, casts each member of the ArrayList to IShape, and calls its Draw method. The Draw method is called by passing the Graphics object of the form, effectively making the drawing happen on the form's surface.

The only menu item, File, contains the following menu items: New, Open, Save, Save As, and Exit. The functions of these items are:

  • Clicking the New menu item clears the shapes ArrayList and the form's surface.
  • Clicking the Open menu item shows an Open File dialog, from which the user can select a file containing an ArrayList full of the previously-drawn shapes. Each shape will then be redrawn on the form.
  • Clicking the Save menu item serializes the shapes ArrayList into a file with the specified filename. If the filename is null, the application will try to call the SaveAs method.
  • Clicking the Save As menu item shows a Save File dialog. The user can choose the directory to which to save the object file, and select a filename.
  • Clicking the Exit item causes the application to exit.

The form class for this application is shown in Example 2.

Example 2. The form class for the application.


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;


namespace MyCSharp
{
  /// <summary>
  /// Summary description for Form1.
  /// </summary>
  public class Form1 : System.Windows.Forms.Form
  {
    private System.Windows.Forms.MainMenu mainMenu1;
    private System.Windows.Forms.MenuItem fileMenuItem;
    private System.Windows.Forms.MenuItem fileNewMenuItem;
    private System.Windows.Forms.MenuItem fileOpenMenuItem;
    private System.Windows.Forms.MenuItem fileSaveMenuItem;
    private System.Windows.Forms.MenuItem fileSaveAsMenuItem;
    private System.Windows.Forms.MenuItem fileExitMenuItem;
    private System.Windows.Forms.ToolBar toolBar1;
    private System.Windows.Forms.ToolBarButton lineToolBarButton;
    private System.Windows.Forms.ToolBarButton rectangleToolBarButton;
    private System.Windows.Forms.ToolBarButton ellipseToolBarButton;
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    private ArrayList shapes = new ArrayList();
    private enum shapeType
    {
      line, rectangle, ellipse,
    }

    private shapeType drawnShapeType = shapeType.line;
    private Point startPoint;
    private String filename;

    public Form1()
    {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();

      //
      // TODO: Add any constructor code after InitializeComponent call
      //
    }

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null) 
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

		#region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      this.mainMenu1 = 
	       new System.Windows.Forms.MainMenu();
      this.fileMenuItem = 
	       new System.Windows.Forms.MenuItem();
      this.fileNewMenuItem = 
	       new System.Windows.Forms.MenuItem();
      this.fileOpenMenuItem = 
	       new System.Windows.Forms.MenuItem();
      this.fileSaveMenuItem = 
	       new System.Windows.Forms.MenuItem();
      this.fileSaveAsMenuItem = 
	       new System.Windows.Forms.MenuItem();
      this.fileExitMenuItem = 
	       new System.Windows.Forms.MenuItem();
      this.toolBar1 = 
	       new System.Windows.Forms.ToolBar();
      this.lineToolBarButton = 
	       new System.Windows.Forms.ToolBarButton();
      this.rectangleToolBarButton = 
	       new System.Windows.Forms.ToolBarButton();
      this.ellipseToolBarButton = 
	       new System.Windows.Forms.ToolBarButton();
      this.SuspendLayout();
      // 
      // mainMenu1
      // 
      this.mainMenu1.MenuItems.AddRange(
	       new System.Windows.Forms.MenuItem[] {
                                           this.fileMenuItem});
      // 
      // fileMenuItem
      // 
      this.fileMenuItem.Index = 0;
      this.fileMenuItem.MenuItems.AddRange(
	       new System.Windows.Forms.MenuItem[] {
                                           this.fileNewMenuItem,
                                           this.fileOpenMenuItem,
                                           this.fileSaveMenuItem,
                                           this.fileSaveAsMenuItem,
                                           this.fileExitMenuItem});
      this.fileMenuItem.Text = "File";
      // 
      // fileNewMenuItem
      // 
      this.fileNewMenuItem.Index = 0;
      this.fileNewMenuItem.Shortcut = 
	       System.Windows.Forms.Shortcut.CtrlN;
      this.fileNewMenuItem.Text = 
	       "&New";
      this.fileNewMenuItem.Click += 
	       new System.EventHandler(this.fileNewMenuItem_Click);
      // 
      // fileOpenMenuItem
      // 
      this.fileOpenMenuItem.Index = 1;
      this.fileOpenMenuItem.Shortcut = 
	       System.Windows.Forms.Shortcut.CtrlO;
      this.fileOpenMenuItem.Text = 
	       "&Open";
      this.fileOpenMenuItem.Click += 
	       new System.EventHandler(this.fileOpenMenuItem_Click);
      // 
      // fileSaveMenuItem
      // 
      this.fileSaveMenuItem.Index = 2;
      this.fileSaveMenuItem.Shortcut = 
	       System.Windows.Forms.Shortcut.CtrlS;
      this.fileSaveMenuItem.Text = "&Save";
      this.fileSaveMenuItem.Click += 
	       new System.EventHandler(this.fileSaveMenuItem_Click);
      // 
      // fileSaveAsMenuItem
      // 
      this.fileSaveAsMenuItem.Index = 3;
      this.fileSaveAsMenuItem.Text = "Save &As";
      this.fileSaveAsMenuItem.Click += 
	       new System.EventHandler(this.fileSaveAsMenuItem_Click);
      // 
      // fileExitMenuItem
      // 
      this.fileExitMenuItem.Index = 4;
      this.fileExitMenuItem.Text = "E&xit";
      this.fileExitMenuItem.Click += 
	       new System.EventHandler(this.fileExitMenuItem_Click);
      // 
      // toolBar1
      // 
      this.toolBar1.Buttons.AddRange(
           new System.Windows.Forms.ToolBarButton[] {
                                           this.lineToolBarButton,
                                           this.rectangleToolBarButton,
                                           this.ellipseToolBarButton});
      this.toolBar1.DropDownArrows = true;
      this.toolBar1.Name = "toolBar1";
      this.toolBar1.ShowToolTips = true;
      this.toolBar1.Size = new System.Drawing.Size(292, 39);
      this.toolBar1.TabIndex = 0;
      this.toolBar1.ButtonClick += 
	       new System.Windows.Forms.ToolBarButtonClickEventHandler(
		        this.toolBar1_ButtonClick);
      // 
      // lineToolBarButton
      // 
      this.lineToolBarButton.Text = "Line";
      // 
      // rectangleToolBarButton
      // 
      this.rectangleToolBarButton.Text = "Rectangle";
      // 
      // ellipseToolBarButton
      // 
      this.ellipseToolBarButton.Text = "Ellipse";
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.BackColor = System.Drawing.Color.White;
      this.ClientSize = new System.Drawing.Size(292, 273);
      this.Controls.AddRange(
	       new System.Windows.Forms.Control[] {
                                               this.toolBar1});
      this.Menu = this.mainMenu1;
      this.Name = "Form1";
      this.Text = "Form1";
      this.MouseDown += 
	       new System.Windows.Forms.MouseEventHandler(this_MouseDown); 
      this.MouseUp += 
	       new System.Windows.Forms.MouseEventHandler(this_MouseUp); 
      this.ResumeLayout(false);

    }
		#endregion

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main() 
    {
      Application.Run(new Form1());
    }

    private void fileExitMenuItem_Click(object sender, 
	     System.EventArgs e)
    {
      this.Close(); 
    }

    private void toolBar1_ButtonClick(object sender, 
	     System.Windows.Forms.ToolBarButtonClickEventArgs e)
    {
      if (e.Button==lineToolBarButton)
        drawnShapeType = shapeType.line;
      else if (e.Button==rectangleToolBarButton)
        drawnShapeType = shapeType.rectangle; 
      else
        drawnShapeType = shapeType.ellipse; 
    }

    private void fileNewMenuItem_Click(object sender, 
	      System.EventArgs e)
    {
      shapes.Clear();
      this.CreateGraphics().Clear(Color.White);
    }

    private void fileOpenMenuItem_Click(object sender, 
	     System.EventArgs e)
    {
      OpenDocument();
    }

    private void fileSaveMenuItem_Click(object sender, 
	     System.EventArgs e)
    {
      Save();
    }

    private void fileSaveAsMenuItem_Click(object sender, 
	     System.EventArgs e)
    {
      SaveAs();
    }

    private void OpenDocument()
    {
      OpenFileDialog openFileDialog = new OpenFileDialog();

      if (openFileDialog.ShowDialog() == DialogResult.OK)
      {
        filename = openFileDialog.FileName;
        Stream myStream = openFileDialog.OpenFile();
        if (myStream != null)
        {
          IFormatter formatter = new BinaryFormatter();
          shapes = (ArrayList) formatter.de-serialize(myStream);
          myStream.Close();
          this.Refresh();
        }
      }
    }

    private bool Save() 
    {
      if (filename==null) 
      {
        return SaveAs();
      }
      else {
        Stream myStream ;
        myStream = File.OpenWrite(filename);
        if (myStream != null)
        {
          IFormatter formatter = 
		       /*(IFormatter) */ new BinaryFormatter();
          // serialize shapes
          formatter.Serialize(myStream, shapes);
          myStream.Close();
          return true;
          }
        }
      return false;
    }

    private bool SaveAs() 
    {
      SaveFileDialog saveFileDialog = new SaveFileDialog();
      if (saveFileDialog.ShowDialog() == DialogResult.OK)
      {
        filename = saveFileDialog.FileName;
        return Save();
      }
      return false;
    }

    private void this_MouseDown(object sender, 
	     System.Windows.Forms.MouseEventArgs e)
    {
      if (e.Button==System.Windows.Forms.MouseButtons.Left) 
      {
        startPoint = new Point(e.X, e.Y);
      }
    }

    override protected void OnPaint(
	     System.Windows.Forms.PaintEventArgs e)
    {
      Graphics g = e.Graphics;
      IEnumerator shapeEnum = shapes.GetEnumerator();
      while (shapeEnum.MoveNext()) 
      {
        IShape shape = (IShape) shapeEnum.Current;
        shape.Draw(g);
      }
    }

    private void this_MouseUp(object sender, 
	     System.Windows.Forms.MouseEventArgs e)
    {
      if (e.Button==System.Windows.Forms.MouseButtons.Left) 
      {
        Point endPoint = new Point(e.X, e.Y);
        if (drawnShapeType==shapeType.line)
        {
          shapes.Add(new Line(startPoint, endPoint));
        }
        else if (drawnShapeType==shapeType.rectangle)
        {
          shapes.Add(new Rect(GetRectangleFromPoints(startPoint, 
		       endPoint)));
        }
        else 
        {
          shapes.Add(new Ellipse(GetRectangleFromPoints(startPoint, 
		       endPoint)));
        }
      }
      this.Refresh();
    }

    private Rectangle GetRectangleFromPoints(Point p1, Point p2) 
    {
      int x1, x2, y1, y2;
      if (p1.X < p2.X) 
      {
        x1 = p1.X;
        x2 = p2.X;
      }
      else 
      {
        x1 = p2.X;
        x2 = p1.X;
      }

      if (p1.Y < p2.Y) 
      {
        y1 = p1.Y;
        y2 = p2.Y;
      }
      else 
      {
        y1 = p2.Y;
        y2 = p1.Y;
      }
      // x2 > x1 and y2 > y1
      return new Rectangle(x1, y1, x2 - x1, y2 - y1);
    }

  }

}


Class Variables

Some of the class variables are given below.


    private ArrayList shapes = new ArrayList();
    private enum shapeType
    {
      line, rectangle, ellipse,
    }

    private shapeType drawnShapeType = shapeType.line;
    private Point startPoint;
    private String filename;

The shapes ArrayList contains all shapes drawn on the form's surface. The shapeType enumerator contains the three types of shapes. The drawnShapeType denotes the type of shape that the user chooses to draw. The startPoint variable is a System.Drawing.Point indicating the point at which the user clicks the mouse button to start drawing. The filename is the name of the file to which to serialize the shapes.

The OnPaint Method

The OnPaint method is overriden to draw all of the shapes on the form's surface. It first obtains the Graphics object of the form.


      Graphics g = e.Graphics;

It then creates an Ienumerator from the GetEnumerator method of the shapes ArrayList.


      IEnumerator shapeEnum = shapes.GetEnumerator();

Next, it iterates through all members of the shapes ArrayList in a while loop, casts each member into an IShape object, and calls its Draw method.


      while (shapeEnum.MoveNext()) 
      {
        IShape shape = (IShape) shapeEnum.Current;
        shape.Draw(g);
      }

The toolbar1_ButtonClick Event Handler

This event handler is invoked every time the user clicks a toolbar button to select a shape to draw. The event handler checks which button is clicked and updates the value of drawnShapeType.


      if (e.Button==lineToolBarButton)
        drawnShapeType = shapeType.line;
      else if (e.Button==rectangleToolBarButton)
        drawnShapeType = shapeType.rectangle; 
      else
        drawnShapeType = shapeType.ellipse; 

The this_MouseDown Event Handler

The form's MouseDown event is wired to the this_MouseDown event handler. What it does is simple: it checks if the user clicks the mouse's left button, and, if so, assigns the startPoint class variable with the click point.


      if (e.Button==System.Windows.Forms.MouseButtons.Left) 
      {
        startPoint = new Point(e.X, e.Y);
      }

The this_MouseUp Event Handler

This event handler is invoked when the user releases the mouse button. It first checks if the released button is the left button of the mouse, and if yes, creates a shape. The shape created depends on the value of drawnShapeType. If its value is ShapeType.line, then a Line object is constructed. If the value is ShapeType.rectangle, a Rect object is created. If the value is ShapeType.ellipse, an Ellipse object is created. The newly-created shape is then added to the shapes ArrayList.

The Save Method

The Save method serializes the shapes ArrayList into a file. The part that does that is as follows:


        Stream myStream ;
        myStream = File.OpenWrite(filename);
        if (myStream != null)
        {
          IFormatter formatter = new BinaryFormatter();
          // serialize shapes
          formatter.Serialize(myStream, shapes);
          myStream.Close();
          return true;
          }
        }

It first creates a Stream object, and then a BinaryFormatter object. Serializing the shapes ArrayList is done by calling the Serialize method of the formatter.

The OpenDocument Method

The OpenDocument method de-serializes the persisted ArrayList object into shapes. The code that does the de-serialization is as follows:


        Stream myStream = openFileDialog.OpenFile();
        if (myStream != null)
        {
          IFormatter formatter = new BinaryFormatter();
          shapes = (ArrayList) formatter.de-serialize(myStream);
          myStream.Close();
          this.Refresh();
        }

It first constructs a Stream object and then a BinaryFormatter object. To de-serialize an object, it calls the formatter's de-serialize method.

Summary

In this article, you have learned the object serialization technique in the .NET Framework. You have also seen how this technique finds application in a vector-based drawing application.


Return to .NET DevCenter