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.
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
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.
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`.
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."
class Puppy : Dog
{
public sealed override void Speak() => Console.WriteLine("Yip!");
}
// classes that inherit Puppy cannot override Speak again5) Hiding with `new`
Using `new` instead of `override` means "hide the parent method." Polymorphism does not kick in β be careful.
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.
class Animal { protected int HungerLevel = 0; }
class Dog : Animal { public void Feed() => HungerLevel--; }Examples
Example 1 β `AnimalDog`: simple inheritance
// Program.cs
using CodingNow.Lecture.Oop08;
var d = new Dog();
d.Name = "Choco";
d.Eat(); // inherited from parent
d.Bark(); // child's own// 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**
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()`
// Program.cs
using CodingNow.Lecture.Oop08;
var d = new Dog("Choco", "Poodle");
d.Introduce();// 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**
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
// Program.cs
using CodingNow.Lecture.Oop08;
Animal a1 = new Animal();
Animal a2 = new Dog();
Animal a3 = new Puppy();
a1.Speak();
a2.Speak();
a3.Speak();// 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**
...
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
// 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// 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**
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
namespace CodingNow.Lecture.Oop08;
internal class Animal
{
public string Name = "";
public void Eat() => Console.WriteLine($"{Name} is eating");
}
src/AnimalDog/AnimalDog.csproj
<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
namespace CodingNow.Lecture.Oop08;
internal class Dog : Animal
{
public void Bark() => Console.WriteLine($"{Name} woof!");
}
src/AnimalDog/Program.cs
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
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
<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
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
using CodingNow.Lecture.Oop08;
var d = new Dog("Choco", "Poodle");
d.Introduce();
src/NewKeyword/Animal.cs
namespace CodingNow.Lecture.Oop08;
internal class Animal
{
public virtual void Speak() => Console.WriteLine("Animal");
}
src/NewKeyword/Cat.cs
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
<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
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
namespace CodingNow.Lecture.Oop08;
internal class Animal
{
// virtual: children can override.
public virtual void Speak() => Console.WriteLine("...");
}
src/VirtualOverride/Dog.cs
namespace CodingNow.Lecture.Oop08;
internal class Dog : Animal
{
public override void Speak() => Console.WriteLine("Woof!");
}
src/VirtualOverride/Program.cs
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
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
<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
- Parent constructor takes arguments but the child doesn't call `: base(...)` β compile error.
- Trying to `override` a method that wasn't marked `virtual` β compile error.
- Accidentally using `new` where `override` was intended, breaking polymorphism β hard to debug.
- Trying to access a `private` member from a child β blocked. Use `protected` to expose it to children.
- 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
Circle area: 78.54
Rectangle area: 12Hints
- 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
Name: Yeonghee, Salary: 3000000
Name: Cheolsu, Salary: 5000000
Bonus: 1000000Hints
- 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
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
<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
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
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
namespace CodingNow.Lecture.Oop08;
internal class Shape
{
public virtual double Area() => 0;
}
homework/answer/Homework02/Employee.cs
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
<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
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
using CodingNow.Lecture.Oop08;
var emp = new Employee("Yeonghee", 3000000);
emp.PrintInfo();
var mgr = new Manager("Cheolsu", 5000000, 1000000);
mgr.PrintInfo();
Try It Yourself
cd src/AnimalDog
dotnet run
cd ../BaseCall
dotnet run
cd ../VirtualOverride
dotnet run
cd ../NewKeyword
dotnet runNext 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.
All lecture materials and example code are openly available on GitHub.
View on GitHub β