Polymorphism, Up-casting and Down-casting


The original aim of this article was to explain Up-casting and Down-casting. Having started writing the article, I was struggling to find an ideal way to describe these two concepts. Having thought further about it, one way I found was to introduce the concept of polymorphism to help explain up and down-casting. This has expanded the article further and when you have finished reading it, you should hopefully understand the three concepts better.

Polymorphism

Polymorphism is a powerful aspect of object oriented programming. According to many searches on the Internet to find a definitive meaning, I have found two that seem to explain it quite nicely, these are "Having many forms" and "Having multiple forms".

 

Consider the following. Ask yourself, what is a circle? Most would say a circle is a shape. A square, rectangle and a triangle are also shapes. So, what we are saying is a shape can take on many forms or has multiple forms. So how do we implement this concept in C#?

 

First we need to design our base class, this will be called Shape. Our shape class will implement a constructor that will accept an X and Y co-ordinate and a method that will draw our shape. Below is the code for our shape class.

 

public class Shape

{

    protected int m_xpos;

    protected int m_ypos;

 

    public Shape()

    {

    }

 

    public Shape(int x, int y)

    {

        m_xpos = x;

        m_ypos = y;

    }

 

    public virtual void Draw()

    {

        Console.WriteLine("Drawing a SHAPE at {0},{1}", m_xpos, m_ypos);

    }

}

We now need to make our Draw method behave polymorphically. To do this, we declare our method with the keyword virtual in our base class. When we derive a class from shape we are able to implement a specific version of Draw by overriding the base class implementation of Draw. To do this, we use the keyword override when declaring our Draw method in our derived classes. 

What we will now do is implement two derived classes from our shape base class. We will implement Circle and Square. This will show how we override our base class Draw method.

Circle and Square derived classes

 

One thing to note about the base class is the fact that I have used protected variables for the X and Y co-ordinates. Ideally you would use public properties and declare m_xpos and m_ypos as private.

 

Here are our two derived classes.

 

public class Square : Shape

{

    public Square()

    {

    }

 

    public Square(int x, int y) : base(x, y)

    {

    }

 

    public override void Draw()

    {

        Console.WriteLine("Drawing a SQUARE at {0},{1}", m_xpos, m_ypos);

    }

}

And finally Circle...
 

public class Circle : Shape

{

    public Circle()

    {

    }

 

    public Circle(int x, int y) : base(x, y)

    {

    }

 

    public override void Draw()

    {

        Console.WriteLine("Drawing a CIRCLE at {0},{1}", m_xpos, m_ypos);

    }

} 

 

Notice, the highlighted code blocks. We have overridden the base class implementation of Draw in each of our derived classes. Basically override tells the compiler that we are intentionally overriding the behaviour of Draw.

 

We will now test the classes we have written, by putting together a simple piece of code that will create an instance of Shape, Circle and Square and call the Draw methods for each type.

 

class Program

{

    static void Main(string[] args)

    {

        Shape sh = new Shape(100, 100);

        sh.Draw();

 

        Square sq = new Square(200, 200);

        sq.Draw();

 

        Circle ci = new Circle(300, 300);

        ci.Draw();

    }

}

The output generated by the test code is:

 

Drawing a SHAPE at 100,100

Drawing a SQUARE at 200,200

Drawing a CIRCLE at 300,300

 

So what's happened that's polymorphic? Well, at this point absolutely nothing! Consider the code below.

 

class Program

{

    static void Main(string[] args)

    {

        Shape[] shapes = new Shape[3];

        shapes[0] = new Shape(100, 100);

        shapes[1] = new Square(200, 200);

        shapes[2] = new Circle(300, 300);

 

        foreach (Shape shape in shapes)

        shape.Draw();

    }

}

What we have done is to create an array of the type Shape. Because Square and Circle are derived from Shape, we are able to put them in our array. What we are then doing is looping through all the elements of our array and calling Draw for each of our types. Because we have overridden the Draw method in each of our derived classes the output of our code is:

 

Drawing a SHAPE at 100,100

Drawing a SQUARE at 200,200

Drawing a CIRCLE at 300,300

 

If we did not override Draw in one of our derived classes, the base class implementation of Draw would be called. For example, if we declared Draw in our Circle class as follows:

 

public void Draw()

{

    Console.WriteLine("Drawing a CIRCLE at {0},{1}", m_xpos, m_ypos);

}

 

Our output would be:

 

Drawing a SHAPE at 100,100

Drawing a SQUARE at 200,200

Drawing a SHAPE at 300,300

 

By declaring the method as shown above, we will receive a compiler warning as follows:

 

Polymorphism.Circle.Draw() : Hides inherited member.

  

Polymorphism.Shape.Draw() :To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

 

If we do not want to override the base class method, we need to use the new keyword when declaring our method, for example:

 

public new void Draw()

{

    Console.WriteLine("Drawing a CIRCLE at {0},{1}", m_xpos, m_ypos);

}

This will remove the compiler warning. Basically we are telling the compiler that we are not overriding the base class implementation.

 

So what have we achieved with polymorphism? What we have been able to do is create an array of shapes and add a specific shape to each element of the array. When drawing each shape, we have not concerned ourselves with the fact that the shape could be a circle or a square. We have simply said, "here is an array of shapes, please draw them!"  It is the responsibility of the compiler to discover the real type and to ensure that the correct implementation is called.  

Up-casting 

Now we come on to one of the original goals of the article. With the simple example above of polymorphism, you should be able to quickly understand what up-casting is, in fact we have already used up-casting in our example.

 

The diagram below is a UML diagram for our polymorphism example.



Figure 1: UML Diagram For Shape.
 


Consider the following code:

 

Shape s = new Circle(100, 100);

We have cast Circle to the type Shape. This is perfectly legal code (as we saw in the Polymorphism example). This is possible, because Circle has been derived from Shape and you expect all methods and properties of Shape to exist in Circle. Executing the Draw method by doing s.Draw() gives the following output:

 

Drawing a CIRCLE at 100,100

 

If we had declared the Draw method in Circle as follows, public new void Draw() the output would have been:

 

Drawing a SHAPE at 100,100

 

As we have already mentioned, marking the method with new, tells the compiler that we are not overriding the base class implementation of the method.

 

So why is this called up-casting? Consider the diagram above. From Circle, we are moving up the object hierarchy to the type Shape, so we are casting our object "upwards" to its parent type.

 

Up-casting is implicit and is safe. What do we mean by safe? Well, we can happily cast Circle to Shape and expect all the properties and methods of Shape to be available. 

Down-casting 

The flip side of the coin to up-casting is ...yes you guessed it, down-casting. Down-casting takes a little more understanding and you have to be very careful when down-casting types.

 

To help us better understand down-casting, we are going to add a new method to our Circle class. This will be a simple method called FillCircle.

 

public void FillCircle()

{

    Console.WriteLine("Filling CIRCLE at {0},{1}", m_xpos, m_ypos);

}

Using the example from up-casting, we know that we are able to write the following:

 

Shape s = new Circle(100, 100);

 

We are then free to call the Draw method. Having added the FillCircle method to our Circle class, are we able to call this method by doing the following?

 

s.FillCircle ();

 

In short, the answer is no. Because we have cast Circle to the type Shape, we are only able to use methods found in Shape, that is, Circle has inherited all the properties and methods of Shape. If we want to call FillCircle, we need to down-cast our type to Circle. Why is it called down-casting? Quite simply, we are moving down the object hierarchy, from Shape down to Circle.

 

So how do we code a down-cast from Shape to Circle? The code for doing this is quite simple:

 

Circle c;

c = (Circle)s;

 

Simply, we are declaring c as the type Circle and explicitly casting s to this type. We are now able to call the FillCircle method by doing the following:

 

c.FillCircle();

 

This gives us the following output:

 

Drawing a CIRCLE at 100,100

Filling CIRCLE at 100,100

 

We could also write ((Circle)s).FillCircle() reducing the lines of code needed to down-cast our type and call the required method.

 

Down-casting is potentially unsafe, because you could attempt to use a method that the derived class does not actually implement. With this in mind, down-casting is always explicit, that is, we are always specifying the type we are down-casting to. 

The as and is keywords

 

To demonstrate as and is, lets implement a FillSquare method in our Square class. Our code could be:

 

Public void FillSquare()

{

    Console.WriteLine("Filling SQUARE at {0},{1}", m_xpos, m_ypos);

}

 

Taking some code from our polymorphism example, as follows:

 

class Program

{

        static void Main(string[] args)

        {

            Shape[] shapes = new Shape[3];

            shapes[0] = new Shape(100, 100);

            shapes[1] = new Square(200, 200);

            shapes[2] = new Circle(300, 300);

 

            foreach (Shape shape in shapes)

                shape.Draw();

           

        }

}

We have Circle and Square in our array of type Shape. How do we know what type we have to down-cast to in order to call the correct routine to fill the shape?

 

Within the foreach statement, we have to test the type of shape being drawn before we down-cast, we will use the is keyword to test type. We will modify the code above, to ensure the correct method is called to fill our shapes as they are drawn.

 

foreach (Shape shape in shapes)

{

    shape.Draw();

 

    if (shape is Circle)

        ((Circle)shape).FillCircle();

 

        if (shape is Square)

            ((Square)shape).FillSquare();

}
 

Within the loop, we are performing a test to see if the type of shape is a Circle or a Square, depending on the result, we are down-casting our type in order to call the correct fill routine. Our output now is:

 

Drawing a SHAPE at 100,100
Drawing a SQUARE at 200,200
Filling SQUARE at 200,200

Drawing a CIRCLE at 300,300

Filling CIRCLE at 300,300

 

I have highlighted the changes our code has made to the output. The alternative to the is keyword is as. We can write:

 

Circle c = shape as Circle;

 

The difference is that as tries to cast shape to a type (in our example Circle). If the cast fails, c is set to null. For example:

 

Circle c = shape as Circle;

If(c!=null)

  c.FillCircle(); 

Conclusion

 

Quite a lot of ground has been covered in this article. Whilst the polymorphism example is very simple, it gives you an understanding of what polymorphism is and how you could implement it in your design.

 

The down-casting example is a little contrived. Some of you may be thinking why not just implement a base class method for filling the shapes and override it in the derived classes. The idea of implementing the fill routines as I did was to aid in the explanation of down-casting and has only been done for this reason.

 

I hope you have enjoyed reading this article and have learned something from it.

Up Next
    Ebook Download
    View all
    Learn
    View all