What polymorphism is
Polymorphism comes from Greek words meaning "many forms". In C# it means that one piece of code can work with objects of different classes and still do the right thing for each of them. If Dog and Cat both derive from Animal and both override Speak, then code that holds an Animal and calls Speak works for either, without checking which one it has.
Why it is useful
Consider a method that makes every animal in a list speak:
static void Roll(Animal[] animals) {
foreach (Animal a in animals) {
a.Speak();
}
}
This method only knows about Animal. If you add a Cow class tomorrow that overrides Speak to print "Moo", Roll works with cows immediately. You don't have to change it, and you don't need a chain of if statements asking "is this a dog? is this a cat?". The code keeps working as the family of classes grows.
Static type and runtime type
Every object has two types to think about:
- The static type is the type the variable was declared with. In
Animal a = new Dog();it isAnimal. The compiler uses it to decide which members you may call. - The runtime type is the class of the actual object, here
Dog.
For a virtual method, C# picks the implementation by the runtime type. That is the mechanism behind polymorphism. A list can hold the base type, and each element answers according to its own class.
Because the compiler checks against the static type, the variable can only use members that the base type declares. If Dog has a method Wag that Animal lacks, then a.Wag() is a compile error, even though the object is a dog.
It needs virtual and override
Runtime dispatch happens only for methods marked virtual in the base class and override in the derived class. Without that pair, the compiler chooses the method from the variable's type:
class Animal {
public void Sleep() { Console.WriteLine("Animal sleeps"); }
}
class Dog : Animal {
public new void Sleep() { Console.WriteLine("Dog sleeps"); }
}
Animal a = new Dog();
a.Sleep(); // Animal sleeps
The object is a Dog, but Sleep was hidden rather than overridden, so the call uses Animal's version. This is the most common reason a polymorphic call "does the wrong thing" in C#. Code ported from Java often forgets that methods are not virtual unless you ask.
Collections of the base type
Any collection can hold derived objects under the base type. An array initializer converts each element to Animal implicitly, and a List<Animal> works the same way:
var zoo = new List<Animal>();
zoo.Add(new Dog());
zoo.Add(new Cat());
foreach (Animal a in zoo) {
a.Speak(); // Woof, then Meow
}
A fixed-size array is fine when you know the contents. A List is better when you add animals as the program runs.
Common mistakes
- Missing
virtualoroverride. The call then uses the variable's type, as shown above. - Declaring the array as
Dog[]. ACatcan't go into it. The array must be typed with the common base class,Animal[]. - Calling derived-only members through the base type. Cast the variable first, or add the method to the base class.
- Using lowercase method names. The lesson's method is
Speak; a call tospeak()doesn't match. - Checking types by hand. A long
if (a is Dog) ... else if (a is Cat)chain defeats the purpose. Put the behaviour in an override instead.
The exercise, step by step
The classes Animal, Dog and Cat are already written, with Speak marked virtual in Animal and override in the two subclasses. In MainFunction you write the code that uses them. The expected output is:
Woof
Meow
- Build the array. Declare an
Animal[]namedanimals, and fill it withnew Dog()andnew Cat()using an initializer in braces. Both objects are stored asAnimalreferences. - Loop and call. A
foreachloop with anAnimalvariableacallsa.Speak()on each element. The loop doesn't know which animal it has. The first call runsDog.Speakand the second runsCat.Speak.
class Animal {
public virtual void Speak() {
Console.WriteLine("...");
}
}
class Dog : Animal {
public override void Speak() {
Console.WriteLine("Woof");
}
}
class Cat : Animal {
public override void Speak() {
Console.WriteLine("Meow");
}
}
class Solution {
public void MainFunction() {
Animal[] animals = { new Dog(), new Cat() };
foreach (Animal a in animals) {
a.Speak();
}
}
}
The base version prints .... It would run only for an object whose class doesn't override Speak.
Key takeaways
- Polymorphism lets one call behave differently depending on the object's actual class.
- In C# it requires
virtualin the base class andoverridein the derived classes. - The variable's static type decides what you may call; the runtime type decides which version runs.
- Store derived objects in arrays or lists of the base type, and new subclasses fit in without changing the loop.