Skip to content
wannacode

Polymorphism

Problem

Pay with an if for each kind

The company has regular employees, managers with a bonus and part-time interns. Someone made one Employee class with a "role" field, and the pay is worked out like this:

static int MonthlyPay(Employee e)
{
if (e.Role == "manager")
{
return e.Salary + e.Bonus;
}
else if (e.Role == "intern")
{
return e.Salary / 2;
}
return e.Salary;
}

It works. But then a new kind of employee comes along: a contractor. You need a new else if here, and in the method for taxes, and in the method for the payslip, and everywhere anyone asked about the role. And every employee carries a bonus field, even though only managers get a bonus.

The code for the manager is scattered all over the program instead of being in the manager.

The idea

Each class decides for itself

With inheritance from the last lesson, Manager and Intern are separate classes. Let each one say what its monthly pay is. The parent has a general version, and the child writes its own version of the same method:

class Employee
{
// ... Name, Salary and the constructor
public virtual int MonthlyPay()
{
return Salary;
}
}
class Manager : Employee
{
// ... Bonus and the constructor
public override int MonthlyPay()
{
return Salary + Bonus;
}
}
Employee ivan = new Employee("Ivan", 2000);
Manager maria = new Manager("Maria", 3000, 500);
Console.WriteLine(ivan.MonthlyPay());
Console.WriteLine(maria.MonthlyPay());
Output
2000
3500

This is called overriding a method. The child writes a method with the same name, the same parameters and the same return type. For objects of the child class, its version replaces the parent's.

In C# both sides have to agree. The parent says the method can be overridden, with virtual. The child says it overrides it, with override. Without virtual in the parent, override does not compile.

More precisely, if you are curious
  • If the child writes a method with the same name without override, the compiler warns that the method hides the parent's. If you really want that, you write new. But a hidden method is not overridden: through a variable of the parent's type, the parent's version is called.
class Report
{
public virtual string Title()
{
return "Report";
}
}
class SalesReport : Report
{
public new string Title()
{
return "Sales report";
}
}
SalesReport sales = new SalesReport();
Report report = sales;
Console.WriteLine(sales.Title());
Console.WriteLine(report.Title());
Output
Sales report
Report
  • sealed override says "classes further down cannot change it again".
  • The child cannot make the method more hidden: if it is public in the parent, it stays public.

Try it

What changed?

The intern overrides only the method for the monthly pay:

What will the program print?

class Intern : Employee
{
public Intern(string name, int salary) : base(name, salary)
{
}
public override int MonthlyPay()
{
return Salary / 2;
}
}
Intern ana = new Intern("Ana", 1600);
Console.WriteLine(ana.MonthlyPay());
Console.WriteLine(ana.Salary);
Answer

The idea

One array for all

A manager is an employee. So a variable of type Employee can point to an object of type Manager, and an Employee[] array can hold any kind of employee:

Employee[] staff =
{
new Employee("Ivan", 2000),
new Manager("Maria", 3000, 500),
new Intern("Ana", 1600)
};
foreach (Employee e in staff)
{
Console.WriteLine(e.Name + ": €" + e.MonthlyPay());
}
Output
Ivan: €2000
Maria: €3500
Ana: €800

The loop calls the same method, e.MonthlyPay(), and the result is different. The version that runs is the object's, not the one of the variable's type. This is called polymorphism: Greek for "many forms". One call, and each object answers in its own way.

Now a new kind of employee is a new class with its own MonthlyPay(). The loop and the rest of the program stay as they are. There is no if on the role and no bonus field in employees who get no bonus.

The type of the variable, though, decides what you can call. Through e you cannot reach the bonus, even if the object is a manager: the compiler only knows that e is some Employee. How to reach things that only some objects have is in the lesson "What is it really?".

See it

Which method runs

Employee[] staff =
{
new Employee("Ivan", 2000),
new Manager("Maria", 3000, 500),
new Intern("Ana", 1600)
};
foreach (Employee e in staff)
{
Console.WriteLine(e.Name + ": €" + e.MonthlyPay());
}
Step by step, in words
  1. The array is of type Employee, but the objects are of three classes. Each slot holds an arrow.
  2. e points to Ivan, a plain Employee: Employee's MonthlyPay runs.
  3. e points to Maria, a Manager: her own method runs, salary plus bonus.
  4. e points to Ana, an Intern: the intern gets half the salary.
  5. The same line, three different methods. The object decides, not the type of the variable.

The array holds three arrows of type Employee. Each one points to an object of a different class. On each round e points to the next object, and the call goes to that object's method: first in Employee, then in Manager, then in Intern.

The choice is made while the program runs, not when it compiles. The compiler does not know what will be in the array. It only checks that every Employee has such a method.

Try it

Find the bug

The program is the same, but the intern gets the full salary:

Output
Ivan: €2000
Maria: €3500
Ana: €1600

Here is the Intern class. Which line has the bug?

Pick the line with the bug.

The idea

Add to the parent, do not rewrite it

Sometimes the child does not want to replace the method completely, only to add something to it. A manager is described like an employee, plus the bonus. You call the parent's version with base.ToString():

public override string ToString()
{
return base.ToString() + ", bonus €" + Bonus;
}
Console.WriteLine(new Employee("Ivan", 2000));
Console.WriteLine(new Manager("Maria", 3000, 500));
Output
Ivan (€2000)
Maria (€3000), bonus €500

If Employee changes how it describes itself, the manager follows on its own.

This is also the ToString() from the "Classes and objects" module. There you were already overriding a method: the version in object, which is virtual. That is why you wrote override.

More precisely, if you are curious
  • You can call base.Method() from any method of the child, not only from the overriding one.
  • Overriding is not the overloading from the "Methods" module. With overloading the name is the same and the parameters are different, and the compiler chooses by the arguments. With overriding everything is the same, and the object chooses while the program runs.

Try it

What is missing?

The intern is described like an employee plus the word "intern". What goes in the blank?

class Intern : Employee
{
    public Intern(string name, int salary) : base(name, salary)
    {
    }

    public override string ToString()
    {
        return .ToString() + ", intern";
    }
}

Exercise

Each in its own way

Solve the tasks in your editor. Each task has an array of the parent type and one loop.

1. Animal shelter. An Animal class with a name and a MakeSound() method that prints "some sound". Dog and Cat override it. Put one of each of the three in an Animal[] array:

Output
Guest: some sound
Rex: Woof!
Mitzi: Meow, meow!

2. The student introduces themselves. Student from the last lesson already has a grade. Override Introduce() so that it says the grade too. Use base.Introduce(); do not copy the line from Person.

Output
I am Georgi, 45 years old.
I am Elena, 19 years old.
Average grade: 5.5

3. Party. A Performance class with a title and a Start() method. DanceMove, BreakDanceMove (inherits from DanceMove, adds difficulty) and Song override it. A Party class holds a Performance[] array and has a StartParty() method that starts everything in order and at the end says the party has started.

Output
Dancing moonwalk
Dancing windmill
difficulty 8 out of 10
Singing 'Bohemian Rhapsody'
Starting raffle
The party has started!

More tasks

  • Contractor. Add a Contractor to the employees, with hours and a rate per hour. Their monthly pay is hours × rate. Work out the payroll for Ivan, Maria, Ana and Peter (60 hours at €25):
Output
Payroll: €7800
  • Payslip. Give Employee a Payslip() method that returns the text "name: €pay" with the monthly pay. Write it only in Employee. Why does it work correctly for the manager and for the intern too?

Test

Check yourself

1. Employee e = new Manager("Maria", 3000, 500); Which MonthlyPay runs on e.MonthlyPay()?
2. Employee e = new Manager("Maria", 3000, 500); Can you get the bonus of e?
3. What do you have to write in the parent so that the child can override the method?
4. What does base.toString() do inside the child's toString()?
5. What is the difference between overriding and overloading?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·