C# inheritance, virtual, and abstract members

Override behaviour in subclasses and call it through a base-type reference.

C#
using System;

abstract class Employee {
  public string Name { get; }

  protected Employee(string name) {
    Name = name;
  }

  public abstract int MonthlyPay();

  public virtual string Title {
    get { return "Employee"; }
  }

  public override string ToString() {
    return Title + " " + Name + ": " + MonthlyPay();
  }
}

class Salaried : Employee {
  private readonly int yearly;

  public Salaried(string name, int yearly) : base(name) {
    this.yearly = yearly;
  }

  public override int MonthlyPay() {
    return yearly / 12;
  }
}

class Hourly : Employee {
  private readonly int rate;
  private readonly int hours;

  public Hourly(string name, int rate, int hours) : base(name) {
    this.rate = rate;
    this.hours = hours;
  }

  public override int MonthlyPay() {
    return rate * hours;
  }
}

sealed class Manager : Salaried {
  private readonly int bonus;

  public Manager(string name, int yearly, int bonus) : base(name, yearly) {
    this.bonus = bonus;
  }

  public override string Title {
    get { return "Manager"; }
  }

  public override int MonthlyPay() {
    return base.MonthlyPay() + bonus;
  }
}

class Program {
  static void Main() {
    Employee[] staff = {
      new Salaried("Ada", 60000),
      new Hourly("Bo", 20, 160),
      new Manager("Cy", 96000, 500),
    };
    int total = 0;
    foreach (Employee e in staff) {
      Console.WriteLine(e);
      total += e.MonthlyPay();
    }
    Console.WriteLine("total " + total);
  }
}
Output
Employee Ada: 5000
Employee Bo: 3200
Manager Cy: 8500
total 16700

Explanation

A subclass inherits the members of its base class. A base member marked virtual can be replaced in a subclass with override, and an abstract member has no body and must be overridden. When code calls the member through a base-type variable, the version of the actual object runs. This is polymorphism: the same call behaves differently for different objects.

base.Method() calls the parent's version from inside an override, so a subclass can extend instead of replace it. sealed stops further inheritance or overriding. Overriding ToString makes an object print as readable text. The example calculates monthly pay for three kinds of employees with one loop that does not know their concrete classes.

Explore the dry run
Guided walkthroughReady
1using System;
2
3abstract class Employee {
4 public string Name { get; }
5
6 protected Employee(string name) {
7 Name = name;
8 }
9
10 public abstract int MonthlyPay();
11
12 public virtual string Title {
13 get { return "Employee"; }
14 }
15
16 public override string ToString() {
17 return Title + " " + Name + ": " + MonthlyPay();
18 }
19}
20
21class Salaried : Employee {
22 private readonly int yearly;
23
24 public Salaried(string name, int yearly) : base(name) {
25 this.yearly = yearly;
26 }
27
28 public override int MonthlyPay() {
29 return yearly / 12;
30 }
31}
32
33class Hourly : Employee {
34 private readonly int rate;
35 private readonly int hours;
36
37 public Hourly(string name, int rate, int hours) : base(name) {
38 this.rate = rate;
39 this.hours = hours;
40 }
41
42 public override int MonthlyPay() {
43 return rate * hours;
44 }
45}
46
47sealed class Manager : Salaried {
48 private readonly int bonus;
49
50 public Manager(string name, int yearly, int bonus) : base(name, yearly) {
51 this.bonus = bonus;
52 }
53
54 public override string Title {
55 get { return "Manager"; }
56 }
57
58 public override int MonthlyPay() {
59 return base.MonthlyPay() + bonus;
60 }
61}
62
63class Program {
64 static void Main() {
65 Employee[] staff = {
66 new Salaried("Ada", 60000),
67 new Hourly("Bo", 20, 160),
68 new Manager("Cy", 96000, 500),
69 };
70 int total = 0;
71 foreach (Employee e in staff) {
72 Console.WriteLine(e);
73 total += e.MonthlyPay();
74 }
75 Console.WriteLine("total " + total);
76 }
77}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Every concrete employee must say how it is paid.—
—
A virtual property: subclasses may replace it.—
—
A Manager extends the Salaried calculation instead of repeating it: 96000 / 12 + 500.—
—
One array holds three different kinds of object, all seen as Employee.—
—
ToString runs, which calls the Title and MonthlyPay of the real object.—
Employee Ada: 5000
Employee Bo: 3200
Manager Cy: 8500
5000 + 3200 + 8500.—
Employee Ada: 5000
Employee Bo: 3200
Manager Cy: 8500
total 16700

Cost and common mistakes

Each MonthlyPay is O(1).

  • A member without virtual cannot be overridden. Declaring a member with the same name hides it instead, which is rarely what you want.
  • An abstract class cannot be instantiated. new Employee("x") does not compile.
  • Deep inheritance trees are hard to change. Prefer composition and interfaces when classes only share a few behaviours.

Try it yourself

How would you add an Intern with a fixed pay?

Reveal answer and reasoning

Create class Intern : Employee with an override of MonthlyPay returning the fixed amount. The loop in Main needs no change.

Further reading