polymorphism in c#

By | February 12, 2019

Polymorphism in C#-: Polymorphism is one of the major benefits of inheritance. With polymorphism, the correct function call is decided at run time based on the derived type of a base reference. This is called late binding and is accomplished by casting instances of related types back to a common base class reference. The common base class makes the derived classes related.

A graphics program provides a good example. In the graphics program, instead of having separate routines for rectangle, ellipse, and triangle shapes, we write a generic algorithm that uses the base type. This is more extensible and maintainable than handling each type of geometric shape differently. At run time, process a specific instance, such as a rectangle, ellipse, or triangle shape, in the generic routine. Then the appropriate behaviour will be chosen from the derived type.

The following program draws rectangles, ellipses, and triangles. This program uses inheritance but not polymorphism:

using System;
namespace Donis.CSharpBook {
    public class Starter {
        public static void Main() {
            Rectangle shape1 = new Rectangle();
            Rectangle shape2 = new Rectangle();
            Ellipse shape3 = new Ellipse();
            shape1.Draw();
            shape2.Draw();
            shape3.Draw();
        }
    }
    public abstract class Geoshape {
        public Geoshape() {
            ++count;
            ID = count;
        }
        private static int count = 0;
        protected int ID = 0;
        public virtual void Draw() {
        }
    }
    public class Rectangle : Geoshape {
        public override void Draw() {
            Console.WriteLine("Drawing Shape {0} : rectangle",
                ID.ToString());
        }
    }
    public class Triangle : Geoshape {
        public override void Draw() {
            Console.WriteLine("Drawing Shape {0} : triangle",
                ID.ToString());
        }
    }
    public class Ellipse : Geoshape {
        public override void Draw() {
            Console.WriteLine("Drawing Shape {0} : ellipse",
                ID.ToString());
        }
    }
}

The two primary advantages to polymorphism are late binding and extensibility:

  • Late binding in the context of polymorphism is binding a specific object to a function call at run time. Early binding binds a function to a specific object at compile time, which is not always possible. For example, in an interactive graphics program, users decide the objects to draw at run time. The previous program does not use polymorphism and decides the shapes to draw at compile time. The same objects are drawn every time.
  • Extensible code adapts easily to future changes. In the preceding code, the Draw method is called separately for each kind of geometric type. A rectangle is drawn differently from an ellipse. An ellipse is drawn differently from a triangle. As the program evolves in the future, more geometric shapes are likely to be added. It eventually could support dozens of shapes.

 

Polymorphism starts with related classes, such as specific geometric shapes. In the base class, define abstract function members that each derived type must override and implement. To use polymorphism, create a base class reference to instances of a derived type. When a virtual function is called on the base class reference, the correct function is chosen from the derived type. The method in the derived type is either inherited or overridden.

BaseReference.VirtualMethod calls different implementations at run time depending on the type of derived object to which the base class reference points. This is the key behaviour of polymorphism.

The sample code that follows illustrates these concepts. In the following code, Geoshape is the base class reference, and Draw is the abstract (and virtual) method. The Draw method is overridden in the Rectangle, Ellipse, and Triangle child classes. In the example, two derived instances are created in an array of base class objects. Geoshape references shape[0] and shape[1], which hold Ellipse and Rectangle objects, respectively. The Draw method then is invoked on the Geoshape base class references. At that time, the correct Draw method of the derived type is called. Therefore, the base method call has different behaviour depending on the kind of object assigned to the reference:

Geoshape [] shapes = { new Ellipse(),
new Rectangle() };
shapes[0].Draw(); // Geoshape.Draw()->Ellipse.Draw()
shapes[1].Draw(); // Geoshape.Draw()->Rectangle.Draw()

In the next code, the DrawShape method draws a geometric shape. Can we predict which type.Draw is called? The method has one parameter, which is the Geoshape base class. On the first call to DrawShape method, the base reference is assigned an Ellipse instance, which means Ellipse.Draw is called. On the second call, a Rectangle instance is passed, and then Rectangle.Draw is called. Therefore, one statement, shape.Draw, calls different implementations. The shape.Draw statement is both polymorphic and extensible:

public static void Main() {
DrawShape(new Ellipse());
DrawShape(new Rectangle());
}

public static void DrawShape(Geoshape shape) {
shape.Draw(); // which Draw is called?
}

Polymorphism has three ingredients: 

  • Related classes
  • Common method
  • Different behaviour

The following code is a better example of the benefits of polymorphism. The program has the three elements of polymorphism. There are related types: Triangle, Ellipse, and Rectangle. The common method is Draw. Each Draw method is implemented differently in the derived class. At run time, command-line arguments indicate which shapes to draw. The following command draws a rectangle, ellipse, triangle, and another rectangle, which highlights the benefits of polymorphism and late binding:

shapes r e t r

At compile time, we know that clients will draw shapes. But we don’t know which shapes or how many. Therefore, a collection of Geoshape base class references, which are a generalisation of all shape types, is defined at compile time. Polymorphism always begins with base references to derived instances. In the first loop, derived instances are created and added to the collection of base class references.

In the second loop, the array of Geoshape base class references is iterated and Geoshape.Draw is called. At run time, Rectangle.Draw, Ellipse.Draw, or Triangle.Draw actually is called for each element of the Geoshape collection. This is the polymorphic behaviour:

using System;
using System.Collections.Generic;
namespace Donis.CSharpBook {

public class Starter {
public static void Main(string [] shapeArray) {
List<Geoshape> shapes = new List<Geoshape>();
Geoshape obj=null;
foreach (string shape in shapeArray) {
if (shape.ToUpper() == "R") {
obj = new Rectangle();
}
else if (shape.ToUpper() == "E") {
obj = new Ellipse();
}
else if (shape.ToUpper() == "T") {
obj = new Triangle();
}
else {
continue;
}
shapes.Add(obj);
}

foreach (Geoshape shape in shapes) {
shape.Draw(); // polymorphic behavior
}
}
}

public abstract class Geoshape {
public Geoshape() {
++count;
ID = count;
}

private static int count = 0;
protected int ID = 0;

public abstract void Draw(); }

public class Rectangle : Geoshape {
public override void Draw() {
Console.WriteLine("Drawing Shape {0} : rectangle",
ID.ToString());
}
}

public class Triangle : Geoshape {
public override void Draw() {
Console.WriteLine("Drawing Shape {0} : triangle",
ID.ToString());
}
}

public class Ellipse : Geoshape {
public override void Draw() {
Console.WriteLine("Drawing Shape {0} : ellipse",
ID.ToString());
}
}
}

Geoshape is an abstract class, not a concrete one. A generic geometric shape is intangible. In addition, the Geoshape.Draw method is abstract, which means the containing class must be abstract as well. There is an added benefit of an abstract Draw. Derived types are forced to implement their own Draw method.

Interface Polymorphism-: Interface polymorphism is the same as regular polymorphism except that interfaces are used instead of abstract classes and methods. The result is also the same. Because the Geoshape class has some implementation, and interfaces cannot have implementation, the Geoshape class cannot be converted to an interface. Instead, the Draw abstract method can be lifted from the class and placed in a separate IDraw interface. That interface then can be used for polymorphism, as shown in the following code:

using System;
using System.Collections.Generic;

namespace Donis.CSharpBook {

public class Starter {
public static void Main(string [] shapeArray) {
List<Geoshape> shapes = new List<Geoshape>();

// partial listing
foreach (IDraw shape in shapes) {
shape.Draw();
}
}
}
public interface IDraw {
void Draw();
}

public abstract class Geoshape : IDraw {
public Geoshape() {
++count;
ID = count;
}

private static int count = 0;
protected int ID = 0;

public abstract void Draw();
}
// partial listing

The new Modifier and Polymorphism-: The new modifier affects polymorphism and can cause unexpected results. Unlike an overridden member, the new method is not considered related to the virtual member of the same function in the base class. As mentioned, the virtual modifier propagates to all descendants. A virtual method in a base class remains virtual when overridden in the derived class, even if the virtual modifier is not present.

Polymorphism calls the function either inherited or overridden in the derived class. The exception is a method with the new modifier, which is not considered related to a virtual method of the base class. In that case, an overridden or inherited method closest to the derived type is called.

The following code is typical polymorphism. ZClass is abstract with a single abstract method—Method A. Y Class inherits ZClass and overrides Method A. Finally, X Class inherits Y Class and also overrides Method A again. In Main, an instance of X Class is cast to ZClass, which is a reference to a base type. Method A is called on the base type and predictably, X Class.Method A is invoked:

using System;

public abstract class ZClass {
public abstract void MethodA();
}

public class YClass : ZClass {
public override void MethodA()
{
Console.WriteLine("YClass.MethodA");
}
}

public class XClass : YClass {
public override void MethodA() {
Console.WriteLine("XClass.MethodA");
}
}
class Startup {
public static void Main() {
ZClass obj = new XClass();
obj.MethodA(); // Writes XClass.MethodA
}
}

The following code is identical to the preceding code, except for the MethodA header in XClass. Instead of overriding MethodA, XClass.MethodA uses the new modifier, shown in bold type, to replace the inherited function. In Main, the XClass instance is cast to ZClass, which is the base type as before. The call to ZClass.MethodA now invokes YClass.MethodA, not XClass.MethodA. The new modifier indicated that XClass.MethodA was unrelated to ZClass.MethodA. If not planned or expected, this could be a problem.

using System;

public abstract class ZClass {
public abstract void MethodA();
}

public class YClass : ZClass {
public override void MethodA()
{
Console.WriteLine("YClass.MethodA");
}
}

public class XClass : YClass {
public new void MethodA()
{
Console.WriteLine("XClass.MethodA");
}
}
class Startup {
public static void Main() {
ZClass obj = new XClass();
obj.MethodA(); // Writes YClass.MethodA
}
}

Leave a Reply

Your email address will not be published. Required fields are marked *