Achieving (something similar to) multiple inheritance in C#

Multiple inheritance is a concept supported in some programming languages (C++, Python, etc) which allows programmers to define classes that inherit more than just a single base class. C# does not allow multiple inheritance on classes, however it’s possible for a class to implement multiple interfaces. In case you find it necessary for your project, there are some patterns that utilize interfaces to achieve a class architecture similar to multiple class inheritance.


When do I need multiple inheritance?

A typical use case is an environment with multiple abstract classes that define properties and behaviors, that are very unlikely to change in each implementation. For example, let’s assume you are developing a project, related somehow to animals and you have a trivial base class, defining common members of all animal species.

The Animal class is abstract and it also has members that have implementation, because it’s unlikely for them to change in its subclasses. Although if something does need to change, that can be done by overriding the definitions as needed.

To extend this environment, you'd want to add some more abstract classes.

Obviously, the abstraction level will differ, depending on our scope, but in our basic example, we can implement some animal classes that make use of these archetypes.

Which would give us a convenient mechanism for building classes by writing very little code, because majority of the functionality is not changed from class to class.

Since C# doesn’t support multiple inheritance directly, let's look at some ways of achieving similar results, their benefits and drawbacks.


  1. Handler pattern

This approach utilizes interfaces that enforce implementing classes to contain a special object – Handler. The handler is aware of its parent context and takes care of specific behaviors. Essentially handlers play the role of parent classes in multiple inheritance, but their implementation is delegated instead.

By accessing the Parent property, the handler can resolve their encapsulating object, similar to how base works in inheritance. This allows them to statically access any members that the interface enforces.

Usage example:

Pros:

  • Easy to implement and maintain
  • Class relationship is known at compile-time, meaning that all references can be resolved and verified statically
  • Can have different handlers for one interface, as long as they derive from the required handler class
  • Handlers can be passed in constructors or can be set at run-time

Cons:

  • Each “inheritance” requires adding of an interface and a handler (2 classes at least)
  • Handlers cannot have hidden shared members with the encapsulating class or other handlers
  1. Component system

This approach is very similar to the handler pattern, but unlike the later, the class relationship is built at run-time instead of compile-time. It’s really popular in game development industry, because these components can be dynamically loaded from external assemblies or compiled on the fly using scripts.

Note that the Human.Name and Duck.Quack() were left in, but it's generally considered a bad practice when dealing with a component system. Typically, in environments that utilize this pattern, all of object’s behaviors are supposed to be handled only by its components. Classes at this point merely become a mechanism of grouping components together.

The curiously recurring template pattern (public class Animal : Extendable<Animal>) is not inherent to this method, but it provides type safety when accessing the Parent object from the component, as well as when adding components to an object – the generic constraint will prevent adding a component, which is not applicable for the given type.

Usage example:

Pros:

  • Components can be added or removed at any point
  • Components can be loaded dynamically from other assemblies or compiled at run-time (scripts, plugins)
  • Each “inheritance” requires adding of a single class (the component)

Cons:

  • Relatively difficult to implement
  • Requires curiously recurring template pattern for type safety
  • Difficult to override or substitute components
  • Components cannot have hidden shared members with encapsulating class or other componentsbeen in MI)
Comments