Polymorphism

.Polymorphism
Polymorphism in C# | Object-Oriented Polymorphism in C# | Polymorphism, Dynamic Binding, and Casting | Is and As Operators | Polymorphism, Inheritance, and Interfaces | Polymorphism Example Program

The constructs of polymorphism within computer science tend to vary with the capabilities and implementations of the programming language. The term "polymorphism" has been used to describe a wide variety of programming-language features and has been divided and classified in different ways. Whether using a generalized definition of polymorphism as "the capability of a language to use the same name to denote different entities", or a more specific object-oriented definition as "run-time determination of which method to call based on an object's type" ... the goal behind using polymorphism is the same ... software reuse.

In 1967 1Christopher Strachey classified polymorphism in programming languages as:

  1. Ad-Hoc Polymorphism - a function that works, or appears to work, on a finite number of types, behaving differently for each type.
  2. Parametric Polymorphism - a function that works uniformly on an infinite number of types, with the types having some common structure.

In 1985 2Cardelli and Wegner extended this classification to accommodate object-oriented languages:

  1. Ad-Hoc Polymorphism
    • Overloading
    • Coercion
  2. Universal Polymorphism
    • Parametric
    • Inclusion (a.k.a. subtype, which actually predates the term "Inclusion Polymorphism" when it was used in Simula in1967)

This classification is illustrated by the following diagram.

.Polymorphism Categories

Polymorphism by Pronesto, CC3.0 via Wikimedia Commons



Polymorphism in C#

The language features of C# fit into these categories of polymorphism as:

  1. Ad-Hoc Polymorphism
    • Overloading (C# Member Overloading - Binding the call to a member with a version of the member whose signature matches the arguments used in the method call (a.k.a. Static Polymorphism).)
    • Coercion (C# Implicit Type Casting - Automatic type conversion performed by the compiler.)
  2. Universal Polymorphism
    • Parametric (C# Generics - using a parameter to indicate type)
    • Subtyping (C# Interfaces - a set of public methods any implementing class must define, or, C# Inheritance (a.k.a. Subclass) - derive new classes based on existing classes (Subtyping Polymorphism is a.k.a. Inclusion Polymorphism, Dynamic Polymorphism, Runtime Polymorphism).)
Top



Object-Oriented Polymorphism in C#

While polymorphism plays various roles in programming languages, object-oriented languages have a particular need for polymorphism:

3"When objects of different classes have a method of the same name and type, it often makes sense to be able to call that method in contexts where the exact class of the object is not known at compile time."

In C# an analysis is made of a class's declared type when the code is compiled (compile-time type). However, when the code runs the classes types are again analyzed (runtime type). When classes are not explicitly inherited, the compile-time type is the same as the runtime type. The result of this is the compile-time type is used to resolve method calls. But when classes are explicitly defined as inherited, the compile-time type can differ from the runtime type ... and it is the runtime type that is used to resolve these method calls. This allows methods with the same signature string to behave differently based upon the determination of the objects type at runtime. In C#, this is the construct that is usually referred to as simply "polymorphism". This construct is also sometimes referred to as: Dynamic Polymorphism, Runtime Polymorphism, Subtyping Polymorphism or Inclusion Polymorphism.

In the C# programming Guide for Visual Studio 2013, the two distinct aspects of Polymorphism is defined as:

  1. At run time, objects of a derived class may be treated as objects of a base class in places such as method parameters and collections or arrays. When this occurs, the object's declared type is no longer identical to its run-time type.

  2. Base classes may define and implement virtual methods, and derived classes can override them, which means they provide their own definition and implementation. At run-time, when client code calls the method, the CLR looks up the run-time type of the object, and invokes that override of the virtual method. Thus in your source code you can call a method on a base class, and cause a derived class's version of the method to be executed.

In viewing other definitions of polymorphism within the context of object-oriented programming, the concept of inheritance is the same, but different terminology is used:

Parent Class = base class (C#) = superclass (Java)

Child Class = derived class (C#) = subclass (Java)

Other definitions I have found for polymorphism in object-oriented programming include:

  1. Dynamic polymorphism is implemented by abstract classes and virtual functions. This allows classes to have different functionality while sharing a common interface.
  2. Polymorphism is where the method to be invoked is determined at runtime based on the type of the object.
  3. Polymorphism deals with how the program decides which methods it should use, depending on what type of thing it has.
  4. Polymorphism allows two or more classes in an inheritance hierarchy to have identical member functions that perform distinct tasks.
  5. The ability to treat objects of different types in a similar manner. e.g. Bird and Wolf are both Animals that can Move(). If you have an instance of Animal you can call Move() without caring what type of animal it is.
  6. The ability for classes to have different functionality while sharing a common interface.
  7. The ability of one type to appear as and be used as another type. e.g. one type A derives from type B.
  8. The ability to treat an object of any subclass of a base class as if it were an object of the base class.
  9. Polymorphism means that you can have multiple classes that can be used interchangeably, even though each class implements the same properties or methods in different ways.
  10. Polymorphism is the ability for classes to provide different implementations of methods that are called by the same name.
  11. Polymorphism is when a super class refers to subclass object, the type of the referred object determines which overridden method is called.



Top


Polymorphism, Dynamic Binding, and Casting

Object-oriented languages used dynamic binding (a.k.a. late binding, runtime binding) to support polymorphism, which determines the piece of code to execute at run-time. However, dynamic binding can result in a runtime error if an explicit type casting fails. During compilation C# is not able to check whether some explicit type casts are safe. So during runtime, an "InvalidCastException" exception is thrown when the conversion of an instance of one type to another type is not supported. This exception can occur when downcasting (converting an instance of a base type to one of its derived types, i.e. going down the inheritance hierarchy tree), but will never happen during upcasting.

class Animal
{
    public virtual void MakeNoise() { System.Console.WriteLine("Base class: an animal noise"); }
}

class Dog : Animal
{
    public override void MakeNoise() { System.Console.WriteLine("Dog: Bark"); }
}


static void Main()
{   

    // Upcast always succeeds (cast the derived class to base class)
    Animal myAnimal = new Animal();
    Dog myDog = new Dog();
    myAnimal = myDog; // Implicit upcast
           
    // Unsucessful Downcast (cast the base class to the derived class)
    Animal myAnimal2 = new Animal();
    Dog myDog2 = new Dog();
    //myDog2 = (Dog) myAnimal2; // InvalidCastException (myAnimal2 was not born a Dog)

    // Successful Downcast (must be upcast first, before can be downcast)
    // i.e. must be "born" a Dog before can be downcast as a Dog
    Dog myDog3 = new Dog();
    Animal myAnimal3 = new Animal();
    myAnimal3 = myDog3; // Implicit Upcast
    myDog3 = (Dog)myAnimal3; // Successful Downcast (born a Dog)
}



Top


Is and As Operators

C# also has the is operator to test if a downcast is supported. You can use the is operator to test if types are compatible before performing a cast. This will avoid the "InvalidCastException" exception, but duplicates the test of checking for type capability. The is operator will perform the first test, then the second test will be performed as part of the actual cast.

The as operator performs a more efficient way to cast because it only checks for type capability once and does not throw an exception if the cast fails. Instead if the cast fails, the reference variable is set to null. It is much more efficient to check for null that it is to handle an exception. However the as operator does not distinguish between the two cases "where the object was actually null" and "where the object did not support the downcast".

class Animal
{
    public virtual void MakeNoise() { System.Console.WriteLine("Base class: an animal noise"); }
}

class Dog : Animal
{
    public override void MakeNoise() { System.Console.WriteLine("Dog: Bark"); }
}

static void Main()
{
    System.Console.WriteLine("*** \"is\" operator test for type compatibility ***");
    // Use "is" a.k.a. "is compatible with" operator to test for compatible types
    // i.e. tests if object can be cast without causing an exception
    if (myDog2 is Animal)
    {
        System.Console.WriteLine("myDog2 is an Animal"); // Prints
    }
    if (myDog2 is Dog)
        System.Console.WriteLine("myDog2 is a Dog"); // Prints

    if (myAnimal2 is Dog)
        System.Console.WriteLine("myAnimal2 is a Dog");
    else
        System.Console.WriteLine("myAnimal2 is NOT a Dog"); // This one prints
    System.Console.WriteLine();

    // As operator performs cast or returns null (does not throw an exception)
    // As operator only works with reference types
    // As has best performances better because Null comparison is faster than
    //    catching exception (traditional cast) or performing the cast twice ("is")
    System.Console.WriteLine("*** \"as\" returns null if cast fails (best cast performance) ***");
    myDog2 = myAnimal2 as Dog;

    if (myDog2 == null) // "As" sets variable to null if cast fails
        System.Console.WriteLine("Cast of myDog2 was NOT successful");

    Animal myAnimal4 = new Dog();
    myAnimal2 = myAnimal4;
    myAnimal4 = myAnimal2 as Dog;
    if (myAnimal4 == null)
        System.Console.WriteLine("Cast of myAnimal4 was NOT successful");
    else
        System.Console.WriteLine("Cast of myAnimal4 was successful"); // This one Prints
    System.Console.WriteLine();   
}

Top


Polymorphism, Inheritance, and Interfaces

The concepts of polymorphism and inheritance are tightly linked in languages that perform type checking at compile time (C#, Java). The tight linking causes a reduction in polymorphic opportunities. Such as a creating a client that has inflexible dependencies on a class definition. These dependencies can also make the classes difficult to maintain or extend. These negative effects tight linking caused C# and Java to introduce the concept of interface to weaken the link between inheritance and polymorphism, but still provide strong type checking at compile time. See the Interface article for more information about using Interfaces in C#.



Top


Polymorphism Example Program

.Polymorphism Program Output
C# Polymorphism

/*
* C# Polymorphism
*
* Polymorphism is the behavior of an object to respond to a
* call to its methods based on the object type at run time.
* Polymorphism is enabled by redefining methods in derived classes.
*
* In this example, derived class methods are invoked through
* a base class reference.
*/

namespace Polymorphism
{
    class Animal
    {
        public virtual void MakeNoise() { System.Console.WriteLine("Base class: an animal noise"); }
    }

    class Dog : Animal
    {
        public override void MakeNoise() { System.Console.WriteLine("Dog: Bark"); }
    }
    class Cat : Animal
    {
        public override void MakeNoise() { System.Console.WriteLine("Cat: Meow"); }
    }

    class Chihuahua : Dog
    {
        public override void MakeNoise() { System.Console.WriteLine("Chihuaua: Arf"); }
    }

    class Program
    {
        static void Header()
        {
            System.Console.WriteLine("************************************************************************");
            System.Console.WriteLine("*** Polymorphism, upcast, downcast, \"is\", \"as\", InvalidCastException ***");
            System.Console.WriteLine("************************************************************************\n");
        }

        static void Main()
        {
            Program.Header();

            System.Console.WriteLine("*** Upcast to store in array, but still call derived method ***");
            // Upcast derived classes and store in base class array
            Animal[] myAnimals = new Animal[3];
            myAnimals[0] = new Dog();
            myAnimals[1] = new Cat();
            myAnimals[2] = new Chihuahua();

            // Method invoked is based on the run-time type of each object
            foreach (Animal animal in myAnimals)
                animal.MakeNoise();    // Prints: Bark, Meow, Arf
            System.Console.WriteLine();

            // Upcast always succeeds (cast the derived class to base class)
            Animal myAnimal = new Animal();
            Dog myDog = new Dog();
            myAnimal = myDog; // Implicit upcast
           
            // Unsucessful Downcast (cast the base class to the derived class)
            Animal myAnimal2 = new Animal();
            Dog myDog2 = new Dog();
            //myDog2 = (Dog) myAnimal2; // InvalidCastException (myAnimal2 was not born a Dog)

            // Successful Downcast (must be upcast first, before can be downcast)
            // i.e. must be "born" a Dog before can be downcast as a Dog
            Dog myDog3 = new Dog();
            Animal myAnimal3 = new Animal();

            myAnimal3 = myDog3; // Implicit Upcast
            myDog3 = (Dog)myAnimal3; // Successful Downcast (born a Dog)

            System.Console.WriteLine("*** \"is\" operator test for type compatibility ***");
            // Use "is" a.k.a. "is compatible with" operator to test for compatible types
            // i.e. tests if object can be cast without causing an exception
            if (myDog2 is Animal)
            {
                System.Console.WriteLine("myDog2 is an Animal"); // Prints
            }
            if (myDog2 is Dog)
                System.Console.WriteLine("myDog2 is a Dog"); // Prints

            if (myAnimal2 is Dog)
                System.Console.WriteLine("myAnimal2 is a Dog");
            else
                System.Console.WriteLine("myAnimal2 is NOT a Dog"); // This one prints
            System.Console.WriteLine();

            // As operator performs cast or returns null (does not throw an exception)
            // As operator only works with reference types
            // As has best performances better because Null comparison is faster than
            //    catching exception (traditional cast) or performing the cast twice ("is")
            System.Console.WriteLine("*** \"as\" returns null if cast fails (best cast performance) ***");
            myDog2 = myAnimal2 as Dog;

            if (myDog2 == null) // "As" sets variable to null if cast fails
                System.Console.WriteLine("Cast of myDog2 was NOT successful");

            Animal myAnimal4 = new Dog();
            myAnimal2 = myAnimal4;
            myAnimal4 = myAnimal2 as Dog;
            if (myAnimal4 == null)
                System.Console.WriteLine("Cast of myAnimal4 was NOT successful");
            else
                System.Console.WriteLine("Cast of myAnimal4 was successful"); // This one Prints
            System.Console.WriteLine();
        }
    }
}





Top




Reference Articles

*Another use of polymorphism in computer science is a stealth technique used in computer viruses and worms. A "Polymorphic engine" is used to mutate code each time it runs, but keeps the original algorithm intact.

Top