Click here to Skip to main content
11,705,212 members (38,509 online)
Click here to Skip to main content

Ride the Override

, 28 Aug 2003 79.5K 455 19
Rate this:
Please Sign up or sign in to vote.
An in depth look at polymorphism using C#
<!-- Article image -->

Sample Image - override.gif

<!-- Add the rest of your HTML here -->

Introduction

My previous article proposed an enhancement to the existing C# property syntax. This article will focus on a few pitfalls associated with the virtual, new and override keywords. This article assumes the reader has a basic working knowledge of the C# programming language.

The downloadable source code provided demonstrates the problem discussed in this article. I am using Visal Studio 2003 with .NET Framework SDK 1.1 to create this example.

Polymorphism in Java & C#

The Java programming language introduced by Sun Microsystems was a popular choice among programmers until Microsoft announced the advent of C# programming language as a part of the .NET platform. Java programmers find it relatively easy to learn C# because of its simiarity to the Java programming language syntax. There are a few things however that the programmer should be on the look out for when using the C# programming language.

In Java, all methods of a class are virtual by default unless the developer decides to use the final keyword thus preventing subclasses from overriding that method. In contrast C# adopts the strategy used by C++ where the developer has to use the virtual keyword for subclasses to override the method. Thus all methods in C# are non virtual by default.

Consider the C# code snippet below. Assume that a developer is using a library developed by a ficticious third party software vendor which provides a framework consisting of a set of classes and interfaces to be used in a drawing application. The application being developed creates shapes which can be drawn onto a whiteboard. The developer extends the Shape class to create concrete shapes such as a Rectangle or Square.

namespace Framework
{
    // Interface
    public interface IDrawable
    {
        void Draw();  // interface methods are by default public virtual
    }

    // Abstract Shape
    public abstract class Shape : IDrawable
    {
        virtual public void Draw() { ... } // Notice the virtual keyword 
                                           // and not override

        abstract protected int ComputeArea(); // abstract methods are by
                                              // default virtual

        public int Area
        {
            get
            {
                return ComputeArea();
            }
        }
    }
}

namespace WhiteBoard
{
    // Concrete Shape
    public class Rectangle : Shape
    {
        override protected int ComputeArea() {...} // Notice the override
                                                   // keyword
    }

    // Concrete Shape
    public class Square : Rectangle
    {
        // Details omitted for clarity
    }

    public sealed class Board
    {
        public void Draw(Shape shape)
        {
            if(shape.Area <= 0)
            {
                throw new ArgumentException("Cannot draw the " + 
                              shape.Type + " with zero area.");
            }

            // Details omitted for clarity

            shape.Draw();
        }
    }

The code snippet above highlights a few salient points on the usage of virtual and override keywords. First, all methods on IDrawable are virtual public by default. An interface by design does not allow the programmer to provide implementation for any of its methods. Using these keywords as shown below result in a compiler error. These methods have to be implemented by concrete subclasses which implement that interface.

// Interface
public interface IDrawable
{
    // Compiler Error. The modifier 'protected' is not valid for this item.
    protected void Draw();
}

// OR

public interface IDrawable
{
    // Compiler Error. The modifier 'virtual' is not valid for this item.
    virtual void Draw();
}

On the other hand, all abstract methods on the Shape class are virtual by design as a result of which the addition of virtual keyword for the abstract method results in a compiler error. An abstract class typically has one or more abstract methods which concrete subclasses have to override and provide the necessary behavior.

// Abstract Shape
public abstract class Shape
{
    // Compiler Error. The abstract method 'Framework.Shape.ComputeArea()'
    // cannot be marked virtual.
    abstract virtual protected int ComputeArea();
}

Analyzing & Solving the Problem

The Shape class implements the IDrawable interface and provides an implementation of the Draw() method. This method is marked virtual for the subclasses of Shape to override if need be. But aren't we overriding the one on IDrawable in first place? The answer is 'No'. Since IDrawable does not provide any implementation of Draw(), there is no override for that method in the Shape class.

The Shape class also declares an abstract ComputeArea() method which concrete subclasses of Shape have to override thus providing the necessary behavior. This is because the algorithm for computation of an area is specific to a particular shape. The Rectangle provides an implementation for the ComputeArea() method by using the override keyword. But what is it that we overriding here? The Shape class did not provide any implementation for the ComputeArea() method.

The logical explanation to this question is that implementor of the Shape class may choose to make ComputeArea() non abstract by providing a default behavior in which case the override on Rectangle for ComputeArea() does make sense.

Beware as you perform the step mentioned above for providing default behavior to the ComputeArea() method on the Shape class. Consider the case where the implementor of the Shape class unknowingly removes the abstract keyword and provides a default implementation as shown in the code snippet below. The code using the Framework (i.e. WhiteBoard) now fails compilation.

namespace Framework
{
    // Abstract Shape
    public abstract class Shape : IDrawable
    {
        protected int ComputeArea() { return 0; } // This method is not
                                                  // virtual anymore!
    }
}

namespace WhiteBoard
{
    // Concrete Shape
    public class Rectangle : Shape
    {
        // Compiler Error. 'WhiteBoard.Rectangle.ComputeArea()' : cannot
        // override inherited member 'Framework.Shape.ComputeArea()' 
        // because it is not marked virtual, abstract, or override.
        override protected int ComputeArea() {...}
    }
}

One way to get around this problem is to make use of the new keyword on the ComputeArea() in class Rectangle as shown below.

namespace WhiteBoard
{
    // Concrete Shape
    public class Rectangle : Shape
    {
        protected new int ComputeArea() {...} // Notice the new keyword
    }

    // Somewhere in the code
    Rectangle rect = new Rectangle(4, 6);
    board.Draw(rect); // Problem. Early binding to Shape.ComputeArea() and
                      // thows exception.
}

But this does not give us the desired behavior as the new keyword uses a phenomenon called early binding. When the code is compiled, the C# compiler looks up the call to shape.ComputeArea() and determines the address in memory that it would need to jump to when the call is made. In which case, the memory location will be that of Shape.ComputeArea() method.

What we want instead is for late binding to occur. Late binding means that the compiler does not select the method to execute until runtime. This will only happen if ComputeArea() was declared virtual in the Shape class as shown in the code snippet below.

namespace Framework
{
    // Abstract Shape
    public abstract class Shape : IDrawable
    {
        virtual protected int ComputeArea() { return 0; } // Notice the
                                                          // virtual keyword

        public int Area
        {
            get
            {
                return ComputeArea();
            }
        }
    }
}

namespace WhiteBoard
{
    // Concrete Shape
    public class Rectangle : Shape
    {
        override protected int ComputeArea() {...} // Notice the override
                                                   // keyword
    }

    // Somewhere in the code
    Rectangle rect = new Rectangle(4, 6);
    board.Draw(rect); // Correct. Late binding to Rectangle.ComputeArea()
}

Conclusion

Don't forget to replace the abstract with virtual keyword when providing default behavior to previously defined abstract methods of a class.

The problem mentioned above wouldn't have surfaced if all methods on a C# class were virtual by default. Just dropping the abstract keyword on the Shape class would suffice as a result of which the method now becomes virtual by its default nature. Any methods which were not intended to be overriden could have then been marked with the sealed keyword.

References/Links

Essential .NET Volume 1 (The Common Language Runtime) - Don Box with Chris Sells.

Inside C# - Tom Archer.

Microsoft Visual C# .NET Step By Step - John Sharp, Jon Jagger.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Salil Pitkar
Web Developer
United States United States
Over 15 years of experience in designing and architecting high availability/scalability financial application software using OO technologies such as C++/Java/C#.

You may also be interested in...

Comments and Discussions

 
GeneralThe virtual keyword is a defect in C# Pin
Peter Gummer14-Sep-03 18:18
memberPeter Gummer14-Sep-03 18:18 
GeneralRe: The virtual keyword is a defect in C# Pin
Salil Pitkar14-Sep-03 19:42
memberSalil Pitkar14-Sep-03 19:42 
GeneralRe: The virtual keyword is a defect in C# Pin
Paulo Zemek31-Aug-09 2:40
memberPaulo Zemek31-Aug-09 2:40 
GeneralMany Misunderstandings Pin
Blake Coverett29-Aug-03 2:39
memberBlake Coverett29-Aug-03 2:39 
GeneralRe: Many Misunderstandings Pin
Salil Pitkar29-Aug-03 5:08
memberSalil Pitkar29-Aug-03 5:08 
GeneralRe: Many Misunderstandings Pin
Blake Coverett29-Aug-03 14:57
memberBlake Coverett29-Aug-03 14:57 
GeneralRe: Many Misunderstandings Pin
Salil Pitkar29-Aug-03 20:47
memberSalil Pitkar29-Aug-03 20:47 
GeneralRe: Many Misunderstandings Pin
Blake Coverett29-Aug-03 21:52
memberBlake Coverett29-Aug-03 21:52 
GeneralRe: Many Misunderstandings Pin
Salil Pitkar30-Aug-03 8:05
memberSalil Pitkar30-Aug-03 8:05 
GeneralRe: Many Misunderstandings Pin
Blake Coverett30-Aug-03 17:59
memberBlake Coverett30-Aug-03 17:59 
GeneralRe: Many Misunderstandings Pin
Salil Pitkar30-Aug-03 18:42
memberSalil Pitkar30-Aug-03 18:42 
GeneralRe: Many Misunderstandings Pin
Joaquin Grech (Creative1)3-May-07 3:25
memberJoaquin Grech (Creative1)3-May-07 3:25 
Generalagreed Pin
dog_spawn29-Aug-03 5:41
memberdog_spawn29-Aug-03 5:41 
GeneralRe: agreed Pin
Salil Pitkar29-Aug-03 9:43
memberSalil Pitkar29-Aug-03 9:43 
GeneralRe: agreed Pin
dog_spawn29-Aug-03 11:19
memberdog_spawn29-Aug-03 11:19 
GeneralRe: agreed Pin
Salil Pitkar29-Aug-03 11:54
memberSalil Pitkar29-Aug-03 11:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150819.1 | Last Updated 29 Aug 2003
Article Copyright 2003 by Salil Pitkar
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid