The general solution to avoid a multi-inheritance scenario is called composition.
And because you are the author of all participating types you can even consider to refactor your types to the relevant types P
and T
into one inheritance tree (not recommended).
We can generally say that it is not natural that a type must inherit from more than one superclass. While multi-inheritance is primarily not supported because of compiler issues (compilers are usually required to be deterministic), multi-inheritance is plainly wrong in terms of semantics.
Fore example, TypeX
should inherit from Type1
and TypeA
: if TypeX
is Type1
and TypeX
is also TypeA
then both Type1
and TypeA
should already be children of the same inheritance tree. Usually they are not because they have nothing in common.
It is semantically wrong to inherit from both Car
and Submarine
to create a Spaceship
just because you need particular functionality of each.
It's semantically wrong even when multi-inheritance would be allowed. Inheritance is not always the solution to reuse functionality.
Inheritance is always a technique primarily meant to add specialization to a generalized super type and not to reuse functionality.
While Car
is the most generalized type a RaceCar
represents a specialization. It's therefore semantically correct to let RaceCar
derive from Car
because a RaceCar
is really a Car
.
But how to design the aforementioned Spaceship
when you need particular functionality of the Car
e.g. the cockpit, and functionality of the Submarine
e.g. the air conditioning system? ==> Composition.
Although every beginner believes inheritance is the core and must-use feature of OO programming, composition is the more natural and intuitive technique.
In fact most objects in nature are composed of other objects.
For example, a hot air balloon is composed of a basket (gondola), some rigging and the bag that holds the air, and a source of heat that burns propane gas. The air itself is composed of molecules. A molecule is composed of two or more atoms. Each atom is composed of the nucleus, that consists of protons and neutrons and is surrounded by a cloud of electrons. You can do this with every object. Object are composed of other objects that themselves are compositions.
To compose our Spaceship
wen have to create the objects we can compose it of. This means, we must extract the cockpit from the Car
and create a Cockpit
class. Now Car
and Spaceship
can both use a Cockpit
. Same applies to the air conditioning system: we extract the functionality to a dedicated AirConditioningSystem
class and enable both the Submrine
and the Spaceship
to use it. Spaceship
can now use the best of both types without being forced to inherit from them (i.e. create a very unnatural relationship).
It's difficult to tell which of the two solutions (merge types into a common inheritance tree vs composition) is the better for your case as you haven't shared enough details.
Composition expresses a has a relationship: House
has a Basement
and has a Stairway
. Inheritance expresses a is a relationship: Warehouse
is a House
. You have to decide which technique expresses the relationship between your types best.
Usually you will always use a mix of composition and inheritance. A subclass inherits functionality from a superclass and at the same time adds more functionality by using other objects.
Solution 1: Composition
Your intent is to let F1
, F2
and F3
extend P
and T
. This means that F1
, F2
and F3
use functionality of P
AND T
.
Applying the spaceship example to your problem would lead us to extract the reusable functionality into a new class or even classes.
In your scenario we will end up with at least two classes: Q
, that contains the extracted functionality of P
that we want to reuse, and U
, that contains the extracted functionality of T
.
The final class design is as follows:
Q.cs
The reusable functionality extracted from P
.
class Q
{
// Functionality that was previously implemented in P
public void DoSomething()
{}
}
U.cs
The reusable functionality extracted from T
.
class U
{
// Functionality that was previously implemented in T
public void DoSomething()
{}
}
P.cs
Derives from the Microsoft UserControl
class and is composed of Q
.
class P : UserControl
{
private Q QFunctionality { get; }
// Use the constructor to actually compose the instance
// either by creating the dependencies locally
// or by requesting the via constructor parameters
public P()
=> this.QFunctionality = new Q();
public void ExecuteSomeExternalFunctionality()
=> this.QFunctionality.DoSomething();
}
T.cs
Derives from the Microsoft UserControl
class and is composed of U
.
class T : UserControl
{
private U UFunctionality { get; }
// Use the constructor to actually compose the instance
// either by creating the dependencies locally
// or by requesting the via constructor parameters
public T()
=> this.UFunctionality = new U();
public void ExecuteSomeExternalFunctionality()
=> this.UFunctionality.DoSomething();
}
F1.cs
Example code for F1
, F2
and F3
.
class F1
{
// Functionality extracted from P
private Q QFunctionality { get; }
// Functionality extracted from T
private U UFunctionality { get; }
// Use the constructor to actually compose the instance
// either by creating the dependencies locally
// or by requesting the via constructor parameters
public F1()
{
this.QFunctionality = new Q();
this.UFunctionality = new U();
}
public void ExecuteSomeQExternalFunctionality()
=> this.QFunctionality.DoSomething();
public void ExecuteSomeUExternalFunctionality()
=> this.UFunctionality.DoSomething();
}
Solution 2: Inheritance
Although it appears that P
and T
are not related, we can still use inheritance by merging P
and T
into a common type hierarchy. This eliminates the multi-inheritance scenario.
Without knowing more details about your classes I'm pretty sure that inheritance is the wrong choice, semantically and contextually.
In other words F1
is not P
and F1
is not T
. Furthermore, P
is not T
. As a result F1
would therefore inherit functionality it is not supposed to have.
If inheritance would reflect the correct relationship you had already used it intuitively.
But because there are cases where this transformation makes sense I will continue to act like your scenario is one of those just to show how to convert multi-inheritance to single-inheritance (in cases where you have full access to the sources in order to refactor the type hierarchy).
This example also shows why composition is the better solution. If you compare both solutions, you will instantly see that inheritance for the only purpose to provide functionality to unrelated types is just wrong.
This example, the ugliness of the class design in particular, shows that inheritance is much more than simply making functionality available.
Again, inheritance is about specialization of a type hierarchy, from the root to the leafs of the inheritance tree, from the generalized type to the most specialized types.
And semantics help to ensure that we are not violating the specialization. The Liskov Substitution principle (LSP) is another good principle that helps to identify when inheritance must be avoided.
P.cs
Derives from the .NET UserControl
.
class P : UserControl
{
// Additional P functionality
}
T.cs
Now, T
derives from the .NET UserControl
class by extending P
.
This is where the design starts to smell.
class T : P
{
// Additional T functionality
}
F1.cs
By letting F1
, F2
and F3
derive from T
it inherits the desired functionality from P
and T
- but also from UserControl
. Additionally, F1
, F2
and F3
potentially inherits a lot of functionality from P
and T
that they are not supposed to have. At this point the wrong semantics have become obvious.
This is where the code smell (or design smell) becomes obnoxious.
class F1 : T
{
}
To answer your question regarding interfaces:
"what is the scenario (condition, motivation?) that would compel a programmer to implement a C# interface?"
"Implementing an interface" means to provide or write a class that implements the contract, the public members defined by the interface. If this is really what you mean, then you must know that interfaces are like abstract classes: you can't create instances of abstract classes or interfaces. In order to create an instance you must implement an interface or extend the abstract class.
If you meant why to use interfaces, I recommend to look up the SOLID principles. The letters "I" (Interface Segregation principle) and "D" (Dependency Inversion principle) give you good and important idea how to use interfaces.
Interfaces are an alternative to inheritance and can be used like base types in order to enable polymorphism. Interfaces are like abstract classes except they don't allow for virtual and protected members because they are not allowed to define implementations (except for default members staring with C# 8.0), constructors or derive from classes.