C# (.NET 8 LTS) track

Lesson 08 · Part 2: Object-Oriented Programming

Inheritance

Reuse and extend existing classes with inheritance, virtual methods and controlled overriding.

Part 2: Object-Oriented Programming5 min readFree lesson

When there is already a well-made class, it is a waste to rebuild a similar class from scratch. Inheritance allows you to inherit the members of the parent class and add or change only the necessary parts.

Learning Objectives

  • Inherit a class using the : base syntax.
  • Call the parent constructor with : base(...) from a child constructor.
  • Know the difference between virtual / override / sealed override
  • Know how method hiding with the new keyword differs from overriding.

Key concepts

1) Inheritance with : base

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

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

Dog can be used as Animal, Name, and Eat(). C# only allows single inheritance (only one parent).

2) Calling base(...) in a child constructor

If the parent has a constructor with parameters, the child must call it directly.

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 method marked by the parent as virtual can be redefined (overridden) by the child as override.

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

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

Even if the variable type is Animal, if the actual object is Dog, Dog.Speak() is executed (=polymorphism, next section).

4) sealed override

An indication that "this method can no longer be overridden by children."

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

5) hide with new keyword

Using new instead of override means "hiding the parent method." Be careful, polymorphism does not work.

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();   //returns "Animal" — Cat's new method is ignored

override replaces the virtual dispatch table, but new is simply a new separate method with the same name.

6) protected

An access limiter used when you want to allow only children to access the parent's members.

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

See as example

Example 1 — AnimalDog: Simple inheritance

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

var d = new Dog();
d.Name = "chocolate";
d.Eat();    //inherited from parents
d.Bark();   //git unique
// Animal.cs
namespace CodingNow.Lecture.Oop08;

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

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

Execution result

I eat chocolate
Chocolate puppy!

Memo: Children use the parent's Name and Eat() without having to write them separately.

Example 2 — BaseCall: child constructor + base.Method()

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

var d = new Dog("chocolate", "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 am animal {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 parent version first
        Console.WriteLine($"The species is {Breed}.");
    }
}

Execution result

I am animal chocolate.
The breed is poodle.

Memo: base.Method() is a syntax that calls the same name method of the parent class as is.

Example 3 — VirtualOverride: A starting point for 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 woof!");
}

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

Execution result

...
Woof woof!
Wow!

Memo: The types of variables are all Animal, but Speak(), which matches the actual object, is called. Puppy.Speak() is sealed, so it cannot be changed further below that.

Example 4 — NewKeyword : override vs new difference

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

Animal a = new Cat();   //Variable type is Animal, actual object is Cat
a.Speak();              //Because it is new, the parent version ("Animal") is called.

Cat c = new Cat();
c.Speak();              //This is called the Cat 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, but new — simply define a new method with the same name.
    public new void Speak() => Console.WriteLine("Cat");
}

Execution result

Animal
Cat

Note: new operates as a "new method independent of its parent method". When polymorphism is required, override is almost always the correct answer.

Common Mistakes

  1. There are arguments in the parent constructor, but the child does not call : base(...) — compilation error.
  2. Trying to use a method without virtual in a child as override — compilation error.
  3. Polymorphism does not work because new is inadvertently used in place of override — debugging is difficult.
  4. A child tries to use member private directly — blocked. If you want to show it to your children, use protected.
  5. Forgetting the fact that it is single inheritance, write both parents like class A : B, C — compile error (multiple inheritance X). Multiple interfaces are OK (Part 10).

Cleanup

  • Single inheritance with class Child : Parent. Call the parent as base(...)/base.M().
  • virtual + override is the basic framework of polymorphism. It is possible to hide with new, but it is recommended.
  • Block additional redefinition with sealed override, and expose only to children with protected.

Try it yourself

cd src/AnimalDog
dotnet run

cd ../BaseCall
dotnet run

cd ../VirtualOverride
dotnet run

cd ../NewKeyword
dotnet run

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/AnimalDog/Animal.csC#
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    public string Name = "";

    public void Eat() => Console.WriteLine($"{Name} eat");
}
src/AnimalDog/AnimalDog.csprojText
<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.csC#
namespace CodingNow.Lecture.Oop08;

internal class Dog : Animal
{
    public void Bark() => Console.WriteLine($"{Name} Woof!");
}
src/AnimalDog/Program.csC#
using CodingNow.Lecture.Oop08;

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

d.Eat();    //Method inherited from parent (Animal)
d.Bark();   //Child (Dog) unique method
src/BaseCall/Animal.csC#
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    public string Name;

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

    public void Introduce()
    {
        Console.WriteLine($"I am animal {Name}.");
    }
}
src/BaseCall/BaseCall.csprojText
<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.csC#
namespace CodingNow.Lecture.Oop08;

internal class Dog : Animal
{
    public string Breed;

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

    //Hide (new) the parent's Introduce and call the parent version from within with base.Introduce().
    public new void Introduce()
    {
        base.Introduce();
        Console.WriteLine($"The species is {Breed}.");
    }
}
src/BaseCall/Program.csC#
using CodingNow.Lecture.Oop08;

var d = new Dog("chocolate", "poodle");
d.Introduce();
src/NewKeyword/Animal.csC#
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    public virtual void Speak() => Console.WriteLine("Animal");
}
src/NewKeyword/Cat.csC#
namespace CodingNow.Lecture.Oop08;

internal class Cat : Animal
{
    //new, not override — polymorphism doesn't work.
    //A “separate method” with the same name is just defined.
    public new void Speak() => Console.WriteLine("Cat");
}
src/NewKeyword/NewKeyword.csprojText
<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.csC#
using CodingNow.Lecture.Oop08;

//When hiding with the new keyword: Which method is called depends on the variable type.
Animal a = new Cat();
a.Speak();      //Called with Animal type → “Animal”

Cat c = new Cat();
c.Speak();      //Called with Cat type → “Cat”
src/VirtualOverride/Animal.csC#
namespace CodingNow.Lecture.Oop08;

internal class Animal
{
    //virtual: Children can redefine it with override.
    public virtual void Speak() => Console.WriteLine("...");
}
src/VirtualOverride/Dog.csC#
namespace CodingNow.Lecture.Oop08;

internal class Dog : Animal
{
    public override void Speak() => Console.WriteLine("Woof woof!");
}
src/VirtualOverride/Program.csC#
using CodingNow.Lecture.Oop08;

//The type of variables are all Animal, but Speak() is called for the actual object.
Animal a1 = new Animal();
Animal a2 = new Dog();
Animal a3 = new Puppy();

a1.Speak();
a2.Speak();
a3.Speak();
src/VirtualOverride/Puppy.csC#
namespace CodingNow.Lecture.Oop08;

internal class Puppy : Dog
{
    //sealed override: Prevents children from overriding Speak anymore.
    public sealed override void Speak() => Console.WriteLine("Wow!");
}
src/VirtualOverride/VirtualOverride.csprojText
<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>

Assignment

Problem 1 — Shape → Circle/Rectangle

  • Reference solution: shown below
  • Key concepts: Inheritance, virtual / override

Requirements

  • Create a virtual double Area() method in the parent Shape class (default return 0).
  • Child Circle receives the radius and overrides Area() with π × r².
  • Child Rectangle accepts horizontal/vertical and overrides Area() with w × h.
  • Create Circle and Rectangle objects from Program.cs and output Area().

Expected output

Circle area: 78.54
Rectangle Area: 12

Hint

  • Just use Math.PI as is.
  • For rounding, use the format specifier Math.Round(value, 2) or :F2.

Problem 2 — Employee → Manager

  • Reference solution: shown below
  • Key concept: calling : base(...) from child constructor

Requirements

  • Employee class: fields Name(string), Salary(int). Both are received from the constructor. Information output to PrintInfo().
  • Manager : Employee: Additional field Bonus(int). In the constructor, take the parent's two values ​​+ bonus and call the parent constructor with : base(name, salary).
  • Manager.PrintInfo() calls the parent's PrintInfo and also prints the bonus.
  • Create two objects in Program.cs and output information.

Expected output

Name: Younghee, Salary: 3000000
Name: Withdrawal, Salary: 5000000
Bonus: 1000000

Hint

  • Calling base.PrintInfo() from within child PrintInfo.
  • When using a child method with the same name as the parent, select between virtual/override or new.

Check the correct answer

After solving it yourself, compare it with the reference solution below.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

Homework01/Circle.csC#
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;
}
Homework01/Homework01.csprojText
<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>
Homework01/Program.csC#
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()}");
Homework01/Rectangle.csC#
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;
}
Homework01/Shape.csC#
namespace CodingNow.Lecture.Oop08;

internal class Shape
{
    public virtual double Area() => 0;
}
Homework02/Employee.csC#
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}");
    }
}
Homework02/Homework02.csprojText
<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>
Homework02/Manager.csC#
namespace CodingNow.Lecture.Oop08;

internal class Manager : Employee
{
    public int Bonus;

    //Pass name and salary 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}");
    }
}
Homework02/Program.csC#
using CodingNow.Lecture.Oop08;

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

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

Learn this lesson with a mentor

Work through the C# (.NET 8 LTS) track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now