← Back to C# series
🧩
OOP
OOP Β· Prerequisite: properties

08. Inheritance

Inheritance builds a new class by reusing an existing one's members. Learn base, virtual, override, sealed, and protected to express "is-a" relationships in code.

C#.NET 8OOPinheritance
Duration
⏱ ~1-1.5 hours
Level
πŸ“Š Intermediate
Prerequisite
🎯 Properties and encapsulation
OUTCOME
Inheritance builds a new class by reusing an existing one's members. Learn base, virtual, override, sealed, and protected to express "is-a" relationships in code.

What you'll learn

  • 1Inherit a class with the `: base` notation
  • 2Call the parent constructor via `: base(...)` in the child constructor
  • 3Know the difference between `virtual` / `override` / `sealed override`
  • 4Understand what happens when you hide a method with the `new` keyword

Overview

When you already have a well-built class, rewriting a similar one from scratch is a waste. With **inheritance** you inherit the parent's members and add or change just the parts you need.

Core Concepts

1) `: base` notation

csharp
class Animal
{
    public string Name = "";
    public void Eat() => Console.WriteLine($"{Name} is eating");
}

class Dog : Animal       // inherits Animal
{
    public void Bark() => Console.WriteLine($"{Name} woof!");
}

`Dog` can use `Animal`'s `Name` and `Eat()` as-is. C# allows **single inheritance** only (one parent).

2) Call `: base(...)` from the child constructor

If the parent has a parameterized constructor, the child must call it.

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

class Dog : Animal
{
    public Dog(string name) : base(name) { }   // call parent constructor
}

3) `virtual` and `override`

A child can redefine a parent method marked `virtual` by using `override`.

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

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

Even if the variable is of type `Animal`, if the actual object is a `Dog` then `Dog.Speak()` runs (= polymorphism β€” next lecture).

4) `sealed override`

Marks "no further override allowed for this method by descendants."

csharp
class Puppy : Dog
{
    public sealed override void Speak() => Console.WriteLine("Yip!");
}
// classes that inherit Puppy cannot override Speak again

5) Hiding with `new`

Using `new` instead of `override` means "hide the parent method." Polymorphism does not kick in β€” be careful.

csharp
class Animal { public virtual void Speak() => Console.WriteLine("Animal"); }
class Cat : Animal { public new void Speak() => Console.WriteLine("Cat"); }

Animal a = new Cat();
a.Speak();   // prints "Animal" β€” Cat's new method is ignored

`override` patches the virtual dispatch table; `new` simply defines a separate method that happens to share the name.

6) `protected`

An access modifier that lets only subclasses access the parent's member.

csharp
class Animal { protected int HungerLevel = 0; }
class Dog : Animal { public void Feed() => HungerLevel--; }

Examples

Example 1 β€” `AnimalDog`: simple inheritance

csharp
// Program.cs
using CodingNow.Lecture.Oop08;

var d = new Dog();
d.Name = "Choco";
d.Eat();    // inherited from parent
d.Bark();   // child's own
csharp
// Animal.cs
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    public string Name = "";
    public void Eat() => Console.WriteLine($"{Name} is eating");
}

// Dog.cs
internal class Dog : Animal
{
    public void Bark() => Console.WriteLine($"{Name} woof!");
}

**Output**

text
Choco is eating
Choco woof!

**Note:** The child uses the parent's `Name` and `Eat()` without redefining them.

Example 2 β€” `BaseCall`: child constructor + `base.Method()`

csharp
// Program.cs
using CodingNow.Lecture.Oop08;

var d = new Dog("Choco", "Poodle");
d.Introduce();
csharp
// Animal.cs
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    public string Name;

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

    public void Introduce()
    {
        Console.WriteLine($"I'm an animal named {Name}.");
    }
}

// Dog.cs
internal class Dog : Animal
{
    public string Breed;

    public Dog(string name, string breed) : base(name)   // call parent constructor first
    {
        Breed = breed;
    }

    public new void Introduce()
    {
        base.Introduce();   // call the parent version first
        Console.WriteLine($"My breed is {Breed}.");
    }
}

**Output**

text
I'm an animal named Choco.
My breed is Poodle.

**Note:** `base.Method()` calls the same-named method on the parent class directly.

Example 3 β€” `VirtualOverride`: starting point of polymorphism

csharp
// Program.cs
using CodingNow.Lecture.Oop08;

Animal a1 = new Animal();
Animal a2 = new Dog();
Animal a3 = new Puppy();

a1.Speak();
a2.Speak();
a3.Speak();
csharp
// Animal.cs
namespace CodingNow.Lecture.Oop08;

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

// Dog.cs
internal class Dog : Animal
{
    public override void Speak() => Console.WriteLine("Woof!");
}

// Puppy.cs
internal class Puppy : Dog
{
    public sealed override void Speak() => Console.WriteLine("Yip!");
}

**Output**

text
...
Woof!
Yip!

**Note:** All variables are typed `Animal` but the `Speak()` that matches the actual object runs. `Puppy.Speak()` is `sealed` so further descendants cannot change it.

Example 4 β€” `NewKeyword`: `override` vs `new` difference

csharp
// Program.cs
using CodingNow.Lecture.Oop08;

Animal a = new Cat();   // variable type Animal, actual object Cat
a.Speak();              // uses 'new', so parent's ("Animal") is called

Cat c = new Cat();
c.Speak();              // this calls Cat's version
csharp
// Animal.cs
namespace CodingNow.Lecture.Oop08;

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

// Cat.cs
internal class Cat : Animal
{
    // not override β€” new: a brand new method that happens to share the name.
    public new void Speak() => Console.WriteLine("Cat");
}

**Output**

text
Animal
Cat

**Note:** `new` defines "a new method unrelated to the parent method." When you need polymorphism, `override` is almost always the right answer.

Full example code (src/)

src/AnimalDog/Animal.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    public string Name = "";

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

src/AnimalDog/AnimalDog.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop08</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/AnimalDog/Dog.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Dog : Animal
{
    public void Bark() => Console.WriteLine($"{Name} woof!");
}

src/AnimalDog/Program.cs

csharp
using CodingNow.Lecture.Oop08;

var d = new Dog();
d.Name = "Choco";

d.Eat();    // inherited from parent (Animal)
d.Bark();   // child's (Dog) own

src/BaseCall/Animal.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    public string Name;

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

    public void Introduce()
    {
        Console.WriteLine($"I'm an animal named {Name}.");
    }
}

src/BaseCall/BaseCall.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop08</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/BaseCall/Dog.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Dog : Animal
{
    public string Breed;

    // Pass name up to the parent constructor. base(...) runs before the child's body.
    public Dog(string name, string breed) : base(name)
    {
        Breed = breed;
    }

    // Hide the parent's Introduce (new) and call base.Introduce() to invoke the parent version.
    public new void Introduce()
    {
        base.Introduce();
        Console.WriteLine($"My breed is {Breed}.");
    }
}

src/BaseCall/Program.cs

csharp
using CodingNow.Lecture.Oop08;

var d = new Dog("Choco", "Poodle");
d.Introduce();

src/NewKeyword/Animal.cs

csharp
namespace CodingNow.Lecture.Oop08;

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

src/NewKeyword/Cat.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Cat : Animal
{
    // not override β€” new: polymorphism does NOT kick in.
    // Just defines a "separate method" with the same name.
    public new void Speak() => Console.WriteLine("Cat");
}

src/NewKeyword/NewKeyword.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop08</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/NewKeyword/Program.cs

csharp
using CodingNow.Lecture.Oop08;

// When hidden with new, the called method depends on the variable type.
Animal a = new Cat();
a.Speak();      // called via Animal type β†’ "Animal"

Cat c = new Cat();
c.Speak();      // called via Cat type β†’ "Cat"

src/VirtualOverride/Animal.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    // virtual: children can override.
    public virtual void Speak() => Console.WriteLine("...");
}

src/VirtualOverride/Dog.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Dog : Animal
{
    public override void Speak() => Console.WriteLine("Woof!");
}

src/VirtualOverride/Program.cs

csharp
using CodingNow.Lecture.Oop08;

// All variables typed as Animal but the Speak() matching the actual object runs.
Animal a1 = new Animal();
Animal a2 = new Dog();
Animal a3 = new Puppy();

a1.Speak();
a2.Speak();
a3.Speak();

src/VirtualOverride/Puppy.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Puppy : Dog
{
    // sealed override: prevents any further descendant from overriding Speak.
    public sealed override void Speak() => Console.WriteLine("Yip!");
}

src/VirtualOverride/VirtualOverride.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop08</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Common Mistakes

  1. Parent constructor takes arguments but the child doesn't call `: base(...)` β€” compile error.
  2. Trying to `override` a method that wasn't marked `virtual` β€” compile error.
  3. Accidentally using `new` where `override` was intended, breaking polymorphism β€” hard to debug.
  4. Trying to access a `private` member from a child β€” blocked. Use `protected` to expose it to children.
  5. Forgetting single inheritance and writing `class A : B, C` β€” compile error (no multiple inheritance). Multiple **interfaces** are fine (lecture 10).

Summary

  • Single inheritance via `class Child : Parent`. Call the parent with `base(...)` / `base.M()`.
  • `virtual` + `override` is the backbone of polymorphism. Hiding with `new` is possible but not recommended.
  • `sealed override` blocks further overrides; `protected` exposes only to children.

Practice

**Practice - 08. Inheritance**

Problem 1 β€” `Shape` β†’ `Circle`/`Rectangle`

  • Project folder: `Homework01/`
  • Key concepts: inheritance, `virtual` / `override`

Requirements

  • Parent `Shape` has `virtual double Area()` (defaults to 0).
  • Child `Circle` takes a radius and overrides `Area()` to `Ο€ Γ— rΒ²`.
  • Child `Rectangle` takes width/height and overrides `Area()` to `w Γ— h`.
  • In `Program.cs` create `Circle` and `Rectangle` objects and print `Area()`.

Expected output

text
Circle area: 78.54
Rectangle area: 12

Hints

  • Use `Math.PI` directly.
  • Round with `Math.Round(value, 2)` or the `:F2` format specifier.

Problem 2 β€” `Employee` β†’ `Manager`

  • Project folder: `Homework02/`
  • Key concepts: child constructor calling `: base(...)`

Requirements

  • `Employee`: fields `Name`(string), `Salary`(int). Constructor takes both. `PrintInfo()` prints info.
  • `Manager : Employee`: extra field `Bonus`(int). Constructor takes parent values + bonus and calls `: base(name, salary)`.
  • `Manager.PrintInfo()` calls the parent's PrintInfo then prints the bonus.
  • In `Program.cs` build two objects and print info.

Expected output

text
Name: Yeonghee, Salary: 3000000
Name: Cheolsu, Salary: 5000000
Bonus: 1000000

Hints

  • Inside child `PrintInfo`, call `base.PrintInfo()`.
  • When sharing a name with the parent, pick `virtual`/`override` or `new`.

Check your answer

Try it yourself, then compare against the [`answer/`](./answer/) folder.

Answer (answer/)

homework/answer/Homework01/Circle.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Circle : Shape
{
    public double Radius;

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double Area() => Math.PI * Radius * Radius;
}

homework/answer/Homework01/Homework01.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop08</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

homework/answer/Homework01/Program.cs

csharp
using CodingNow.Lecture.Oop08;

var circle = new Circle(5);
Console.WriteLine($"Circle area: {circle.Area():F2}");

var rect = new Rectangle(3, 4);
Console.WriteLine($"Rectangle area: {rect.Area()}");

homework/answer/Homework01/Rectangle.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Rectangle : Shape
{
    public double Width;
    public double Height;

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double Area() => Width * Height;
}

homework/answer/Homework01/Shape.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Shape
{
    public virtual double Area() => 0;
}

homework/answer/Homework02/Employee.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Employee
{
    public string Name;
    public int Salary;

    public Employee(string name, int salary)
    {
        Name = name;
        Salary = salary;
    }

    public virtual void PrintInfo()
    {
        Console.WriteLine($"Name: {Name}, Salary: {Salary}");
    }
}

homework/answer/Homework02/Homework02.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop08</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

homework/answer/Homework02/Manager.cs

csharp
namespace CodingNow.Lecture.Oop08;

internal class Manager : Employee
{
    public int Bonus;

    // Pass name, salary up to the parent constructor.
    public Manager(string name, int salary, int bonus) : base(name, salary)
    {
        Bonus = bonus;
    }

    public override void PrintInfo()
    {
        base.PrintInfo();   // call parent version
        Console.WriteLine($"Bonus: {Bonus}");
    }
}

homework/answer/Homework02/Program.cs

csharp
using CodingNow.Lecture.Oop08;

var emp = new Employee("Yeonghee", 3000000);
emp.PrintInfo();

var mgr = new Manager("Cheolsu", 5000000, 1000000);
mgr.PrintInfo();

Try It Yourself

bash
cd src/AnimalDog
dotnet run

cd ../BaseCall
dotnet run

cd ../VirtualOverride
dotnet run

cd ../NewKeyword
dotnet run

Next Lecture

[09_Polymorphism](../09_%EB%8B%A4%ED%98%95%EC%84%B1/) β€” Use inheritance to make the same call run different behavior β€” polymorphism, in detail.

Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—