Source: Ch6_OOP.pdf

Method Hiding and Polymorphism

Compile-time vs runtime polymorphism, overloading, overriding, method hiding with new, and upcasting.

Compile-time Polymorphism — Overloading

Overloading means multiple methods share a name but have different parameter lists. The compiler resolves which version to call at compile time, so it is called static (compile-time) polymorphism.

csharp
class Display
{
    public void Show(int n)    => Console.WriteLine("int: " + n);
    public void Show(double d) => Console.WriteLine("double: " + d);
    public void Show(string s) => Console.WriteLine("string: " + s);
}

Display d = new Display();
d.Show(5);
d.Show(3.14);
d.Show("Hello");

Output

int: 5
double: 3.14
string: Hello

Overloading — Exercise 1

The compiler picks the best matching overload based on argument types. Ambiguous calls that match two overloads equally cause a compile error.

Add the missing overload to handle a double argument

Hint

Add a Format(double d) overload to handle the double argument.

At what stage is an overloaded method call resolved?

Runtime Polymorphism — Overriding

When a base-type variable holds a derived object and you call a virtual method, C# calls the derived override at runtime. This is dynamic dispatch.

csharp
class Animal
{
    public virtual string Sound() => "...";
}

class Dog : Animal
{
    public override string Sound() => "Woof";
}

class Cat : Animal
{
    public override string Sound() => "Meow";
}

Animal[] animals = { new Dog(), new Cat(), new Dog() };
foreach (Animal a in animals)
    Console.WriteLine(a.Sound());

Output

Woof
Meow
Woof

Overriding — Exercise 1

Runtime polymorphism requires both virtual on the base and override on the derived. Missing either defeats dynamic dispatch.

Fix the missing virtual and override keywords

Hint

Add virtual to Printer.Print() and override to ColorPrinter.Print().

What type of polymorphism uses virtual and override?

Method Hiding with new

The new keyword intentionally hides a base class member with a same-name derived member. Unlike override, there is no runtime dispatch — the declared type of the variable determines which version runs.

csharp
class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

class B : A
{
    public new void F() => Console.WriteLine("B.F (hidden)");
}

B b = new B();
b.F();          // B.F

A a = new B();
a.F();          // A.F — no polymorphism with new

Output

B.F (hidden)
A.F

Method Hiding — Exercise 1

When a derived method uses new instead of override, calling through a base-type reference always invokes the base version.

Change hiding to override for true polymorphism

Hint

Replace new with override so that Logger reference uses FileLogger's version at runtime.

What does new do in a derived member declaration?

Upcasting and Downcasting

Upcasting (derived → base) is implicit and safe. Downcasting (base → derived) requires an explicit cast and throws InvalidCastException if the object is the wrong type. Use is and as for safe checks.

csharp
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"); }

Animal a = new Dog();    // upcast (implicit)
a.Speak();               // Woof

if (a is Dog dog)        // safe downcast with pattern matching
    Console.WriteLine("It's a dog!");

Animal b = new Cat();
Dog? d = b as Dog;       // returns null if wrong type
Console.WriteLine(d == null ? "Not a dog" : "Dog");

Output

Woof
It's a dog!
Not a dog

Upcasting / Downcasting — Exercise 1

A direct explicit cast to the wrong type throws InvalidCastException. Use as or is to cast safely.

Fix the unsafe downcast with as

Hint

Use as to attempt the cast — it returns null instead of throwing on failure.

What does the as operator return when the cast fails?