Blogs
Safe Inheritance

Writing Safe Inheritance

Background

I found myself writing many classes, abstractions, and a lot of inheritance at Microsoft. Something that I found myself struggling was writing well structured inheritance. Argueably I’d choose Composition over Inheritance to avoid the flaws of Inheritance. But, when I do find myself using Inheriance I will think about what I will discuss in this blog. See you must be very careful when choosing to use Inheritance, I learned this the hard way by making poorly design decisions. Inheritance became a nightmare! But, as I became more experienced I noticed patterns and determine scenarios in which I will use Inheritance and how to use it.

See, at schools or textbooks, you will simply learn about Inheritance and its mechanism but not how to use it properly.

Let me first point out the complexities I’ve faced:

  • Subclasses were coupled to parent classes - I will find myself needing to override functions I didn’t need. If I went ahead and break the uneccessary props/methods from these base classes then this will cause a expensive refactor since many classes depending on this will start breaking. There were dependency issues — modifying the base class can accidentally cause problems for the subclasses, so you’re stuck with your base class, and don’t even get compiler errors here. One way to avoid this is have every subclass override every method, in which case I’m just using interfaces basically…
  • This type of complexity can lead to ambiguities and confusion about which superclass’s methods and properties are being used, making the code harder to read and maintain.
  • Misuing virtual and override keywords - in certain cases I found myself using the virtual keyword to override the method in the child class and not knowing wheen to call the base virtual method. There will also be cases where I will override the virtual method when it is doing side effects.
  • Be consistent with your inheritance across your codebase! Say if you’re dealing with a part of a codebase Extensions and it uses, say, ExtensionBasebase class that its child classes uses across the codebase, and you start using another base class the hierachy will start being confusing. If you are going to do this, have a good reason for this, do you truly need to do this?

Alternatives

So what solutions can we try?

  1. My goto will be to use Composition. If you’d like to see a code walkthrough of, learn in the Avoid this Inheriance Problem section.
  2. Leverage Interfaces to achieve this:
C#
1
interface IAnimal
2
{
3
void Eat();
4
}
5
6
class Animal : IAnimal
7
{
8
public void Eat()
9
{
10
Console.WriteLine("Eating...");
11
}
12
}
13
14
class Dog : IAnimal
15
{
16
public void Eat()
17
{
18
Console.WriteLine("Dog is eating...");
19
}
20
}
  1. Using Abstract Classes and Interfaces Together. This approach provides a balance between reusing code (through abstract classes) and ensuring polymorphic behavior (through interfaces):
C#
1
abstract class Content
2
{
3
public abstract void Display();
4
}
5
6
interface IStorable
7
{
8
void Save();
9
}
10
11
class Article : Content, IStorable
12
{
13
public override void Display()
14
{
15
// Display the article
16
}
17
18
public void Save()
19
{
20
// Save the article to a database
21
}
22
}
  1. Understand when to use virtual and abstract:

I follow these principels:

  • If the base method has side effects, then keep non-virtual.
  • If the base method only returns a result, then make it virtual and override it freely.
  • If you need the base result, call the base method, and use its result freely.

See for example of a bad design choice:

  • As you may see, StartEngine() may have side effects which is a big NO to override!
  • When will you call the base.StartEngine()?
C#
1
public class Vehicle
2
{
3
public string Make { get; set; }
4
5
public string Model { get; set; }
6
7
public string EngineStatus { get; private set; } = "Off";
8
9
public Vehicle(string make, string model)
10
=> (Make, Model) = (make, model);
11
12
// this code will cause side effects!
13
public virtual void StartEngine()
14
{
15
// some code...
16
17
EngineStatus = "idle";
18
19
// some code...
20
}
21
}
22
23
public class Car : Vehicle
24
{
25
public string ScreenContent { get; private set; } = "Off";
26
27
public Car(string make, string model) : base(make, model)
28
{
29
}
30
31
public override void StartEngine()
32
{
33
ScreenContent = "...";
34
base.StartEngine(); // when to call this?
35
}
36
}

To fix this, we can leverage abstract methods by doing the following:

C#
1
public abstract class Vehicle
2
{
3
public string Make { get; set; }
4
5
public string Model { get; set; }
6
7
public string EngineStatus { get; private set; } = "Off";
8
9
public Vehicle(string make, string model)
10
=> (Make, Model) = (make, model);
11
12
public void StartEngine()
13
{
14
if (EngineStatus is not "Off") return;
15
16
BeforeEngineStart();
17
EngineStatus = "Idle";
18
AfterEngineStart();
19
}
20
21
// Freely to override necessary methods
22
protected virtual void BeforeEngineStart() { }
23
protected virtual void AfterEngineStart() { }
24
}
25
26
public class Car : Vehicle
27
{
28
public string ScreenContent { get; private set; } = "Off";
29
30
public Car(string make, string model) : base(make, model)
31
{
32
}
33
34
protected override void AfterEngineStart()
35
{
36
ScreenContent = "...";
37
base.StartEngine(); // you may call this before or after...
38
}
39
}

I hope this helps!

x, gio