Start learning free

LESSON 187 OF 347 · OBJECT-ORIENTED PROGRAMMING

Polymorphism in C#: One Call, Many Behaviors

Lesson Read the full explanation

TL ; DR:

Polymorphism is when a single piece of code (like animal.speak()) does different things depending on which subclass the object actually is. You write the call once, and the right override runs automatically.


Animal[] animals = { new Dog(), new Cat() };
foreach (Animal a in animals) {
    a.Speak();   // dispatches to Dog.Speak then Cat.Speak
}


Full lesson:

Polymorphism (Greek for "many forms") is what makes inheritance and interfaces actually useful at scale. Once Dog and Cat both inherit from Animal and override speak(), you can stop caring which one you're holding. Any code that takes an Animal and calls speak() works for both.

Why it matters

Imagine you're writing a function that prints what every animal in a zoo says:

void Roll(Animal[] animals) {
    foreach (var a in animals) a.Speak();
}

This function works for any current or future kind of animal. Add a Cow subclass tomorrow — this loop runs Cow.Speak() automatically, no edits needed. That's the win: code that works today and keeps working as the type hierarchy grows.

Static type vs dynamic type

The reference is typed as Animal. The object it actually points to is a Dog, or a Cat, etc. Polymorphism is the language dispatching the method call based on the dynamic type (what it is at runtime) rather than the static type (what the variable was declared as).

Notes by language

  • C# / C++ — the parent method must be virtual (and the override marked override) for runtime dispatch. Otherwise you get static dispatch and the wrong version runs.
  • Java / JS / Python — methods are virtual by default. Just override and it works.

Your task

Animal, Dog, and Cat are already defined, with the right overrides. Inside MainFunction, build a collection containing one Dog and one Cat (typed as Animal), then loop through it calling speak() on each.

Expected output:

Woof
Meow
STEP 1 OF 2
    Ctrl + Enter to check

    C# · OBJECT-ORIENTED PROGRAMMING

    Polymorphism in C#, explained

    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 is Animal. 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 virtual or override. The call then uses the variable's type, as shown above.
    • Declaring the array as Dog[]. A Cat can'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 to speak() 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
    
    1. Build the array. Declare an Animal[] named animals, and fill it with new Dog() and new Cat() using an initializer in braces. Both objects are stored as Animal references.
    2. Loop and call. A foreach loop with an Animal variable a calls a.Speak() on each element. The loop doesn't know which animal it has. The first call runs Dog.Speak and the second runs Cat.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 virtual in the base class and override in 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.
    Keep going with C#About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free