Click here to Skip to main content
15,878,945 members
Articles / Programming Languages / C#
Article

Ride the Override

Rate me:
Please Sign up or sign in to vote.
2.47/5 (12 votes)
28 Aug 20034 min read 92.2K   19   16
An in depth look at polymorphism using C#

Sample Image - override.gif

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.

C#
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.

C#
// 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.

C#
// 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.

C#
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.

C#
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.

C#
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


Written By
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#.

Comments and Discussions

 
GeneralThe virtual keyword is a defect in C# Pin
Peter Gummer14-Sep-03 18:18
Peter Gummer14-Sep-03 18:18 
GeneralRe: The virtual keyword is a defect in C# Pin
Salil Pitkar14-Sep-03 19:42
Salil Pitkar14-Sep-03 19:42 
GeneralRe: The virtual keyword is a defect in C# Pin
Paulo Zemek31-Aug-09 2:40
mvaPaulo Zemek31-Aug-09 2:40 
GeneralMany Misunderstandings Pin
Blake Coverett29-Aug-03 2:39
Blake Coverett29-Aug-03 2:39 
GeneralRe: Many Misunderstandings Pin
Salil Pitkar29-Aug-03 5:08
Salil Pitkar29-Aug-03 5:08 
GeneralRe: Many Misunderstandings Pin
Blake Coverett29-Aug-03 14:57
Blake Coverett29-Aug-03 14:57 
GeneralRe: Many Misunderstandings Pin
Salil Pitkar29-Aug-03 20:47
Salil Pitkar29-Aug-03 20:47 
Hi Blake,

Very well said and the points you cover are bang on target but does everyone using C# know the logic behind the usage of these keywords? You're right on with your comments and I apologize if I conveyed the wrong message with the article.

Can new be used (Syntactically) when the virtual mechanism is broken? The answer is 'Yes'. I'm not saying that it is the correct way to do so but it is possible hat someone might do it because the compiler will not complain about it. I'm just trying to explain what would happen in such a scenario.

I know that the new keyword has been introduced with the notion that experienced programmers can hide the compiler warnings and create alternate dispatch mechanisms when needed and the points you make are correct.

Thanks.
GeneralRe: Many Misunderstandings Pin
Blake Coverett29-Aug-03 21:52
Blake Coverett29-Aug-03 21:52 
GeneralRe: Many Misunderstandings Pin
Salil Pitkar30-Aug-03 8:05
Salil Pitkar30-Aug-03 8:05 
GeneralRe: Many Misunderstandings Pin
Blake Coverett30-Aug-03 17:59
Blake Coverett30-Aug-03 17:59 
GeneralRe: Many Misunderstandings Pin
Salil Pitkar30-Aug-03 18:42
Salil Pitkar30-Aug-03 18:42 
GeneralRe: Many Misunderstandings Pin
Joaquin Grech (Creative1)3-May-07 3:25
Joaquin Grech (Creative1)3-May-07 3:25 
Generalagreed Pin
dog_spawn29-Aug-03 5:41
dog_spawn29-Aug-03 5:41 
GeneralRe: agreed Pin
Salil Pitkar29-Aug-03 9:43
Salil Pitkar29-Aug-03 9:43 
GeneralRe: agreed Pin
dog_spawn29-Aug-03 11:19
dog_spawn29-Aug-03 11:19 
GeneralRe: agreed Pin
Salil Pitkar29-Aug-03 11:54
Salil Pitkar29-Aug-03 11:54 

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

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