Lesson 7 · ~14 min · ← Lesson 6

Inheritance & interfaces

Two ways for types to relate: inheritance ("a Dog IS an Animal — gets everything it has") and interfaces ("a Robot CAN Speak() — promises a behavior"). Both exist so code can work with many types through one shape.

virtual / override: the base lets you replace behavior

class Animal
{
    public string Name;

    public Animal(string name)
    {
        Name = name;
    }

    public virtual void Speak()          // virtual: children may replace this
    {
        Console.WriteLine(Name + " makes a sound");
    }
}

class Dog : Animal                        // Dog inherits from Animal
{
    public Dog(string name) : base(name) { }   // base(...): run Animal's constructor

    public override void Speak()          // replace it
    {
        Console.WriteLine(Name + " says woof");
    }
}

Animal a = new Dog("Rex");     // a Dog IS an Animal — this assignment is legal
a.Speak();                     // dispatch follows the OBJECT: Dog's version runs
Animal b = new Animal("Generic");
b.Speak();

The last two lines are the whole point: the variable's type is Animal, but the METHOD chosen is the one on the actual object — virtual dispatch. One loop over a mixed collection can call Speak() on each and get the right bark every time.

base: build on what the parent did

base(...) calls the parent constructor; base.Method() calls the parent's version — the standard "extend, don't replace" shape:

class Animal
{
    public virtual void Speak() { Console.WriteLine("..."); }
}

class Cat : Animal
{
    public override void Speak()
    {
        base.Speak();                  // do what animals do…
        Console.WriteLine("meow");     // …then add cat behavior
    }
}

is: "is this really that?" — with a bonus binding

class Animal { }
class Dog : Animal
{
    public void Fetch()
    {
        Console.WriteLine("fetching!");
    }
}

Animal x = new Dog();          // upcast: fine, Dog is an Animal

if (x is Dog d)                // test + declare in one step
{
    d.Fetch();                 // only Dogs can fetch
}

Its quieter sibling as converts and yields null on mismatch: Dog dd = x as Dog; — then check dd != null. Use is when you'd only branch; as when you'd keep using the value either way.

Interfaces: a promise, not a family

interface IGreeter
{
    void Greet();               // a contract: no body, just the promise
}

class EnglishGreeter : IGreeter
{
    public void Greet() { Console.WriteLine("Hello"); }
}

class SpanishGreeter : IGreeter
{
    public void Greet() { Console.WriteLine("Hola"); }
}

IGreeter[] greeters = { new EnglishGreeter(), new SpanishGreeter() };   // unrelated classes, same promise
foreach (IGreeter g in greeters)
{
    g.Greet();                 // each keeps its own behavior
}

The two greeters share no base class — the interface isn't ancestry, it's a common promise. That's why C# lets a class implement MANY interfaces but inherit from only ONE base class: family is exclusive, promises are not.

Which tool, when?

SituationUse
"X is a kind of Y" with shared codeInheritance (class Dog : Animal)
"X can do Z", across unrelated familiesInterface (: IComparable)
Child tweaks parent behaviorvirtual + override
Child extends parent behavioroverride + base.Method()
Safely downcast before using extra membersis (branch) / as (keep)

Practice this now