Fake Multiple Inheritance in C#

Why is it fake ? Because C# doesn’t really support multiple inheritance. But we can pull off the similar effect with the given tools from C#: interface and extension method (aha!) or default interface methods. The mechanism is very basic:

  • In C# we can implement as many interfaces as we want
  • We can decorate a type with more functionalities by using methods external to the class itself or its derived class. These external methods to extend a type are called extension methods. It’s quite a luxury of C# over Java, really. If you know that LINQ queries are based on extension methods given to the type that they operate on, you will be surprised at how powerful of a tool C# gives us.
  • Since C# 8 and above, we can declare the default implementations as interface methods. Then, every class automatically uses the default implementation. It is somewhat similar to extension methods, but yield better performance and provide better encapsulation.
  • By using extension methods or default interface method, we can give an interface default implementations.
  • By using default implementations of multiple interfaces, we can reuse methods across classes, or practically fake multiple inheritance.

Voil√† it doesn’t feel so fake after all. However please remember what we are implementing is not multiple inheritance because we make use of extensions rather than hierarchical relationships.

The steps are straightforward. As a C# developer you know at least how to implement an interface already. In our example we’ll have MyConcrete class implements two interfaces MyInterface1 and MyInterface2. We will see how we can give each of theses interface a default implementation using extension methods and default interface method (C# 8.0 and above).

 

Extension methods

public static class MyInterface1Extension
{
        public static void MyMethod(this MyInterface1 obj)
        {
            Console.WriteLine("I'm from Interface 1");
        }
}

public static class MyInterface2Extension
{
        public static void MyMethod(this MyInterface2 obj)
        {
            Console.WriteLine("I'm from Interface 2");
        }
}

Pay close attention to the this keyword, it marks the type that the method is extending. Both the extension method and container class has to be static and you have to explicitly import the namespace of the extension class.

class Program
{
        static void Main(string[] args)
        {
            var run = new MyConcrete();
            run.MyMethod();
        }
}

public class MyConcrete : MyInterface1, MyInterface2
{  
}

public interface MyInterface1
{       
}

public interface MyInterface2
{       
}

As MyConcrete implements MyInterface1 and MyInterface2, methods that extend these two interfaces also define MyConcrete type. When run, the program will print I'm from Interface 1. This is because the class MyConcrete does not have any method with the same name to override MyMethod() from the default implementations of MyInterface1 and MyInterface2. When the program compiles, the compiler binds MyMethod() to the first extension method it can find for MyConcrete type, in this case MyInterface1Extension.MyMethod().

 

Default interface methods

Since the C# 8.0 (shipped with .NET Core 3.0), we can provide interface methods with default concrete implementation, free the need to implement these methods in concrete class.

In our example below, we do not need to implement MyMethod() in MyConcrete class, because both of its interfaces have given a default implementation each. Note that when call MyMethod() on MyConcrete object, we have to first cast it to either of the interfaces. Fail to do so the code will not compile. If MyMethod() exists in MyConcrete() we do not have to cast. The explicit cast is a great way to avoid potential ambiguities.

class Program
{
        static void Main(string[] args)
        {
            var run = new MyConcrete();
            var run2 = run as MyInterface1;
            run2.MyMethod();
        }
}

public class MyConcrete : MyInterface1, MyInterface2
{  
}

public interface MyInterface1
{       
         public void MyMethod() =>
            Console.WriteLine("Default implementation from MyInterface1");
}

public interface MyInterface2
{       
        public void MyMethod()
        {
            Console.WriteLine("Default implementation from MyInterface2");
        }
}

 

Diamond problem

Diamond is a luxury but can also lead to all sorts of problems. The famous enigma of multiple inheritance is the “Deadly Diamond of Death”. To understand this we will introduce a new interface MySuperInterface.

class Program
{
        static void Main(string[] args)
        {
            var run = new MyConcrete();
            run.MyMethod();
        }
}

public class MyConcrete : MyInterface1, MyInterface2
{  
}

public interface MyInterface1: MySuperInterface
{       
}

public interface MyInterface2: MySuperInterface
{       
}

public interface MySuperInterface
{       
}

Now our program doesn’t compile anymore, the compiler complains that it doesn’t know which of MyMethod() you are calling. To resolve the error, you have to override the method in MyConcrete class, or explicitly call MyInterface1Extension.MyMethod(run). Note that the introduction of MySuperInterface does not create a diamond problem, it only invalidates our syntax as a way for the compiler to avoid the problem later on. In fact, via extensions we never have to encounter the diamond problem.

Via default interface methods, this ambiguity flag however doesn’t appear because we either need an explicit cast or actual implementation in our concrete class.

 

Why (fake) multiple inheritance

Be careful with what you wish for ! There are times it is tempting to reuse methods from different classes via inheritance, this may make our life a little bit simpler than going full composition. However convenient it might sound, multiple inheritance can cause you nightmare because of the complexity you are introducing to the inheritance tree. Inheritance alone is already prone to much abused, now multiple inheritance means the danger power n.

C# offers us alternative ways to pull off the similar desired effect without going deep into inheritance. By using extension methods and default interface implementation, we compose our classes with functionalities from external objects. What we’ve been doing is known as trait programming -another way to do composition.

Related posts

Leave a Reply

Your email address will not be published.