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
{
public interface IDrawable
{
void Draw();
}
public abstract class Shape : IDrawable
{
virtual public void Draw() { ... }
abstract protected int ComputeArea();
public int Area
{
get
{
return ComputeArea();
}
}
}
}
namespace WhiteBoard
{
public class Rectangle : Shape
{
override protected int ComputeArea() {...}
}
public class Square : Rectangle
{
}
public sealed class Board
{
public void Draw(Shape shape)
{
if(shape.Area <= 0)
{
throw new ArgumentException("Cannot draw the " +
shape.Type + " with zero area.");
}
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.
public interface IDrawable
{
protected void Draw();
}
public interface IDrawable
{
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.
public abstract class Shape
{
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
{
public abstract class Shape : IDrawable
{
protected int ComputeArea() { return 0; }
}
}
namespace WhiteBoard
{
public class Rectangle : Shape
{
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
{
public class Rectangle : Shape
{
protected new int ComputeArea() {...}
}
Rectangle rect = new Rectangle(4, 6);
board.Draw(rect);
}
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
{
public abstract class Shape : IDrawable
{
virtual protected int ComputeArea() { return 0; }
public int Area
{
get
{
return ComputeArea();
}
}
}
}
namespace WhiteBoard
{
public class Rectangle : Shape
{
override protected int ComputeArea() {...}
}
Rectangle rect = new Rectangle(4, 6);
board.Draw(rect);
}
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.
Over 15 years of experience in designing and architecting high availability/scalability financial application software using OO technologies such as C++/Java/C#.