Inheritance, Polymorphism, and Casting

In browsing various newsgroups, I found a number of questions pertaining to inheritance, polymorphism, and casting (typically all in the same post). It seemed like quite a few people out there are confused about these concepts and how they are applied so hopefully this entry can clear things up.

Inheritance allows us to extend the functionality of a class (base class) to provide more specialized functionality in a derived (child) class. This is accomplished by: providing new functionality and/or overriding (or hiding) existing functionality.

Polymorphism, on the other hand, can almost be thought of as reverse inheritance; rather than adding functionality by becoming more specific, functionality is removed by becoming less specific. This allows for multiple, related classes that provide similar functionality to be use in a generic sense. For example: a class may be substituted with its base class in order to write code that can be used by all of the specialized classes but is not tied to the specialized classes.

Casting instructs the runtime to “convert” a variable of one type into another type. What is actually happening is that the program is saying “for the object located at X, use the memory structure for type A rather than B,” A common example of casting comes from ADO.NET. When using the ExecuteScalar() method of a Command object to retrieve a count of rows, it is necessary to cast the result from Object to a numeric type: int cnt = (int)cmd.ExecuteScalar();

Casting operations can be performed implicitly or explicitly. Implicit casts (int to long) are performed automatically by the CLR when the allocated space needs to be increased. Explicit casting operations (long to int) must be specified in the code.

Note that casting requires compatible types meaning that a conversion operator must be defined. Casting from a class to its base class may be performed implicitly while casting from a base class to a derived class must be performed explicitly.

Consider the following code that demonstates these principles. To run this code, create a new Console project in Visual Studio and copy/paste it into the automatically created file. (for command line compilation, copy/paste the code into notepad, save as example.cs, and execute csc example.cs at the command line.)

/* Note that this code does not necessarily use best practices.

It is included for illustration purposes only. */

using System;

namespace InheritanceAndPolymorphism
{
public interface ISpeaks
{
void Speak();
}

public abstract class Animal
{
public string Name;
public double Weight = 0;
public double NumberOfLegs = 4;

// All animals sleep
public void Sleep()
{
Console.WriteLine("Zzzzzz.....");
}

// All animals need to eat
public virtual void Eat()
{
Console.WriteLine("Animal is eating");
}
}

public class Cat : Animal, ISpeaks
{
public void Meow()
{
Console.WriteLine("Meow!");
}

// Required by ISpeaks
public void Speak()
{
Meow();
}

public void Purr()
{
Console.WriteLine("Purrrrrrrrr");
}

public override void Eat()
{
Console.WriteLine("Cat is eating");
}
}

public class Dog : Animal, ISpeaks
{
public void Bark()
{
Console.WriteLine("WOOF!");
}

// Required by ISpeaks
public void Speak()
{
Bark();
}

public void WagTail()
{
Console.WriteLine("*WAG* *WAG* *WAG*");
}

public override void Eat()
{
Console.WriteLine("Dog is eating");
}
}

public class StartUp
{
public static void Main()
{
Cat c = new Cat();
Dog d = new Dog();

Sleep(c);
Sleep(d);
Eat(c);
Eat(d);
Speak(c);
Speak(d);
Speak2(c);
Speak2(d);
MakeHappy(c);
MakeHappy(d);
}

private static void Sleep(Animal a)
{
/* The animal class defines what must occur for a generic animal to sleep.
The Sleep() method is not marked as virtual so we are not assuming that the method
will be overridden (it may be hidden however). In this case, the following line
will call the Sleep() method of the animal class.
*/

a.Sleep();
}

private static void Eat(Animal a)
{
/* The animal class defines what must occur for a generic animal to eat but
Eat() is marked as virtual so it can be overridden by derived classes. In this
case, the following line will call the appropriate Eat() method based on the type
that is passed in to "a." If Eat(new Dog()) is called, then Dog.Eat() is executed.
*/

a.Eat();
}

private static void Speak(Animal a)
{
/* The animal class doesn't define the sound that an animal should make so
we need to determine which kind of animal we're working with.
*/

if(a is Cat)
((Cat)a).Meow();
else if(a is Dog)
((Dog)a).Bark();
}

private static void Speak2(ISpeaks s)
{
/* The interface ISpeaks requires that its implementors include a method
called Speak(). No other functionality is available.
*/

s.Speak();
}

private static void MakeHappy(Animal a)
{
/* The animal class doesn't define what animals do when they're happy so we
need to determine which kind of animal we're working with.
*/

if(a is Cat)
((Cat)a).Purr();
else if(a is Dog)
((Dog)a).WagTail();
}
}
}

After compiling and executing the code, the following is displayed:

Zzzzzz.....

Zzzzzz.....
Cat is eating
Dog is eating
Meow!
WOOF!
Meow!
WOOF!
Purrrrrrrrr
*WAG* *WAG* *WAG*

The Animal class provides some basic functionality that each animal will need to have. Eat() and Sleep() methods have been defined along with some basic fields such as NumberOfLegs.

A Cat class and Dog class are also defined and each derives from Animal. The Cat and Dog classes each inherit the functionality provided by the Animal class and extend upon it by adding some new functionality. The added functionality is only accessible from the class in which it was defined. Notice that each class overrides the functionality provided by the Eat() method. The Cat and Dog classes also implement the interface ISpeaks which requires that a method called Speak() be defined.

The StartUp class defines the entry point (Main()) and a few other methods to demonstrate the concepts of inheritance and polymorphism. When the program executes, an instance of the Cat and Dog classes are created. Each of the methods are called for both the Cat and the Dog.

Each of the methods called by the Main() method illustrate polymorphism since each method accepts an Animal (from which Cat and Dog are derived) as an argument. At this point, only the functionality provided by the Animal class is accessible despite the fact that a Cat or Dog was passed to the method. This is illustrated by the Sleep() and Eat() methods of the StartUp class.

The Sleep() method calls a.Sleep(). As indicated in the output [above], when Sleep() is called, the Sleep() method from the Animal class is executed as expected because the functionality provided by Sleep() is inherited by the Cat and Dog classes. Where the polymorphism is truly apparent though is in the Eat() method.

When Eat() is called, the output differs based on the type of animal that was passed in despite the fact that a.Eat() was called. Both the Cat and Dog classes override the Eat() method and provide their own functionality. The CLR knows that, despite being declared as type Animal, the value of the variable “a” is really a more specialized type (Cat or Dog) and calls the appropriate method.

For another example of polymorphism in action, the Speak2() method is included. The Speak2() method accepts a parameter of type ISpeaks rather than Animal. Remember that the Cat and Dog classes both implement the ISpeaks interface and, therefore; are required to define a method called Speak().

When Speak2() is called, the Cat or Dog is implicitly cast to a variable of type ISpeaks. The Speak() method of parameter is called, and like the Speak() method, the CLR knows the actual type of the variable and calls the appropriate method.

Explicit Casting is used by the Speak() and MakeHappy() methods. Additionally, these methods use the C# “is” keyword to determine the actual type that was passed to the method. The Animal class does not provide definitions for Meow() or Woof(), for example, so the variable must be cast back to its actual type in order for the functionality to be utilized.

In conclusion, the concepts of inheritance, polymorphism, and casting can be powerful tools but also reinforce the importance of good application design. The examples shown here barely scratch the surface of the potential of these concepts but should serve as a good foundation for further study.

Advertisements