Just added: Algorithms you can run and practice
Updated Jun 22, 2026 Edit on GitHub

Inheritance & Interfaces - Sharing Behavior

Phase 5 built a single class - fields, constructors, properties, the whole spine of a type. But programs are rarely one type. You get families of related types: a Dog and a Cat both animals, a Circle and a Rectangle both shapes, a FileLogger and a ConsoleLogger that both, well, log. This phase answers: how do related types share code and promise common behavior without copy-pasting?

C# gives you two tools, and the phase is really about telling them apart. Inheritance is "this type is a more specific version of that type" - a Dog is an Animal, so it gets everything an Animal has, plus its own twist. Interfaces are "this type can do a certain thing" - anything drawable promises a Draw() method, no matter what it actually is. Inheritance shares implementation; interfaces share a contract.

Inheritance - the "is-a" relationship

📝 Inheritance - defining a new class (the derived/child class) that automatically gets all the public and protected members of an existing class (the base/parent class), then adds or changes things. Written with a colon: class Dog : Animal. The derived class is a kind of the base class.

The litmus test is the "is-a" test: read it aloud as "a Dog is an Animal." If true and stays true, inheritance fits. "A Car has an Engine" is has-a - a field, not inheritance. Getting this wrong is the single most common OOP mistake.

When the base class has a constructor that needs arguments, the derived class passes them up with the base(...) keyword:

class Animal
{
    public string Name { get; }

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

    public void Eat()
    {
        Console.WriteLine($"{Name} is eating.");
    }
}

class Dog : Animal          // Dog IS AN Animal
{
    public Dog(string name)
        : base(name)        // pass 'name' up to Animal's constructor
    {
    }

    public void Fetch()     // Dog adds its own behavior
    {
        Console.WriteLine($"{Name} fetches the ball.");
    }
}

class Program
{
    static void Main()
    {
        Dog rex = new Dog("Rex");
        rex.Eat();      // inherited from Animal
        rex.Fetch();    // defined on Dog
    }
}
Rex is eating.
Rex fetches the ball.

What just happened: Dog : Animal means Dog inherited Animal's Name property and Eat() method for free - rex.Eat() works even though Dog never defines Eat. : base(name) handed "Rex" up to Animal's constructor so the inherited Name got set; without it, the compiler wouldn't know how to initialize the Animal part of the Dog. Then Dog added Fetch(), which Animal knows nothing about - get everything the parent has, then extend it.

💡 Every class already inherits something. Even a plain class Account with no colon silently inherits from a universal base type called object - why every object has a ToString() you can override (Phase 5). Inheritance is already underneath everything.

virtual and override - and the trap that bites Java refugees

Here's a rule that surprises people from other languages. Inheriting a method is one thing; replacing it with a more specific version is another - and C# makes you ask for that explicitly on both sides.

📝 virtual - a base-class method keyword saying "derived classes may replace this." override - a derived-class keyword saying "I am deliberately replacing the virtual base version." You need both: virtual opens the door, override walks through it.

⚠️ This is the C#-specific rule that trips everyone. In Java, every method is overridable by default - matching the signature is enough. In C#, methods are sealed shut by default: if the base method isn't virtual, the derived class cannot truly override it. A deliberate design choice - C# wants overriding intentional, not accidental.

class Animal
{
    public string Name { get; }
    public Animal(string name) => Name = name;

    public virtual void Speak()      // 'virtual' opens this up for overriding
    {
        Console.WriteLine($"{Name} makes a sound.");
    }
}

class Dog : Animal
{
    public Dog(string name) : base(name) { }

    public override void Speak()     // 'override' replaces the base version
    {
        Console.WriteLine($"{Name} barks: Woof!");
    }
}

class Cat : Animal
{
    public Cat(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} meows: Meow!");
    }
}

class Program
{
    static void Main()
    {
        Animal a = new Dog("Rex");    // an Animal-typed variable holding a Dog
        a.Speak();                     // which Speak runs?

        Animal b = new Cat("Whiskers");
        b.Speak();
    }
}
Rex barks: Woof!
Whiskers meows: Meow!

What just happened: This is dynamic dispatch - the payoff for the virtual/override ceremony. a is declared as Animal, but at run time it holds a Dog. Calling a.Speak(), C# looks at the real object, not the declared type, and runs Dog's overridden Speak. Same for b and its Cat. The decision is made at run time based on the actual object - hence "dynamic."

⚠️ The new keyword is a trap, not a fix. Forget virtual on the base method and write override in the derived class, and the compiler errors - the good case. But write new instead of override, and the code compiles and looks like it works while doing something subtly wrong: new hides the base method rather than overriding it. The difference only shows up through a base-typed variable:

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

class Dog : Animal
{
    public new void Speak()          // 'new' HIDES, it does not override
    {
        Console.WriteLine("Woof!");
    }
}

class Program
{
    static void Main()
    {
        Dog d = new Dog();
        Animal a = d;                // same object, two different declared types

        d.Speak();                   // uses Dog's version
        a.Speak();                   // uses Animal's version - surprise!
    }
}
Woof!
Animal sound

What just happened: d and a point at the exact same object, yet print different things. With new, C# picks the method based on the declared type of the variable, not the real object - the opposite of dynamic dispatch, almost never what you want. When you mean to override, use virtual + override and verify through a base-typed variable. Seeing new on a method is a red flag.

Polymorphism - one type, many behaviors

You just saw the mechanism - now the name.

📝 Polymorphism ("many forms") - treating different derived objects uniformly through a base-class (or interface) reference, each running its own overridden behavior at run time. A variable of type Animal can hold a Dog, Cat, or any other animal, and calling .Speak() does the right thing - without your code knowing or caring which.

The payoff shows up with a collection of mixed subtypes: one loop against the base type, each object bringing its own behavior:

class Program
{
    static void Main()
    {
        // A list of Animals - but each element is really a Dog or a Cat.
        List<Animal> zoo = new List<Animal>
        {
            new Dog("Rex"),
            new Cat("Whiskers"),
            new Dog("Buddy")
        };

        foreach (Animal a in zoo)   // we only know they're Animals here
        {
            a.Speak();              // each runs ITS OWN Speak
        }
    }
}
Rex barks: Woof!
Whiskers meows: Meow!
Buddy barks: Woof!

What just happened: The loop variable a is just an Animal as far as the code can tell - foreach has no idea it's juggling dogs and cats. But every a.Speak() dispatched to the real object's overridden method, so dogs barked and the cat meowed, all from one line of code. Add a Hamster : Animal next week, drop it in the list, and this loop never changes - code against the base type automatically handles types that didn't exist when you wrote it.

Interfaces - a contract any class can sign

Inheritance has a hard limit in C#: a class can inherit exactly one base class. You can't be both a Bird and a Swimmer by inheritance. And often "is-a" is the wrong relationship - a FileLogger and a Button share nothing as types, yet both might need to be "savable" or "disposable." For sharing capability across unrelated types, you want an interface.

📝 Interface - a contract: a named list of members (methods, properties) a type promises to provide, with no implementation. A class signs it with the same colon syntax (class Circle : IShape) and must supply a body for every declared member. By convention interface names start with I: IShape, IComparable, IDisposable.

The crucial difference from inheritance: a class inherits one base class but implements many interfaces. An interface says nothing about what a type is - only what it can do.

interface IShape
{
    double Area();        // just the signature - no body, no fields
    string Describe();
}

class Circle : IShape     // Circle promises to fulfil the IShape contract
{
    private double radius;
    public Circle(double radius) => this.radius = radius;

    public double Area() => Math.PI * radius * radius;
    public string Describe() => $"Circle with area {Area():F2}";
}

class Rectangle : IShape  // an UNRELATED class, same contract
{
    private double w, h;
    public Rectangle(double w, double h) { this.w = w; this.h = h; }

    public double Area() => w * h;
    public string Describe() => $"Rectangle with area {Area():F2}";
}

class Program
{
    static void Main()
    {
        List<IShape> shapes = new List<IShape>
        {
            new Circle(2),
            new Rectangle(3, 4)
        };

        foreach (IShape s in shapes)   // we only know they're IShapes
        {
            Console.WriteLine(s.Describe());
        }
    }
}
Circle with area 12.57
Rectangle with area 12.00

What just happened: IShape declared what a shape must offer - Area() and Describe() - but not how. Circle and Rectangle supplied their own implementations, sharing no base class; their only connection is the contract. List<IShape> treated them uniformly, exactly like polymorphism with inheritance. Interfaces give the same "one loop, many behaviors" payoff without forcing types into a family tree - how unrelated things agree to be interchangeable.

💡 Default interface methods (a modern wrinkle). Since C# 8, an interface can include a default body for a member, so types that don't override it inherit that default - handy for extending an interface without breaking existing implementers. Treat it as a special-purpose escape hatch, not the norm; an interface's everyday job is still to declare a contract, not carry code.

Abstract and sealed - the two ends of the dial

Two more keywords sit at opposite extremes: one forces inheritance, the other forbids it.

📝 abstract class - a base class you cannot instantiate directly (new Animal(...) is a compile error); it exists only to be inherited. It can mix concrete members (shared code) with abstract members - declared but unimplemented - which every concrete subclass is forced to override. Use it when there's no such thing as a generic instance ("only dogs, cats, ... never just an Animal") but subtypes share real code and state.

abstract class Animal
{
    public string Name { get; }
    protected Animal(string name) => Name = name;   // shared setup

    public abstract void Speak();        // no body - subclasses MUST provide one

    public void Sleep()                  // shared concrete behavior
    {
        Console.WriteLine($"{Name} sleeps.");
    }
}

class Dog : Animal
{
    public Dog(string name) : base(name) { }
    public override void Speak() => Console.WriteLine($"{Name}: Woof!");
}

class Program
{
    static void Main()
    {
        // Animal a = new Animal("???");  // compile error: can't instantiate abstract
        Dog d = new Dog("Rex");
        d.Speak();
        d.Sleep();
    }
}
Rex: Woof!
Rex sleeps.

What just happened: abstract class Animal can't be new-ed - there's no such thing as a generic animal, and the commented-out line proves the compiler enforces that. Speak() is abstract, so Animal declares it but refuses to implement it, forcing Dog to override it. Meanwhile Sleep() is fully written once and shared by every subclass. That blend - force some methods, share others, hold common state - is what separates an abstract class from an interface.

📝 sealed class - the opposite: sealed cannot be inherited from at all. sealed class Receipt slams the door so no one can subtype it. Reach for it when behavior must not be altered by subclassing - safety, guarantees, or signaling "this is final." You can also seal an individual override to stop further overriding down the chain.

💡 So which do you actually pick? The plain guidance:

  • Default to interfaces. They model capability, a type can implement many, and they avoid a rigid family tree. When types just need to agree on a contract, an interface is almost always right.
  • Use an abstract class when subtypes genuinely share state and code. If every subclass would copy-paste the same fields and helpers, an abstract base earns its keep by owning that shared implementation once. The cost is the one-base-class limit.

⚠️ Favor composition and interfaces over deep inheritance hierarchies. The classic beginner trap is building tall towers - Animal → Mammal → Carnivore → Dog → Puppy - then finding a change near the top ripples unpredictably to the bottom, or a new type doesn't fit the tree. Most reuse in real C# codebases comes from small interfaces plus composition (an object holding other objects, the "has-a" case). Use inheritance shallowly, only when "is-a" is unmistakably true.

Recap

  1. Inheritance (class Dog : Animal) gives a derived class everything its base has, then lets it add more. Use the "is-a" test, and pass base-constructor arguments with base(...).
  2. virtual + override are both required for true overriding - and ⚠️ this is C#-specific: methods are not virtual by default (unlike Java). The new keyword hides instead of overriding, which is a silent trap; verify behavior through a base-typed variable.
  3. Polymorphism is the payoff: a base-typed variable (or a List<Animal>) runs each object's own overridden method at run time, so one loop handles every subtype - including ones you add later.
  4. Interfaces declare a contract with no implementation; a class implements many interfaces but inherits one base class. Name them IThing. They share capability across unrelated types.
  5. abstract classes can't be instantiated and can force subclasses to implement members (shared code + enforced contract); sealed forbids inheritance entirely.
  6. 💡 Prefer interfaces for capability and composition over deep hierarchies; reach for an abstract class only when subtypes truly share state and code.

You can now connect your types together - let them build on each other, promise common behavior, and be used interchangeably. Next, we deal with the messy real world: what happens when things go wrong (exceptions) and how to read and write data (I/O).

Quick check

Test yourself on the ideas that separate inheritance from interfaces - especially the override trap:

[
  {
    "q": "In C#, what do you need for a derived class to truly override a base-class method (so a base-typed variable runs the derived version)?",
    "choices": [
      "The base method must be marked 'virtual' and the derived method marked 'override'",
      "Nothing special - every C# method is overridable by default, like in Java",
      "The derived method must use the 'new' keyword to replace the base version",
      "Both methods must be marked 'static'"
    ],
    "answer": 0,
    "explain": "C# methods are sealed by default. You need 'virtual' on the base method to allow overriding and 'override' on the derived method to do it. Unlike Java, matching the signature isn't enough - and 'new' only *hides* the method (picking by declared type), which is a common trap, not a real override."
  },
  {
    "q": "What is the key difference between inheriting a base class and implementing an interface in C#?",
    "choices": [
      "A class can implement many interfaces but inherit only one base class; interfaces share a contract while a base class shares implementation",
      "There is no difference - they are two words for the same feature",
      "Interfaces let you inherit from several base classes at once, replacing single inheritance",
      "A base class has no implementation, while an interface always provides full method bodies"
    ],
    "answer": 0,
    "explain": "Inheritance models 'is-a' and shares actual implementation, but C# allows only one base class. Interfaces model 'can-do' - a pure contract (traditionally with no bodies) that any number of unrelated classes can sign, so a class can implement many at once."
  },
  {
    "q": "When should you reach for an abstract class instead of an interface?",
    "choices": [
      "When the subtypes genuinely share state and concrete code, and a generic instance of the base makes no sense",
      "Always - abstract classes are strictly better than interfaces",
      "Whenever you want a type to be usable by code that didn't exist when you wrote it",
      "When you need a single type to inherit from several bases at once"
    ],
    "answer": 0,
    "explain": "An abstract class earns its keep when subtypes share real fields and helper methods (so the base owns that code once) and there's no sensible standalone instance of the base. Otherwise prefer interfaces: they model capability, allow multiple implementation, and avoid the one-base-class limit and brittle deep hierarchies."
  }
]

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. In C#, what do you need for a derived class to truly override a base-class method (so a base-typed variable runs the derived version)?

2. What is the key difference between inheriting a base class and implementing an interface in C#?

3. When should you reach for an abstract class instead of an interface?